summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/caddyhttp/caddyhttp.go23
-rw-r--r--modules/caddyhttp/errors.go6
-rw-r--r--modules/caddyhttp/fileserver/caddyfile.go3
-rw-r--r--modules/caddyhttp/reverseproxy/fastcgi/caddyfile.go3
-rw-r--r--modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go6
-rw-r--r--modules/caddyhttp/reverseproxy/healthchecks.go4
-rw-r--r--modules/caddyhttp/reverseproxy/hosts.go15
-rw-r--r--modules/caddyhttp/reverseproxy/httptransport.go3
-rw-r--r--modules/caddyhttp/reverseproxy/reverseproxy.go3
-rw-r--r--modules/caddyhttp/rewrite/caddyfile.go5
-rw-r--r--modules/caddyhttp/rewrite/rewrite.go19
-rw-r--r--modules/caddyhttp/routes.go168
-rw-r--r--modules/caddyhttp/server.go78
-rw-r--r--modules/caddyhttp/starlarkmw/starlarkmw.go10
-rw-r--r--modules/caddyhttp/subroute.go15
-rw-r--r--modules/caddyhttp/vars.go22
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)