1
Fork 0
mirror of https://github.com/caddyserver/caddy.git synced 2024-12-16 21:56:40 -05:00

Begin implementing HTTP replacer and static responder

This commit is contained in:
Matthew Holt 2019-05-04 13:21:20 -06:00
parent 1136e2cfee
commit 2eb3593327
6 changed files with 161 additions and 30 deletions

View file

@ -9,7 +9,7 @@ func BenchmarkLoad(b *testing.B) {
for i := 0; i < b.N; i++ {
r := strings.NewReader(`{
"testval": "Yippee!",
"modules": {
"apps": {
"http": {
"servers": {
"myserver": {

View file

@ -1,28 +1,18 @@
package main
import (
"log"
_ "net/http/pprof"
"bitbucket.org/lightcodelabs/caddy2"
caddycmd "bitbucket.org/lightcodelabs/caddy2/cmd"
// this is where modules get plugged in
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp"
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/caddylog"
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/reverseproxy"
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/staticfiles"
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/staticresp"
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddytls"
_ "bitbucket.org/lightcodelabs/dynamicconfig"
_ "bitbucket.org/lightcodelabs/proxy"
)
func main() {
err := caddy2.StartAdmin("127.0.0.1:1234")
if err != nil {
log.Fatal(err)
}
defer caddy2.StopAdmin()
select {}
caddycmd.Main()
}

View file

@ -167,14 +167,15 @@ func (hc *httpModuleConfig) automaticHTTPS(handle caddy2.Handle) error {
var defaultALPN = []string{"h2", "http/1.1"}
type httpServerConfig struct {
Listen []string `json:"listen"`
ReadTimeout caddy2.Duration `json:"read_timeout"`
ReadHeaderTimeout caddy2.Duration `json:"read_header_timeout"`
HiddenFiles []string `json:"hidden_files"` // TODO:... experimenting with shared/common state
Routes routeList `json:"routes"`
Errors httpErrorConfig `json:"errors"`
TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies"`
DisableAutoHTTPS bool `json:"disable_auto_https"`
Listen []string `json:"listen"`
ReadTimeout caddy2.Duration `json:"read_timeout"`
ReadHeaderTimeout caddy2.Duration `json:"read_header_timeout"`
HiddenFiles []string `json:"hidden_files"` // TODO:... experimenting with shared/common state
Routes routeList `json:"routes"`
Errors httpErrorConfig `json:"errors"`
TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies"`
DisableAutoHTTPS bool `json:"disable_auto_https"`
DisableAutoHTTPSRedir bool `json:"disable_auto_https_redir"`
tlsApp *caddytls.TLS
}
@ -190,6 +191,12 @@ func (s httpServerConfig) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
// set up the replacer
repl := &Replacer{req: r, resp: w, custom: make(map[string]string)}
ctx := context.WithValue(r.Context(), ReplacerCtxKey, repl)
r = r.WithContext(ctx)
// build and execute the main middleware chain
stack := s.Routes.buildMiddlewareChain(w, r)
err := executeMiddlewareChain(w, r, stack)
if err != nil {
@ -329,5 +336,7 @@ func (mrw middlewareResponseWriter) Write(b []byte) (int, error) {
return mrw.ResponseWriterWrapper.Write(b)
}
const ReplacerCtxKey caddy2.CtxKey = "replacer"
// Interface guards
var _ HTTPInterfaces = middlewareResponseWriter{}

View file

@ -139,11 +139,11 @@ func (m matchHeader) Match(r *http.Request) bool {
// Interface guards
var (
_ RouteMatcher = matchHost{}
_ RouteMatcher = matchPath{}
_ RouteMatcher = matchMethod{}
_ RouteMatcher = matchQuery{}
_ RouteMatcher = matchHeader{}
_ RouteMatcher = new(matchProtocol)
_ RouteMatcher = new(matchScript)
_ RouteMatcher = (*matchHost)(nil)
_ RouteMatcher = (*matchPath)(nil)
_ RouteMatcher = (*matchMethod)(nil)
_ RouteMatcher = (*matchQuery)(nil)
_ RouteMatcher = (*matchHeader)(nil)
_ RouteMatcher = (*matchProtocol)(nil)
_ RouteMatcher = (*matchScript)(nil)
)

View file

@ -0,0 +1,75 @@
package caddyhttp
import (
"net/http"
"strings"
)
type Replacer struct {
req *http.Request
resp http.ResponseWriter
custom map[string]string
}
// Map sets a custom variable mapping to a value.
func (r *Replacer) Map(variable, value string) {
r.custom[variable] = value
}
// Replace replaces placeholders in input with the value. If
// the value is empty string, the placeholder is substituted
// with the value empty.
func (r *Replacer) Replace(input, empty string) string {
if !strings.Contains(input, phOpen) {
return input
}
input = r.replaceAll(input, empty, r.defaults())
input = r.replaceAll(input, empty, r.custom)
return input
}
func (r *Replacer) replaceAll(input, empty string, mapping map[string]string) string {
for key, val := range mapping {
if val == "" {
val = empty
}
input = strings.ReplaceAll(input, phOpen+key+phClose, val)
}
return input
}
func (r *Replacer) defaults() map[string]string {
m := map[string]string{
"host": r.req.Host,
"method": r.req.Method,
"scheme": func() string {
if r.req.TLS != nil {
return "https"
}
return "http"
}(),
"uri": r.req.URL.RequestURI(),
}
for field, vals := range r.req.Header {
m[">"+strings.ToLower(field)] = strings.Join(vals, ",")
}
for field, vals := range r.resp.Header() {
m["<"+strings.ToLower(field)] = strings.Join(vals, ",")
}
for _, cookie := range r.req.Cookies() {
m["~"+cookie.Name] = cookie.Value
}
for param, vals := range r.req.URL.Query() {
m["?"+param] = strings.Join(vals, ",")
}
return m
}
const phOpen, phClose = "{", "}"

View file

@ -0,0 +1,57 @@
package staticresp
import (
"fmt"
"net/http"
"bitbucket.org/lightcodelabs/caddy2"
"bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp"
)
func init() {
caddy2.RegisterModule(caddy2.Module{
Name: "http.responders.static",
New: func() (interface{}, error) { return new(Static), nil },
})
}
// Static implements a simple responder for static responses.
type Static struct {
StatusCode int `json:"status_code"`
Headers map[string][]string `json:"headers"`
Body string `json:"body"`
Close bool `json:"close"`
}
func (s Static) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
repl := r.Context().Value(caddyhttp.ReplacerCtxKey).(*caddyhttp.Replacer)
// close the connection
r.Close = s.Close
// set all headers, with replacements
for field, vals := range s.Headers {
field = repl.Replace(field, "")
for i := range vals {
vals[i] = repl.Replace(vals[i], "")
}
w.Header()[field] = vals
}
// write the headers with a status code
statusCode := s.StatusCode
if statusCode == 0 {
statusCode = http.StatusOK
}
w.WriteHeader(statusCode)
// write the response body, with replacements
if s.Body != "" {
fmt.Fprint(w, repl.Replace(s.Body, ""))
}
return nil
}
// Interface guard
var _ caddyhttp.Handler = (*Static)(nil)