@marcel91200 / caddy Mirror · read-only / commits / a5ebec00
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

http: Change routes to sequential matcher evaluation (#2967)

Previously, all matchers in a route would be evaluated before any handlers were executed, and a composite route of the matching routes would be created. This made rewrites especially tricky, since the only way to defer later matchers' evaluation was to wrap them in a subroute, or to invoke a "rehandle" which often caused bugs. Instead, this new sequential design evaluates each route's matchers then its handlers in lock-step; matcher-handlers-matcher-handlers... If the first matching route consists of a rewrite, then the second route will be evaluated against the rewritten request, rather than the original one, and so on. This should do away with any need for rehandling. I've also taken this opportunity to avoid adding new values to the request context in the handler chain, as this creates a copy of the Request struct, which may possibly lead to bugs like it has in the past (see PR #1542, PR #1481, and maybe issue #2463). We now add all the expected context values in the top-level handler at the server, then any new values can be added to the variable table via the VarsCtxKey context key, or just the GetVar/SetVar functions. In particular, we are using this facility to convey dial information in the reverse proxy. Had to be careful in one place as the middleware compilation logic has changed, and moved a bit. We no longer compile a middleware chain per- request; instead, we can compile it at provision-time, and defer only the evaluation of matchers to request-time, which should slightly improve performance. Doing this, however, we take advantage of multiple function closures, and we also changed the use of HandlerFunc (function pointer) to Handler (interface)... this led to a situation where, if we aren't careful, allows one request routed a certain way to permanently change the "next" handler for all/most other requests! We avoid this by making a copy of the interface value (which is a lightweight pointer copy) and using exclusively that within our wrapped handlers. This way, the original stack frame is preserved in a "read-only" fashion. The comments in the code describe this phenomenon. This may very well be a breaking change for some configurations, however I do not expect it to impact many people. I will make it clear in the release notes that this change has occurred.

Matt Holt committed Jan 9, 2020 at 10:00 UTC a5ebec00419f77bb408f67c360b4a09c5884109d
16 files changed +184 -199
modules/caddyhttp/caddyhttp.go
+6 -17
@@ -171,6 +171,9 @@ func (app *App) Provision(ctx caddy.Context) error {
171 if err != nil {
172 return fmt.Errorf("server %s: setting up server routes: %v", srvName, err)
173 }
174 + // pre-compile the handler chain, and be sure to wrap it in our
175 + // route handler so that important security checks are done, etc.
176 + srv.primaryHandlerChain = srv.wrapPrimaryRoute(srv.Routes.Compile())
177 }
178
179 if srv.Errors != nil {
@@ -178,10 +181,7 @@ func (app *App) Provision(ctx caddy.Context) error {
181 if err != nil {
182 return fmt.Errorf("server %s: setting up server error handling routes: %v", srvName, err)
183 }
181 - }
182 -
183 - if srv.MaxRehandles == nil {
184 - srv.MaxRehandles = &DefaultMaxRehandles
184 + srv.errorHandlerChain = srv.Errors.Routes.Compile()
185 }
186 }
187
@@ -210,13 +210,6 @@ func (app *App) Validate() error {
210 }
211 }
212
213 - // each server's max rehandle value must be valid
214 - for srvName, srv := range app.Servers {
215 - if srv.MaxRehandles != nil && *srv.MaxRehandles < 0 {
216 - return fmt.Errorf("%s: invalid max_rehandles value: %d", srvName, *srv.MaxRehandles)
217 - }
218 - }
219 -
213 return nil
214 }
215
@@ -608,7 +601,7 @@ func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
601
602 // Middleware chains one Handler to the next by being passed
603 // the next Handler in the chain.
611 -type Middleware func(HandlerFunc) HandlerFunc
604 +type Middleware func(Handler) Handler
605
606 // MiddlewareHandler is like Handler except it takes as a third
607 // argument the next handler in the chain. The next handler will
@@ -624,7 +617,7 @@ type MiddlewareHandler interface {
617 }
618
619 // emptyHandler is used as a no-op handler.
627 -var emptyHandler HandlerFunc = func(http.ResponseWriter, *http.Request) error { return nil }
620 +var emptyHandler Handler = HandlerFunc(func(http.ResponseWriter, *http.Request) error { return nil })
621
622 // WeakString is a type that unmarshals any JSON value
623 // as a string literal, with the following exceptions:
@@ -734,10 +727,6 @@ const (
727 DefaultHTTPSPort = 443
728 )
729
737 -// DefaultMaxRehandles is the maximum number of rehandles to
738 -// allow, if not specified explicitly.
739 -var DefaultMaxRehandles = 3
740 -
730 // Interface guards
731 var (
732 _ caddy.App = (*App)(nil)
modules/caddyhttp/errors.go
-6
@@ -106,12 +106,6 @@ func trace() string {
106 return ""
107 }
108
109 -// ErrRehandle is a special error value that Handlers should return
110 -// from their ServeHTTP() method if the request is to be re-processed.
111 -// This error value is a sentinel value that should not be wrapped or
112 -// modified.
113 -var ErrRehandle = fmt.Errorf("rehandling request")
114 -
109 // ErrorCtxKey is the context key to use when storing
110 // an error (for use with context.Context).
111 const ErrorCtxKey = caddy.CtxKey("handler_chain_error")
modules/caddyhttp/fileserver/caddyfile.go
+1 -2
@@ -127,8 +127,7 @@ func parseTryFiles(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error)
127 // to the end of the query string.
128 makeRoute := func(try []string, writeURIAppend string) []httpcaddyfile.ConfigValue {
129 handler := rewrite.Rewrite{
130 - Rehandle: true,
131 - URI: "{http.matchers.file.relative}{http.request.uri.query_string}" + writeURIAppend,
130 + URI: "{http.matchers.file.relative}{http.request.uri.query_string}" + writeURIAppend,
131 }
132 matcherSet := caddy.ModuleMap{
133 "file": h.JSON(MatchFile{
modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go
+1 -2
@@ -149,8 +149,7 @@ func parsePHPFastCGI(h httpcaddyfile.Helper) ([]httpcaddyfile.ConfigValue, error
149 }),
150 }
151 rewriteHandler := rewrite.Rewrite{
152 - URI: "{http.matchers.file.relative}{http.request.uri.query_string}",
153 - Rehandle: true,
152 + URI: "{http.matchers.file.relative}{http.request.uri.query_string}",
153 }
154 rewriteRoute := caddyhttp.Route{
155 MatcherSetsRaw: []caddy.ModuleMap{rewriteMatcherSet},
modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
+2 -4
@@ -102,11 +102,9 @@ func (t Transport) RoundTrip(r *http.Request) (*http.Response, error) {
102 defer cancel()
103 }
104
105 - // extract dial information from request (this
106 - // should embedded by the reverse proxy)
105 + // extract dial information from request (should have been embedded by the reverse proxy)
106 network, address := "tcp", r.URL.Host
108 - if dialInfoVal := ctx.Value(reverseproxy.DialInfoCtxKey); dialInfoVal != nil {
109 - dialInfo := dialInfoVal.(reverseproxy.DialInfo)
107 + if dialInfo, ok := reverseproxy.GetDialInfo(ctx); ok {
108 network = dialInfo.Network
109 address = dialInfo.Address
110 }
modules/caddyhttp/reverseproxy/healthchecks.go
+3 -1
@@ -212,7 +212,9 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, host H
212 // attach dialing information to this request
213 ctx := context.Background()
214 ctx = context.WithValue(ctx, caddy.ReplacerCtxKey, caddy.NewReplacer())
215 - ctx = context.WithValue(ctx, DialInfoCtxKey, dialInfo)
215 + ctx = context.WithValue(ctx, caddyhttp.VarsCtxKey, map[string]interface{}{
216 + dialInfoVarKey: dialInfo,
217 + })
218 req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
219 if err != nil {
220 return fmt.Errorf("making request: %v", err)
modules/caddyhttp/reverseproxy/hosts.go
+12 -3
@@ -15,11 +15,13 @@
15 package reverseproxy
16
17 import (
18 + "context"
19 "fmt"
20 "strconv"
21 "sync/atomic"
22
23 "github.com/caddyserver/caddy/v2"
24 + "github.com/caddyserver/caddy/v2/modules/caddyhttp"
25 )
26
27 // Host represents a remote host which can be proxied to.
@@ -223,12 +225,19 @@ func fillDialInfo(upstream *Upstream, repl *caddy.Replacer) (DialInfo, error) {
225 }, nil
226 }
227
226 -// DialInfoCtxKey is used to store a DialInfo
227 -// in a context.Context.
228 -const DialInfoCtxKey = caddy.CtxKey("dial_info")
228 +// GetDialInfo gets the upstream dialing info out of the context,
229 +// and returns true if there was a valid value; false otherwise.
230 +func GetDialInfo(ctx context.Context) (DialInfo, bool) {
231 + dialInfo, ok := caddyhttp.GetVar(ctx, dialInfoVarKey).(DialInfo)
232 + return dialInfo, ok
233 +}
234
235 // hosts is the global repository for hosts that are
236 // currently in use by active configuration(s). This
237 // allows the state of remote hosts to be preserved
238 // through config reloads.
239 var hosts = caddy.NewUsagePool()
240 +
241 +// dialInfoVarKey is the key used for the variable that holds
242 +// the dial info for the upstream connection.
243 +const dialInfoVarKey = "reverse_proxy.dial_info"
modules/caddyhttp/reverseproxy/httptransport.go
+1 -2
@@ -92,8 +92,7 @@ func (h *HTTPTransport) newTransport() (*http.Transport, error) {
92 rt := &http.Transport{
93 DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
94 // the proper dialing information should be embedded into the request's context
95 - if dialInfoVal := ctx.Value(DialInfoCtxKey); dialInfoVal != nil {
96 - dialInfo := dialInfoVal.(DialInfo)
95 + if dialInfo, ok := GetDialInfo(ctx); ok {
96 network = dialInfo.Network
97 address = dialInfo.Address
98 }
modules/caddyhttp/reverseproxy/reverseproxy.go
+1 -2
@@ -320,8 +320,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
320 // attach to the request information about how to dial the upstream;
321 // this is necessary because the information cannot be sufficiently
322 // or satisfactorily represented in a URL
323 - ctx := context.WithValue(r.Context(), DialInfoCtxKey, dialInfo)
324 - r = r.WithContext(ctx)
323 + caddyhttp.SetVar(r.Context(), dialInfoVarKey, dialInfo)
324
325 // set placeholders with information about this upstream
326 repl.Set("http.reverse_proxy.upstream.address", dialInfo.String())
modules/caddyhttp/rewrite/caddyfile.go
+2 -3
@@ -44,7 +44,6 @@ func parseCaddyfileRewrite(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler,
44 return nil, h.ArgErr()
45 }
46 }
47 - rewr.Rehandle = true
47 return rewr, nil
48 }
49
@@ -52,7 +51,7 @@ func parseCaddyfileRewrite(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler,
51 //
52 // strip_prefix [<matcher>] <prefix>
53 //
55 -// The request path will be stripped its prefix if it matches <prefix>.
54 +// The request path will be stripped the given prefix.
55 func parseCaddyfileStripPrefix(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
56 var rewr Rewrite
57 for h.Next() {
@@ -71,7 +70,7 @@ func parseCaddyfileStripPrefix(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHand
70 //
71 // strip_suffix [<matcher>] <suffix>
72 //
74 -// The request path will be stripped its suffix if it matches <suffix>.
73 +// The request path will be stripped the given suffix.
74 func parseCaddyfileStripSuffix(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
75 var rewr Rewrite
76 for h.Next() {
modules/caddyhttp/rewrite/rewrite.go
-19
@@ -15,7 +15,6 @@
15 package rewrite
16
17 import (
18 - "fmt"
18 "net/http"
19 "net/url"
20 "strconv"
@@ -32,9 +31,6 @@ func init() {
31
32 // Rewrite is a middleware which can rewrite HTTP requests.
33 //
35 -// The Rehandle and HTTPRedirect properties are mutually exclusive
36 -// (you cannot both rehandle and issue a redirect).
37 -//
34 // These rewrite properties are applied to a request in this order:
35 // Method, URI, StripPathPrefix, StripPathSuffix, URISubstring.
36 //
@@ -61,10 +57,6 @@ type Rewrite struct {
57 // given status code.
58 HTTPRedirect caddyhttp.WeakString `json:"http_redirect,omitempty"`
59
64 - // If true, the request will sent for rehandling after rewriting
65 - // only if anything about the request was changed.
66 - Rehandle bool `json:"rehandle,omitempty"`
67 -
60 logger *zap.Logger
61 }
62
@@ -82,14 +74,6 @@ func (rewr *Rewrite) Provision(ctx caddy.Context) error {
74 return nil
75 }
76
85 -// Validate ensures rewr's configuration is valid.
86 -func (rewr Rewrite) Validate() error {
87 - if rewr.HTTPRedirect != "" && rewr.Rehandle {
88 - return fmt.Errorf("cannot be configured to both redirect externally and rehandle internally")
89 - }
90 - return nil
91 -}
92 -
77 func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
78 repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
79
@@ -104,9 +88,6 @@ func (rewr Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddy
88 zap.String("method", r.Method),
89 zap.String("uri", r.RequestURI),
90 )
107 - if rewr.Rehandle {
108 - return caddyhttp.ErrRehandle
109 - }
91 if rewr.HTTPRedirect != "" {
92 statusCode, err := strconv.Atoi(repl.ReplaceAll(rewr.HTTPRedirect.String(), ""))
93 if err != nil {
modules/caddyhttp/routes.go
+98 -70
@@ -28,13 +28,15 @@ import (
28 // in a highly flexible and performant manner.
29 type Route struct {
30 // Group is an optional name for a group to which this
31 - // route belongs. If a route belongs to a group, only
32 - // the first matching route in the group will be used.
31 + // route belongs. Grouping a route makes it mutually
32 + // exclusive with others in its group; if a route belongs
33 + // to a group, only the first matching route in that group
34 + // will be executed.
35 Group string `json:"group,omitempty"`
36
37 // The matcher sets which will be used to qualify this
36 - // route for a request. Essentially the "if" statement
37 - // of this route. Each matcher set is OR'ed, but matchers
38 + // route for a request (essentially the "if" statement
39 + // of this route). Each matcher set is OR'ed, but matchers
40 // within a set are AND'ed together.
41 MatcherSetsRaw RawMatcherSets `json:"match,omitempty" caddy:"namespace=http.matchers"`
42
@@ -87,12 +89,14 @@ type Route struct {
89 // If you think of routes in this way, it will be easy and even fun to solve the puzzle of writing correct routes.
90 HandlersRaw []json.RawMessage `json:"handle,omitempty" caddy:"namespace=http.handlers inline_key=handler"`
91
90 - // If true, no more routes will be executed after this one, even if they matched.
92 + // If true, no more routes will be executed after this one.
93 Terminal bool `json:"terminal,omitempty"`
94
95 // decoded values
96 MatcherSets MatcherSets `json:"-"`
97 Handlers []MiddlewareHandler `json:"-"`
98 +
99 + middleware []Middleware
100 }
101
102 // Empty returns true if the route has all zero/default values.
@@ -111,9 +115,9 @@ type RouteList []Route
115
116 // Provision sets up all the routes by loading the modules.
117 func (routes RouteList) Provision(ctx caddy.Context) error {
114 - for i, route := range routes {
118 + for i := range routes {
119 // matchers
116 - matchersIface, err := ctx.LoadModule(&route, "MatcherSetsRaw")
120 + matchersIface, err := ctx.LoadModule(&routes[i], "MatcherSetsRaw")
121 if err != nil {
122 return fmt.Errorf("loading matchers in route %d: %v", i, err)
123 }
@@ -123,93 +127,115 @@ func (routes RouteList) Provision(ctx caddy.Context) error {
127 }
128
129 // handlers
126 - handlersIface, err := ctx.LoadModule(&route, "HandlersRaw")
130 + handlersIface, err := ctx.LoadModule(&routes[i], "HandlersRaw")
131 if err != nil {
132 return fmt.Errorf("loading handler modules in route %d: %v", i, err)
133 }
134 for _, handler := range handlersIface.([]interface{}) {
135 routes[i].Handlers = append(routes[i].Handlers, handler.(MiddlewareHandler))
136 }
137 +
138 + // pre-compile the middleware handler chain
139 + for _, midhandler := range routes[i].Handlers {
140 + routes[i].middleware = append(routes[i].middleware, wrapMiddleware(midhandler))
141 + }
142 }
143 +
144 return nil
145 }
146
137 -// BuildCompositeRoute creates a chain of handlers by
138 -// applying all of the matching routes.
139 -func (routes RouteList) BuildCompositeRoute(req *http.Request) Handler {
140 - if len(routes) == 0 {
141 - return emptyHandler
147 +// Compile prepares a middleware chain from the route list.
148 +// This should only be done once: after all the routes have
149 +// been provisioned, and before serving requests.
150 +func (routes RouteList) Compile() Handler {
151 + var mid []Middleware
152 + for _, route := range routes {
153 + mid = append(mid, wrapRoute(route))
154 + }
155 + stack := emptyHandler
156 + for i := len(mid) - 1; i >= 0; i-- {
157 + stack = mid[i](stack)
158 }
159 + return stack
160 +}
161
144 - var mid []Middleware
145 - groups := make(map[string]struct{})
162 +// wrapRoute wraps route with a middleware and handler so that it can
163 +// be chained in and defer evaluation of its matchers to request-time.
164 +// Like wrapMiddleware, it is vital that this wrapping takes place in
165 +// its own stack frame so as to not overwrite the reference to the
166 +// intended route by looping and changing the reference each time.
167 +func wrapRoute(route Route) Middleware {
168 + return func(next Handler) Handler {
169 + return HandlerFunc(func(rw http.ResponseWriter, req *http.Request) error {
170 + // copy the next handler (it's an interface, so it's just
171 + // a very lightweight copy of a pointer); this is important
172 + // because this is a closure to the func below, which
173 + // re-assigns the value as it compiles the middleware stack;
174 + // if we don't make this copy, we'd affect the underlying
175 + // pointer for all future request (yikes); we could
176 + // alternatively solve this by moving the func below out of
177 + // this closure and into a standalone package-level func,
178 + // but I just thought this made more sense
179 + nextCopy := next
180
147 - for _, route := range routes {
148 - // route must match at least one of the matcher sets
149 - if !route.MatcherSets.AnyMatch(req) {
150 - continue
151 - }
181 + // route must match at least one of the matcher sets
182 + if !route.MatcherSets.AnyMatch(req) {
183 + return nextCopy.ServeHTTP(rw, req)
184 + }
185
153 - // if route is part of a group, ensure only the
154 - // first matching route in the group is applied
155 - if route.Group != "" {
156 - _, ok := groups[route.Group]
157 - if ok {
158 - // this group has already been satisfied
159 - // by a matching route
160 - continue
186 + // if route is part of a group, ensure only the
187 + // first matching route in the group is applied
188 + if route.Group != "" {
189 + groups := req.Context().Value(routeGroupCtxKey).(map[string]struct{})
190 +
191 + if _, ok := groups[route.Group]; ok {
192 + // this group has already been
193 + // satisfied by a matching route
194 + return nextCopy.ServeHTTP(rw, req)
195 + }
196 +
197 + // this matching route satisfies the group
198 + groups[route.Group] = struct{}{}
199 }
162 - // this matching route satisfies the group
163 - groups[route.Group] = struct{}{}
164 - }
200
166 - // apply the rest of the route
167 - for _, mh := range route.Handlers {
168 - // we have to be sure to wrap mh outside
169 - // of our current stack frame so that the
170 - // reference to this mh isn't overwritten
171 - // on the next iteration, leaving the last
172 - // middleware in the chain as the ONLY
173 - // middleware in the chain!
174 - mid = append(mid, wrapMiddleware(mh))
175 - }
201 + // make terminal routes terminate
202 + if route.Terminal {
203 + nextCopy = emptyHandler
204 + }
205
177 - // if this route is supposed to be last, don't
178 - // compile any more into the chain
179 - if route.Terminal {
180 - break
181 - }
182 - }
206 + // compile this route's handler stack
207 + for i := len(route.middleware) - 1; i >= 0; i-- {
208 + nextCopy = route.middleware[i](nextCopy)
209 + }
210
184 - // build the middleware chain, with the responder at the end
185 - stack := emptyHandler
186 - for i := len(mid) - 1; i >= 0; i-- {
187 - stack = mid[i](stack)
211 + return nextCopy.ServeHTTP(rw, req)
212 + })
213 }
189 -
190 - return stack
214 }
215
193 -// wrapMiddleware wraps m such that it can be correctly
194 -// appended to a list of middleware. We can't do this
195 -// directly in a loop because it relies on a reference
196 -// to mh not changing until the execution of its handler,
197 -// which is deferred by multiple func closures. In other
198 -// words, we need to pull this particular MiddlewareHandler
216 +// wrapMiddleware wraps mh such that it can be correctly
217 +// appended to a list of middleware in preparation for
218 +// compiling into a handler chain. We can't do this inline
219 +// inside a loop, because it relies on a reference to mh
220 +// not changing until the execution of its handler (which
221 +// is deferred by multiple func closures). In other words,
222 +// we need to pull this particular MiddlewareHandler
223 // pointer into its own stack frame to preserve it so it
224 // won't be overwritten in future loop iterations.
225 func wrapMiddleware(mh MiddlewareHandler) Middleware {
202 - return func(next HandlerFunc) HandlerFunc {
203 - return func(w http.ResponseWriter, r *http.Request) error {
204 - // TODO: We could wait to evaluate matchers here, just eval
205 - // the next matcher and choose the next route...
206 -
207 - // TODO: This is where request tracing could be implemented; also
208 - // see below to trace the responder as well
209 - // TODO: Trace a diff of the request, would be cool too! see what changed since the last middleware (host, headers, URI...)
226 + return func(next Handler) Handler {
227 + // copy the next handler (it's an interface, so it's
228 + // just a very lightweight copy of a pointer); this
229 + // is a safeguard against the handler changing the
230 + // value, which could affect future requests (yikes)
231 + nextCopy := next
232 +
233 + return HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
234 + // TODO: This is where request tracing could be implemented
235 + // TODO: Trace a diff of the request, would be cool too... see what changed since the last middleware (host, headers, URI...)
236 // TODO: see what the std lib gives us in terms of stack tracing too
211 - return mh.ServeHTTP(w, r, next)
212 - }
237 + return mh.ServeHTTP(w, r, nextCopy)
238 + })
239 }
240 }
241
@@ -219,7 +245,7 @@ func wrapMiddleware(mh MiddlewareHandler) Middleware {
245 type MatcherSet []RequestMatcher
246
247 // Match returns true if the request matches all
222 -// matchers in mset.
248 +// matchers in mset or if there are no matchers.
249 func (mset MatcherSet) Match(r *http.Request) bool {
250 for _, m := range mset {
251 if !m.Match(r) {
@@ -265,3 +291,5 @@ func (ms *MatcherSets) FromInterface(matcherSets interface{}) error {
291 }
292 return nil
293 }
294 +
295 +var routeGroupCtxKey = caddy.CtxKey("route_group")
modules/caddyhttp/server.go
+23 -55
@@ -61,10 +61,12 @@ type Server struct {
61 MaxHeaderBytes int `json:"max_header_bytes,omitempty"`
62
63 // Routes describes how this server will handle requests.
64 - // When a request comes in, each route's matchers will
65 - // be evaluated against the request, and matching routes
66 - // will be compiled into a middleware chain in the order
67 - // in which they appear in the list.
64 + // Routes are executed sequentially. First a route's matchers
65 + // are evaluated, then its grouping. If it matches and has
66 + // not been mutually-excluded by its grouping, then its
67 + // handlers are executed sequentially. The sequence of invoked
68 + // handlers comprises a compiled middleware chain that flows
69 + // from each matching route and its handlers to the next.
70 Routes RouteList `json:"routes,omitempty"`
71
72 // Errors is how this server will handle errors returned from any
@@ -86,11 +88,6 @@ type Server struct {
88 // only on the HTTPS port.
89 AutoHTTPS *AutoHTTPSConfig `json:"automatic_https,omitempty"`
90
89 - // MaxRehandles is the maximum number of times to allow a
90 - // request to be rehandled, to prevent accidental infinite
91 - // loops. Default: 1.
92 - MaxRehandles *int `json:"max_rehandles,omitempty"`
93 -
91 // If true, will require that a request's Host header match
92 // the value of the ServerName sent by the client's TLS
93 // ClientHello; often a necessary safeguard when using TLS
@@ -105,6 +102,9 @@ type Server struct {
102 // This field is not subject to compatibility promises.
103 ExperimentalHTTP3 bool `json:"experimental_http3,omitempty"`
104
105 + primaryHandlerChain Handler
106 + errorHandlerChain Handler
107 +
108 tlsApp *caddytls.TLS
109 logger *zap.Logger
110 accessLogger *zap.Logger
@@ -129,6 +129,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
129 ctx := context.WithValue(r.Context(), caddy.ReplacerCtxKey, repl)
130 ctx = context.WithValue(ctx, ServerCtxKey, s)
131 ctx = context.WithValue(ctx, VarsCtxKey, make(map[string]interface{}))
132 + ctx = context.WithValue(ctx, routeGroupCtxKey, make(map[string]struct{}))
133 var url2 url.URL // avoid letting this escape to the heap
134 ctx = context.WithValue(ctx, OriginalRequestCtxKey, originalRequest(r, &url2))
135 r = r.WithContext(ctx)
@@ -137,22 +138,22 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
138 // anymore, finish setting up the replacer
139 addHTTPVarsToReplacer(repl, r, w)
140
140 - loggableReq := LoggableHTTPRequest{r}
141 + // encode the request for logging purposes before
142 + // it enters any handler chain; this is necessary
143 + // to capture the original request in case it gets
144 + // modified during handling
145 + loggableReq := zap.Object("request", LoggableHTTPRequest{r})
146 errLog := s.errorLogger.With(
142 - // encode the request for logging purposes before
143 - // it enters any handler chain; this is necessary
144 - // to capture the original request in case it gets
145 - // modified during handling
146 - zap.Object("request", loggableReq),
147 + loggableReq,
148 )
149
150 if s.accessLogger != nil {
151 wrec := NewResponseRecorder(w, nil, nil)
152 w = wrec
152 - accLog := s.accessLogger.With(
153 - // capture the original version of the request
154 - zap.Object("request", loggableReq),
155 - )
153 +
154 + // capture the original version of the request
155 + accLog := s.accessLogger.With(loggableReq)
156 +
157 start := time.Now()
158 defer func() {
159 latency := time.Since(start)
@@ -187,8 +188,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
188 return
189 }
190
190 - // build and execute the primary handler chain
191 - err := s.executeCompositeRoute(w, r, s.Routes)
191 + // execute the primary handler chain
192 + err := s.primaryHandlerChain.ServeHTTP(w, r)
193 if err != nil {
194 // prepare the error log
195 logger := errLog
@@ -204,7 +205,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
205
206 if s.Errors != nil && len(s.Errors.Routes) > 0 {
207 // execute user-defined error handling route
207 - err2 := s.executeCompositeRoute(w, r, s.Errors.Routes)
208 + err2 := s.errorHandlerChain.ServeHTTP(w, r)
209 if err2 == nil {
210 // user's error route handled the error response
211 // successfully, so now just log the error
@@ -229,39 +230,6 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
230 }
231 }
232
232 -// executeCompositeRoute compiles a composite route from routeList and executes
233 -// it using w and r. This function handles the sentinel ErrRehandle error value,
234 -// which reprocesses requests through the stack again. Any error value returned
235 -// from this function would be an actual error that needs to be handled.
236 -func (s *Server) executeCompositeRoute(w http.ResponseWriter, r *http.Request, routeList RouteList) error {
237 - maxRehandles := 0
238 - if s.MaxRehandles != nil {
239 - maxRehandles = *s.MaxRehandles
240 - }
241 - var err error
242 - for i := -1; i <= maxRehandles; i++ {
243 - // we started the counter at -1 because we
244 - // always want to run this at least once
245 -
246 - // the purpose of rehandling is often to give
247 - // matchers a chance to re-evaluate on the
248 - // changed version of the request, so compile
249 - // the handler stack anew in each iteration
250 - stack := routeList.BuildCompositeRoute(r)
251 - stack = s.wrapPrimaryRoute(stack)
252 -
253 - // only loop if rehandling is required
254 - err = stack.ServeHTTP(w, r)
255 - if err != ErrRehandle {
256 - break
257 - }
258 - if i >= maxRehandles-1 {
259 - return fmt.Errorf("too many rehandles")
260 - }
261 - }
262 - return err
263 -}
264 -
233 // wrapPrimaryRoute wraps stack (a compiled middleware handler chain)
234 // in s.enforcementHandler which performs crucial security checks, etc.
235 func (s *Server) wrapPrimaryRoute(stack Handler) Handler {
modules/caddyhttp/starlarkmw/starlarkmw.go
+5 -5
@@ -59,7 +59,7 @@ func (s *StarlarkMW) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
59 }
60
61 // dynamically build middleware chain for each request
62 - stack := caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
62 + var stack caddyhttp.Handler = caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
63 wr, err := convert.ToValue(w)
64 if err != nil {
65 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 {
83 // TODO :- make middlewareResponseWriter exported and wrap w with that
84 var mid []caddyhttp.Middleware
85 for _, m := range s.execute.Modules {
86 - mid = append(mid, func(next caddyhttp.HandlerFunc) caddyhttp.HandlerFunc {
87 - return func(w http.ResponseWriter, r *http.Request) error {
86 + mid = append(mid, func(next caddyhttp.Handler) caddyhttp.Handler {
87 + return caddyhttp.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
88 return m.Instance.ServeHTTP(w, r, next)
89 - }
89 + })
90 })
91 }
92
@@ -96,7 +96,7 @@ func (s *StarlarkMW) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
96
97 s.execute.Modules = nil
98
99 - return stack(w, r)
99 + return stack.ServeHTTP(w, r)
100 }
101
102 // Cleanup cleans up any modules loaded during the creation of a starlark route.
modules/caddyhttp/subroute.go
+7 -8
@@ -27,13 +27,12 @@ func init() {
27
28 // Subroute implements a handler that compiles and executes routes.
29 // This is useful for a batch of routes that all inherit the same
30 -// matchers, or for routes with matchers that must be have deferred
31 -// evaluation (e.g. if they depend on placeholders created by other
32 -// matchers that need to be evaluated first).
30 +// matchers, or for multiple routes that should be treated as a
31 +// single route.
32 //
34 -// You can also use subroutes to handle errors from specific handlers.
35 -// First the primary Routes will be executed, and if they return an
36 -// error, the Errors routes will be executed; in that case, an error
33 +// You can also use subroutes to handle errors from its handlers.
34 +// First the primary routes will be executed, and if they return an
35 +// error, the errors routes will be executed; in that case, an error
36 // is only returned to the entry point at the server if there is an
37 // additional error returned from the errors routes.
38 type Subroute struct {
@@ -71,11 +70,11 @@ func (sr *Subroute) Provision(ctx caddy.Context) error {
70 }
71
72 func (sr *Subroute) ServeHTTP(w http.ResponseWriter, r *http.Request, _ Handler) error {
74 - subroute := sr.Routes.BuildCompositeRoute(r)
73 + subroute := sr.Routes.Compile()
74 err := subroute.ServeHTTP(w, r)
75 if err != nil && sr.Errors != nil {
76 r = sr.Errors.WithError(r, err)
78 - errRoute := sr.Errors.Routes.BuildCompositeRoute(r)
77 + errRoute := sr.Errors.Routes.Compile()
78 return errRoute.ServeHTTP(w, r)
79 }
80 return err
modules/caddyhttp/vars.go
+22
@@ -15,6 +15,7 @@
15 package caddyhttp
16
17 import (
18 + "context"
19 "net/http"
20
21 "github.com/caddyserver/caddy/v2"
@@ -75,6 +76,27 @@ func (m VarsMatcher) Match(r *http.Request) bool {
76 return true
77 }
78
79 +// GetVar gets a value out of the context's variable table by key.
80 +// If the key does not exist, the return value will be nil.
81 +func GetVar(ctx context.Context, key string) interface{} {
82 + varMap, ok := ctx.Value(VarsCtxKey).(map[string]interface{})
83 + if !ok {
84 + return nil
85 + }
86 + return varMap[key]
87 +}
88 +
89 +// SetVar sets a value in the context's variable table with
90 +// the given key. It overwrites any previous value with the
91 +// same key.
92 +func SetVar(ctx context.Context, key string, value interface{}) {
93 + varMap, ok := ctx.Value(VarsCtxKey).(map[string]interface{})
94 + if !ok {
95 + return
96 + }
97 + varMap[key] = value
98 +}
99 +
100 // Interface guards
101 var (
102 _ MiddlewareHandler = (*VarsMiddleware)(nil)