diff options
Diffstat (limited to 'modules/caddyhttp')
-rw-r--r-- | modules/caddyhttp/caddyhttp.go | 23 | ||||
-rw-r--r-- | modules/caddyhttp/errors.go | 6 | ||||
-rw-r--r-- | modules/caddyhttp/fileserver/caddyfile.go | 3 | ||||
-rw-r--r-- | modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go | 3 | ||||
-rw-r--r-- | modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go | 6 | ||||
-rw-r--r-- | modules/caddyhttp/reverseproxy/healthchecks.go | 4 | ||||
-rw-r--r-- | modules/caddyhttp/reverseproxy/hosts.go | 15 | ||||
-rw-r--r-- | modules/caddyhttp/reverseproxy/httptransport.go | 3 | ||||
-rw-r--r-- | modules/caddyhttp/reverseproxy/reverseproxy.go | 3 | ||||
-rw-r--r-- | modules/caddyhttp/rewrite/caddyfile.go | 5 | ||||
-rw-r--r-- | modules/caddyhttp/rewrite/rewrite.go | 19 | ||||
-rw-r--r-- | modules/caddyhttp/routes.go | 168 | ||||
-rw-r--r-- | modules/caddyhttp/server.go | 78 | ||||
-rw-r--r-- | modules/caddyhttp/starlarkmw/starlarkmw.go | 10 | ||||
-rw-r--r-- | modules/caddyhttp/subroute.go | 15 | ||||
-rw-r--r-- | modules/caddyhttp/vars.go | 22 |
16 files changed, 184 insertions, 199 deletions
diff --git a/modules/caddyhttp/caddyhttp.go b/modules/caddyhttp/caddyhttp.go index 38e9a65..0aa1d6c 100644 --- a/modules/caddyhttp/caddyhttp.go +++ b/modules/caddyhttp/caddyhttp.go @@ -171,6 +171,9 @@ func (app *App) Provision(ctx caddy.Context) error { if err != nil { return fmt.Errorf("server %s: setting up server routes: %v", srvName, err) } + // pre-compile the handler chain, and be sure to wrap it in our + // route handler so that important security checks are done, etc. + srv.primaryHandlerChain = srv.wrapPrimaryRoute(srv.Routes.Compile()) } if srv.Errors != nil { @@ -178,10 +181,7 @@ func (app *App) Provision(ctx caddy.Context) error { if err != nil { return fmt.Errorf("server %s: setting up server error handling routes: %v", srvName, err) } - } - - if srv.MaxRehandles == nil { - srv.MaxRehandles = &DefaultMaxRehandles + srv.errorHandlerChain = srv.Errors.Routes.Compile() } } @@ -210,13 +210,6 @@ func (app *App) Validate() error { } } - // each server's max rehandle value must be valid - for srvName, srv := range app.Servers { - if srv.MaxRehandles != nil && *srv.MaxRehandles < 0 { - return fmt.Errorf("%s: invalid max_rehandles value: %d", srvName, *srv.MaxRehandles) - } - } - return nil } @@ -608,7 +601,7 @@ func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) error { // Middleware chains one Handler to the next by being passed // the next Handler in the chain. -type Middleware func(HandlerFunc) HandlerFunc +type Middleware func(Handler) Handler // MiddlewareHandler is like Handler except it takes as a third // argument the next handler in the chain. The next handler will @@ -624,7 +617,7 @@ type MiddlewareHandler interface { } // emptyHandler is used as a no-op handler. -var emptyHandler HandlerFunc = func(http.ResponseWriter, *http.Request) error { return nil } +var emptyHandler Handler = HandlerFunc(func(http.ResponseWriter, *http.Request) error { return nil }) // WeakString is a type that unmarshals any JSON value // as a string literal, with the following exceptions: @@ -734,10 +727,6 @@ const ( DefaultHTTPSPort = 443 ) -// DefaultMaxRehandles is the maximum number of rehandles to -// allow, if not specified explicitly. -var DefaultMaxRehandles = 3 - // Interface guards var ( _ caddy.App = (*App)(nil) diff --git a/modules/caddyhttp/errors.go b/modules/caddyhttp/errors.go index 584f56f..0593063 100644 --- a/modules/caddyhttp/errors.go +++ b/modules/caddyhttp/errors.go @@ -106,12 +106,6 @@ func trace() string { return "" } -// ErrRehandle is a special error value that Handlers should return -// from their ServeHTTP() method if the request is to be re-processed. -// This error value is a sentinel value that should not be wrapped or -// modified. -var ErrRehandle = fmt.Errorf("rehandling request") - // ErrorCtxKey is the context key to use when storing // an error (for use with context.Context). const ErrorCtxKey = caddy.CtxKey("handler_chain_error") diff --git a/modules/caddyhttp/fileserver/caddyfile.go b/modules/caddyhttp/fileserver/caddyfile.go index 3bd0ae4..8013fa2 100644 --- a/modules/caddyhttp/fileserver/caddyfile.go +++ b/modules/caddyhttp/fileserver/caddyfile.go @@ -127,8 +127,7 @@ func parseTryFiles(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error) // to the end of the query string. makeRoute := func(try []string, writeURIAppend string) []httpcaddyfile.ConfigValue { handler := rewrite.Rewrite{ - Rehandle: true, - URI: "{http.matchers.file.relative}{http.request.uri.query_string}" + writeURIAppend, + URI: "{http.matchers.file.relative}{http.request.uri.query_string}" + writeURIAppend, } matcherSet := caddy.ModuleMap{ "file": h.JSON(MatchFile{ diff --git a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go index 5317048..a9b02a9 100644 --- a/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go +++ b/modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go @@ -149,8 +149,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error }), } rewriteHandler := rewrite.Rewrite{ - URI: "{http.matchers.file.relative}{http.request.uri.query_string}", - Rehandle: true, + URI: "{http.matchers.file.relative}{http.request.uri.query_string}", } rewriteRoute := caddyhttp.Route{ MatcherSetsRaw: []caddy.ModuleMap{rewriteMatcherSet}, diff --git a/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go index 9a424b0..aa0d1cd 100644 --- a/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go +++ b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go @@ -102,11 +102,9 @@ func (t Transport) RoundTrip(r *http.Request) (*http.Response, error) { defer cancel() } - // extract dial information from request (this - // should embedded by the reverse proxy) + // extract dial information from request (should have been embedded by the reverse proxy) network, address := "tcp", r.URL.Host - if dialInfoVal := ctx.Value(reverseproxy.DialInfoCtxKey); dialInfoVal != nil { - dialInfo := dialInfoVal.(reverseproxy.DialInfo) + if dialInfo, ok := reverseproxy.GetDialInfo(ctx); ok { network = dialInfo.Network address = dialInfo.Address } diff --git a/modules/caddyhttp/reverseproxy/healthchecks.go b/modules/caddyhttp/reverseproxy/healthchecks.go index 76ee945..a3b57e1 100644 --- a/modules/caddyhttp/reverseproxy/healthchecks.go +++ b/modules/caddyhttp/reverseproxy/healthchecks.go @@ -212,7 +212,9 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, host H // attach dialing information to this request ctx := context.Background() ctx = context.WithValue(ctx, caddy.ReplacerCtxKey, caddy.NewReplacer()) - ctx = context.WithValue(ctx, DialInfoCtxKey, dialInfo) + ctx = context.WithValue(ctx, caddyhttp.VarsCtxKey, map[string]interface{}{ + dialInfoVarKey: dialInfo, + }) req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) if err != nil { return fmt.Errorf("making request: %v", err) diff --git a/modules/caddyhttp/reverseproxy/hosts.go b/modules/caddyhttp/reverseproxy/hosts.go index 6e25c66..54de5a8 100644 --- a/modules/caddyhttp/reverseproxy/hosts.go +++ b/modules/caddyhttp/reverseproxy/hosts.go @@ -15,11 +15,13 @@ package reverseproxy import ( + "context" "fmt" "strconv" "sync/atomic" "github.com/caddyserver/caddy/v2" + "github.com/caddyserver/caddy/v2/modules/caddyhttp" ) // Host represents a remote host which can be proxied to. @@ -223,12 +225,19 @@ func fillDialInfo(upstream *Upstream, repl *caddy.Replacer) (DialInfo, error) { }, nil } -// DialInfoCtxKey is used to store a DialInfo -// in a context.Context. -const DialInfoCtxKey = caddy.CtxKey("dial_info") +// GetDialInfo gets the upstream dialing info out of the context, +// and returns true if there was a valid value; false otherwise. +func GetDialInfo(ctx context.Context) (DialInfo, bool) { + dialInfo, ok := caddyhttp.GetVar(ctx, dialInfoVarKey).(DialInfo) + return dialInfo, ok +} // hosts is the global repository for hosts that are // currently in use by active configuration(s). This // allows the state of remote hosts to be preserved // through config reloads. var hosts = caddy.NewUsagePool() + +// dialInfoVarKey is the key used for the variable that holds +// the dial info for the upstream connection. +const dialInfoVarKey = "reverse_proxy.dial_info" diff --git a/modules/caddyhttp/reverseproxy/httptransport.go b/modules/caddyhttp/reverseproxy/httptransport.go index 75f2786..044bb83 100644 --- a/modules/caddyhttp/reverseproxy/httptransport.go +++ b/modules/caddyhttp/reverseproxy/httptransport.go @@ -92,8 +92,7 @@ func (h *HTTPTransport) newTransport() (*http.Transport, error) { rt := &http.Transport{ DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { // the proper dialing information should be embedded into the request's context - if dialInfoVal := ctx.Value(DialInfoCtxKey); dialInfoVal != nil { - dialInfo := dialInfoVal.(DialInfo) + if dialInfo, ok := GetDialInfo(ctx); ok { network = dialInfo.Network address = dialInfo.Address } diff --git a/modules/caddyhttp/reverseproxy/reverseproxy.go b/modules/caddyhttp/reverseproxy/reverseproxy.go index d0c4833..acb5213 100644 --- a/modules/caddyhttp/reverseproxy/reverseproxy.go +++ b/modules/caddyhttp/reverseproxy/reverseproxy.go @@ -320,8 +320,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht // attach to the request information about how to dial the upstream; // this is necessary because the information cannot be sufficiently // or satisfactorily represented in a URL - ctx := context.WithValue(r.Context(), DialInfoCtxKey, dialInfo) - r = r.WithContext(ctx) + caddyhttp.SetVar(r.Context(), dialInfoVarKey, dialInfo) // set placeholders with information about this upstream repl.Set("http.reverse_proxy.upstream.address", dialInfo.String()) diff --git a/modules/caddyhttp/rewrite/caddyfile.go b/modules/caddyhttp/rewrite/caddyfile.go index 6674313..532df9a 100644 --- a/modules/caddyhttp/rewrite/caddyfile.go +++ b/modules/caddyhttp/rewrite/caddyfile.go @@ -44,7 +44,6 @@ func parseCaddyfileRewrite(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, return nil, h.ArgErr() } } - rewr.Rehandle = true return rewr, nil } @@ -52,7 +51,7 @@ func parseCaddyfileRewrite(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, // // strip_prefix [<matcher>] <prefix> // -// The request path will be stripped its prefix if it matches <prefix>. +// The request path will be stripped the given prefix. func parseCaddyfileStripPrefix(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { var rewr Rewrite for h.Next() { @@ -71,7 +70,7 @@ func parseCaddyfileStripPrefix(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHand // // strip_suffix [<matcher>] <suffix> // -// The request path will be stripped its suffix if it matches <suffix>. +// The request path will be stripped the given suffix. func parseCaddyfileStripSuffix(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) { var rewr Rewrite for h.Next() { diff --git a/modules/caddyhttp/rewrite/rewrite.go b/modules/caddyhttp/rewrite/rewrite.go index b875adb..09478b8 100644 --- a/modules/caddyhttp/rewrite/rewrite.go +++ b/modules/caddyhttp/rewrite/rewrite.go @@ -15,7 +15,6 @@ package rewrite import ( - "fmt" "net/http" "net/url" "strconv" @@ -32,9 +31,6 @@ func init() { // Rewrite is a middleware which can rewrite HTTP requests. // -// The Rehandle and HTTPRedirect properties are mutually exclusive -// (you cannot both rehandle and issue a redirect). -// // These rewrite properties are applied to a request in this order: // Method, URI, StripPathPrefix, StripPathSuffix, URISubstring. // @@ -61,10 +57,6 @@ type Rewrite struct { // given status code. HTTPRedirect caddyhttp.WeakString `json:"http_redirect,omitempty"` - // If true, the request will sent for rehandling after rewriting - // only if anything about the request was changed. - Rehandle bool `json:"rehandle,omitempty"` - logger *zap.Logger } @@ -82,14 +74,6 @@ func (rewr *Rewrite) Provision(ctx caddy.Context) error { return nil } -// Validate ensures rewr's configuration is valid. -func (rewr Rewrite) Validate() error { - if rewr.HTTPRedirect != "" && rewr.Rehandle { - return fmt.Errorf("cannot be configured to both redirect externally and rehandle internally") - } - return nil -} - func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error { repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) @@ -104,9 +88,6 @@ func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy zap.String("method", r.Method), zap.String("uri", r.RequestURI), ) - if rewr.Rehandle { - return caddyhttp.ErrRehandle - } if rewr.HTTPRedirect != "" { statusCode, err := strconv.Atoi(repl.ReplaceAll(rewr.HTTPRedirect.String(), "")) if err != nil { diff --git a/modules/caddyhttp/routes.go b/modules/caddyhttp/routes.go index 9b2f849..4ae9bcd 100644 --- a/modules/caddyhttp/routes.go +++ b/modules/caddyhttp/routes.go @@ -28,13 +28,15 @@ import ( // in a highly flexible and performant manner. type Route struct { // Group is an optional name for a group to which this - // route belongs. If a route belongs to a group, only - // the first matching route in the group will be used. + // route belongs. Grouping a route makes it mutually + // exclusive with others in its group; if a route belongs + // to a group, only the first matching route in that group + // will be executed. Group string `json:"group,omitempty"` // The matcher sets which will be used to qualify this - // route for a request. Essentially the "if" statement - // of this route. Each matcher set is OR'ed, but matchers + // route for a request (essentially the "if" statement + // of this route). Each matcher set is OR'ed, but matchers // within a set are AND'ed together. MatcherSetsRaw RawMatcherSets `json:"match,omitempty" caddy:"namespace=http.matchers"` @@ -87,12 +89,14 @@ type Route struct { // If you think of routes in this way, it will be easy and even fun to solve the puzzle of writing correct routes. HandlersRaw []json.RawMessage `json:"handle,omitempty" caddy:"namespace=http.handlers inline_key=handler"` - // If true, no more routes will be executed after this one, even if they matched. + // If true, no more routes will be executed after this one. Terminal bool `json:"terminal,omitempty"` // decoded values MatcherSets MatcherSets `json:"-"` Handlers []MiddlewareHandler `json:"-"` + + middleware []Middleware } // Empty returns true if the route has all zero/default values. @@ -111,9 +115,9 @@ type RouteList []Route // Provision sets up all the routes by loading the modules. func (routes RouteList) Provision(ctx caddy.Context) error { - for i, route := range routes { + for i := range routes { // matchers - matchersIface, err := ctx.LoadModule(&route, "MatcherSetsRaw") + matchersIface, err := ctx.LoadModule(&routes[i], "MatcherSetsRaw") if err != nil { return fmt.Errorf("loading matchers in route %d: %v", i, err) } @@ -123,93 +127,115 @@ func (routes RouteList) Provision(ctx caddy.Context) error { } // handlers - handlersIface, err := ctx.LoadModule(&route, "HandlersRaw") + handlersIface, err := ctx.LoadModule(&routes[i], "HandlersRaw") if err != nil { return fmt.Errorf("loading handler modules in route %d: %v", i, err) } for _, handler := range handlersIface.([]interface{}) { routes[i].Handlers = append(routes[i].Handlers, handler.(MiddlewareHandler)) } + + // pre-compile the middleware handler chain + for _, midhandler := range routes[i].Handlers { + routes[i].middleware = append(routes[i].middleware, wrapMiddleware(midhandler)) + } } + return nil } -// BuildCompositeRoute creates a chain of handlers by -// applying all of the matching routes. -func (routes RouteList) BuildCompositeRoute(req *http.Request) Handler { - if len(routes) == 0 { - return emptyHandler +// Compile prepares a middleware chain from the route list. +// This should only be done once: after all the routes have +// been provisioned, and before serving requests. +func (routes RouteList) Compile() Handler { + var mid []Middleware + for _, route := range routes { + mid = append(mid, wrapRoute(route)) + } + stack := emptyHandler + for i := len(mid) - 1; i >= 0; i-- { + stack = mid[i](stack) } + return stack +} - var mid []Middleware - groups := make(map[string]struct{}) +// wrapRoute wraps route with a middleware and handler so that it can +// be chained in and defer evaluation of its matchers to request-time. +// Like wrapMiddleware, it is vital that this wrapping takes place in +// its own stack frame so as to not overwrite the reference to the +// intended route by looping and changing the reference each time. +func wrapRoute(route Route) Middleware { + return func(next Handler) Handler { + return HandlerFunc(func(rw http.ResponseWriter, req *http.Request) error { + // copy the next handler (it's an interface, so it's just + // a very lightweight copy of a pointer); this is important + // because this is a closure to the func below, which + // re-assigns the value as it compiles the middleware stack; + // if we don't make this copy, we'd affect the underlying + // pointer for all future request (yikes); we could + // alternatively solve this by moving the func below out of + // this closure and into a standalone package-level func, + // but I just thought this made more sense + nextCopy := next - for _, route := range routes { - // route must match at least one of the matcher sets - if !route.MatcherSets.AnyMatch(req) { - continue - } + // route must match at least one of the matcher sets + if !route.MatcherSets.AnyMatch(req) { + return nextCopy.ServeHTTP(rw, req) + } - // if route is part of a group, ensure only the - // first matching route in the group is applied - if route.Group != "" { - _, ok := groups[route.Group] - if ok { - // this group has already been satisfied - // by a matching route - continue + // if route is part of a group, ensure only the + // first matching route in the group is applied + if route.Group != "" { + groups := req.Context().Value(routeGroupCtxKey).(map[string]struct{}) + + if _, ok := groups[route.Group]; ok { + // this group has already been + // satisfied by a matching route + return nextCopy.ServeHTTP(rw, req) + } + + // this matching route satisfies the group + groups[route.Group] = struct{}{} } - // this matching route satisfies the group - groups[route.Group] = struct{}{} - } - // apply the rest of the route - for _, mh := range route.Handlers { - // we have to be sure to wrap mh outside - // of our current stack frame so that the - // reference to this mh isn't overwritten - // on the next iteration, leaving the last - // middleware in the chain as the ONLY - // middleware in the chain! - mid = append(mid, wrapMiddleware(mh)) - } + // make terminal routes terminate + if route.Terminal { + nextCopy = emptyHandler + } - // if this route is supposed to be last, don't - // compile any more into the chain - if route.Terminal { - break - } - } + // compile this route's handler stack + for i := len(route.middleware) - 1; i >= 0; i-- { + nextCopy = route.middleware[i](nextCopy) + } - // build the middleware chain, with the responder at the end - stack := emptyHandler - for i := len(mid) - 1; i >= 0; i-- { - stack = mid[i](stack) + return nextCopy.ServeHTTP(rw, req) + }) } - - return stack } -// wrapMiddleware wraps m such that it can be correctly -// appended to a list of middleware. We can't do this -// directly in a loop because it relies on a reference -// to mh not changing until the execution of its handler, -// which is deferred by multiple func closures. In other -// words, we need to pull this particular MiddlewareHandler +// wrapMiddleware wraps mh such that it can be correctly +// appended to a list of middleware in preparation for +// compiling into a handler chain. We can't do this inline +// inside a loop, because it relies on a reference to mh +// not changing until the execution of its handler (which +// is deferred by multiple func closures). In other words, +// we need to pull this particular MiddlewareHandler // pointer into its own stack frame to preserve it so it // won't be overwritten in future loop iterations. func wrapMiddleware(mh MiddlewareHandler) Middleware { - return func(next HandlerFunc) HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { - // TODO: We could wait to evaluate matchers here, just eval - // the next matcher and choose the next route... - - // TODO: This is where request tracing could be implemented; also - // see below to trace the responder as well - // TODO: Trace a diff of the request, would be cool too! see what changed since the last middleware (host, headers, URI...) + return func(next Handler) Handler { + // copy the next handler (it's an interface, so it's + // just a very lightweight copy of a pointer); this + // is a safeguard against the handler changing the + // value, which could affect future requests (yikes) + nextCopy := next + + return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { + // TODO: This is where request tracing could be implemented + // TODO: Trace a diff of the request, would be cool too... see what changed since the last middleware (host, headers, URI...) // TODO: see what the std lib gives us in terms of stack tracing too - return mh.ServeHTTP(w, r, next) - } + return mh.ServeHTTP(w, r, nextCopy) + }) } } @@ -219,7 +245,7 @@ func wrapMiddleware(mh MiddlewareHandler) Middleware { type MatcherSet []RequestMatcher // Match returns true if the request matches all -// matchers in mset. +// matchers in mset or if there are no matchers. func (mset MatcherSet) Match(r *http.Request) bool { for _, m := range mset { if !m.Match(r) { @@ -265,3 +291,5 @@ func (ms *MatcherSets) FromInterface(matcherSets interface{}) error { } return nil } + +var routeGroupCtxKey = caddy.CtxKey("route_group") diff --git a/modules/caddyhttp/server.go b/modules/caddyhttp/server.go index c333991..ce61b13 100644 --- a/modules/caddyhttp/server.go +++ b/modules/caddyhttp/server.go @@ -61,10 +61,12 @@ type Server struct { MaxHeaderBytes int `json:"max_header_bytes,omitempty"` // Routes describes how this server will handle requests. - // When a request comes in, each route's matchers will - // be evaluated against the request, and matching routes - // will be compiled into a middleware chain in the order - // in which they appear in the list. + // Routes are executed sequentially. First a route's matchers + // are evaluated, then its grouping. If it matches and has + // not been mutually-excluded by its grouping, then its + // handlers are executed sequentially. The sequence of invoked + // handlers comprises a compiled middleware chain that flows + // from each matching route and its handlers to the next. Routes RouteList `json:"routes,omitempty"` // Errors is how this server will handle errors returned from any @@ -86,11 +88,6 @@ type Server struct { // only on the HTTPS port. AutoHTTPS *AutoHTTPSConfig `json:"automatic_https,omitempty"` - // MaxRehandles is the maximum number of times to allow a - // request to be rehandled, to prevent accidental infinite - // loops. Default: 1. - MaxRehandles *int `json:"max_rehandles,omitempty"` - // If true, will require that a request's Host header match // the value of the ServerName sent by the client's TLS // ClientHello; often a necessary safeguard when using TLS @@ -105,6 +102,9 @@ type Server struct { // This field is not subject to compatibility promises. ExperimentalHTTP3 bool `json:"experimental_http3,omitempty"` + primaryHandlerChain Handler + errorHandlerChain Handler + tlsApp *caddytls.TLS logger *zap.Logger accessLogger *zap.Logger @@ -129,6 +129,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx := context.WithValue(r.Context(), caddy.ReplacerCtxKey, repl) ctx = context.WithValue(ctx, ServerCtxKey, s) ctx = context.WithValue(ctx, VarsCtxKey, make(map[string]interface{})) + ctx = context.WithValue(ctx, routeGroupCtxKey, make(map[string]struct{})) var url2 url.URL // avoid letting this escape to the heap ctx = context.WithValue(ctx, OriginalRequestCtxKey, originalRequest(r, &url2)) r = r.WithContext(ctx) @@ -137,22 +138,22 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // anymore, finish setting up the replacer addHTTPVarsToReplacer(repl, r, w) - loggableReq := LoggableHTTPRequest{r} + // encode the request for logging purposes before + // it enters any handler chain; this is necessary + // to capture the original request in case it gets + // modified during handling + loggableReq := zap.Object("request", LoggableHTTPRequest{r}) errLog := s.errorLogger.With( - // encode the request for logging purposes before - // it enters any handler chain; this is necessary - // to capture the original request in case it gets - // modified during handling - zap.Object("request", loggableReq), + loggableReq, ) if s.accessLogger != nil { wrec := NewResponseRecorder(w, nil, nil) w = wrec - accLog := s.accessLogger.With( - // capture the original version of the request - zap.Object("request", loggableReq), - ) + + // capture the original version of the request + accLog := s.accessLogger.With(loggableReq) + start := time.Now() defer func() { latency := time.Since(start) @@ -187,8 +188,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - // build and execute the primary handler chain - err := s.executeCompositeRoute(w, r, s.Routes) + // execute the primary handler chain + err := s.primaryHandlerChain.ServeHTTP(w, r) if err != nil { // prepare the error log logger := errLog @@ -204,7 +205,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { if s.Errors != nil && len(s.Errors.Routes) > 0 { // execute user-defined error handling route - err2 := s.executeCompositeRoute(w, r, s.Errors.Routes) + err2 := s.errorHandlerChain.ServeHTTP(w, r) if err2 == nil { // user's error route handled the error response // successfully, so now just log the error @@ -229,39 +230,6 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } -// executeCompositeRoute compiles a composite route from routeList and executes -// it using w and r. This function handles the sentinel ErrRehandle error value, -// which reprocesses requests through the stack again. Any error value returned -// from this function would be an actual error that needs to be handled. -func (s *Server) executeCompositeRoute(w http.ResponseWriter, r *http.Request, routeList RouteList) error { - maxRehandles := 0 - if s.MaxRehandles != nil { - maxRehandles = *s.MaxRehandles - } - var err error - for i := -1; i <= maxRehandles; i++ { - // we started the counter at -1 because we - // always want to run this at least once - - // the purpose of rehandling is often to give - // matchers a chance to re-evaluate on the - // changed version of the request, so compile - // the handler stack anew in each iteration - stack := routeList.BuildCompositeRoute(r) - stack = s.wrapPrimaryRoute(stack) - - // only loop if rehandling is required - err = stack.ServeHTTP(w, r) - if err != ErrRehandle { - break - } - if i >= maxRehandles-1 { - return fmt.Errorf("too many rehandles") - } - } - return err -} - // wrapPrimaryRoute wraps stack (a compiled middleware handler chain) // in s.enforcementHandler which performs crucial security checks, etc. func (s *Server) wrapPrimaryRoute(stack Handler) Handler { diff --git a/modules/caddyhttp/starlarkmw/starlarkmw.go b/modules/caddyhttp/starlarkmw/starlarkmw.go index 47e335d..bd42e13 100644 --- a/modules/caddyhttp/starlarkmw/starlarkmw.go +++ b/modules/caddyhttp/starlarkmw/starlarkmw.go @@ -59,7 +59,7 @@ func (s *StarlarkMW) ServeHTTP(w http.ResponseWriter, r *http.Request) error { } // dynamically build middleware chain for each request - stack := caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { + var stack caddyhttp.Handler = caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { wr, err := convert.ToValue(w) if err != nil { return fmt.Errorf("cannot convert response writer to starlark value") @@ -83,10 +83,10 @@ func (s *StarlarkMW) ServeHTTP(w http.ResponseWriter, r *http.Request) error { // TODO :- make middlewareResponseWriter exported and wrap w with that var mid []caddyhttp.Middleware for _, m := range s.execute.Modules { - mid = append(mid, func(next caddyhttp.HandlerFunc) caddyhttp.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) error { + mid = append(mid, func(next caddyhttp.Handler) caddyhttp.Handler { + return caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { return m.Instance.ServeHTTP(w, r, next) - } + }) }) } @@ -96,7 +96,7 @@ func (s *StarlarkMW) ServeHTTP(w http.ResponseWriter, r *http.Request) error { s.execute.Modules = nil - return stack(w, r) + return stack.ServeHTTP(w, r) } // Cleanup cleans up any modules loaded during the creation of a starlark route. diff --git a/modules/caddyhttp/subroute.go b/modules/caddyhttp/subroute.go index 13453a5..a324873 100644 --- a/modules/caddyhttp/subroute.go +++ b/modules/caddyhttp/subroute.go @@ -27,13 +27,12 @@ func init() { // Subroute implements a handler that compiles and executes routes. // This is useful for a batch of routes that all inherit the same -// matchers, or for routes with matchers that must be have deferred -// evaluation (e.g. if they depend on placeholders created by other -// matchers that need to be evaluated first). +// matchers, or for multiple routes that should be treated as a +// single route. // -// You can also use subroutes to handle errors from specific handlers. -// First the primary Routes will be executed, and if they return an -// error, the Errors routes will be executed; in that case, an error +// You can also use subroutes to handle errors from its handlers. +// First the primary routes will be executed, and if they return an +// error, the errors routes will be executed; in that case, an error // is only returned to the entry point at the server if there is an // additional error returned from the errors routes. type Subroute struct { @@ -71,11 +70,11 @@ func (sr *Subroute) Provision(ctx caddy.Context) error { } func (sr *Subroute) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error { - subroute := sr.Routes.BuildCompositeRoute(r) + subroute := sr.Routes.Compile() err := subroute.ServeHTTP(w, r) if err != nil && sr.Errors != nil { r = sr.Errors.WithError(r, err) - errRoute := sr.Errors.Routes.BuildCompositeRoute(r) + errRoute := sr.Errors.Routes.Compile() return errRoute.ServeHTTP(w, r) } return err diff --git a/modules/caddyhttp/vars.go b/modules/caddyhttp/vars.go index cd0d8d0..d6c5a66 100644 --- a/modules/caddyhttp/vars.go +++ b/modules/caddyhttp/vars.go @@ -15,6 +15,7 @@ package caddyhttp import ( + "context" "net/http" "github.com/caddyserver/caddy/v2" @@ -75,6 +76,27 @@ func (m VarsMatcher) Match(r *http.Request) bool { return true } +// GetVar gets a value out of the context's variable table by key. +// If the key does not exist, the return value will be nil. +func GetVar(ctx context.Context, key string) interface{} { + varMap, ok := ctx.Value(VarsCtxKey).(map[string]interface{}) + if !ok { + return nil + } + return varMap[key] +} + +// SetVar sets a value in the context's variable table with +// the given key. It overwrites any previous value with the +// same key. +func SetVar(ctx context.Context, key string, value interface{}) { + varMap, ok := ctx.Value(VarsCtxKey).(map[string]interface{}) + if !ok { + return + } + varMap[key] = value +} + // Interface guards var ( _ MiddlewareHandler = (*VarsMiddleware)(nil) |