package caddyhttp import ( "encoding/json" "fmt" "net/http" "github.com/caddyserver/caddy" ) // ServerRoute represents a set of matching rules, // middlewares, and a responder for handling HTTP // requests. type ServerRoute struct { Group string `json:"group,omitempty"` MatcherSets []map[string]json.RawMessage `json:"match,omitempty"` Apply []json.RawMessage `json:"apply,omitempty"` Respond json.RawMessage `json:"respond,omitempty"` Terminal bool `json:"terminal,omitempty"` // decoded values matcherSets []MatcherSet middleware []MiddlewareHandler responder Handler } func (sr ServerRoute) anyMatcherSetMatches(r *http.Request) bool { for _, ms := range sr.matcherSets { if ms.Match(r) { return true } } // if no matchers, always match return len(sr.matcherSets) == 0 } // MatcherSet is a set of matchers which // must all match in order for the request // to be matched successfully. type MatcherSet []RequestMatcher // Match returns true if the request matches all // matchers in mset. func (mset MatcherSet) Match(r *http.Request) bool { for _, m := range mset { if !m.Match(r) { return false } } return true } // RouteList is a list of server routes that can // create a middleware chain. type RouteList []ServerRoute // Provision sets up all the routes by loading the modules. func (routes RouteList) Provision(ctx caddy.Context) error { for i, route := range routes { // matchers for _, matcherSet := range route.MatcherSets { var matchers MatcherSet for modName, rawMsg := range matcherSet { val, err := ctx.LoadModule("http.matchers."+modName, rawMsg) if err != nil { return fmt.Errorf("loading matcher module '%s': %v", modName, err) } matchers = append(matchers, val.(RequestMatcher)) } routes[i].matcherSets = append(routes[i].matcherSets, matchers) } routes[i].MatcherSets = nil // allow GC to deallocate - TODO: Does this help? // middleware for j, rawMsg := range route.Apply { mid, err := ctx.LoadModuleInline("middleware", "http.middleware", rawMsg) if err != nil { return fmt.Errorf("loading middleware module in position %d: %v", j, err) } routes[i].middleware = append(routes[i].middleware, mid.(MiddlewareHandler)) } routes[i].Apply = nil // allow GC to deallocate - TODO: Does this help? // responder if route.Respond != nil { resp, err := ctx.LoadModuleInline("responder", "http.responders", route.Respond) if err != nil { return fmt.Errorf("loading responder module: %v", err) } routes[i].responder = resp.(Handler) } routes[i].Respond = nil // allow GC to deallocate - TODO: Does this help? } return nil } // BuildCompositeRoute creates a chain of handlers by applying all the matching // routes. The returned ResponseWriter should be used instead of rw. func (routes RouteList) BuildCompositeRoute(rw http.ResponseWriter, req *http.Request) (Handler, http.ResponseWriter) { mrw := &middlewareResponseWriter{ResponseWriterWrapper: &ResponseWriterWrapper{rw}} if len(routes) == 0 { return emptyHandler, mrw } var mid []Middleware var responder Handler groups := make(map[string]struct{}) for _, route := range routes { // route must match at least one of the matcher sets if !route.anyMatcherSetMatches(req) { continue } // 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 } // this matching route satisfies the group groups[route.Group] = struct{}{} } // apply the rest of the route for _, m := range route.middleware { // we have to be sure to wrap m outside // of our current scope so that the // reference to this m isn't overwritten // on the next iteration, leaving only // the last middleware in the chain as // the ONLY middleware in the chain! mid = append(mid, wrapMiddleware(m)) } if responder == nil { responder = route.responder } if route.Terminal { break } } // build the middleware stack, with the responder at the end stack := HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { if responder == nil { return nil } mrw.allowWrites = true return responder.ServeHTTP(w, r) }) for i := len(mid) - 1; i >= 0; i-- { stack = mid[i](stack) } return stack, mrw } // wrapMiddleware wraps m such that it can be correctly // appended to a list of middleware. This separate closure // is necessary so that only the last middleware in a loop // does not become the only middleware of the stack, // repeatedly executed (i.e. it is necessary to keep a // reference to this m outside of the scope of a loop)! func wrapMiddleware(m MiddlewareHandler) Middleware { return func(next HandlerFunc) HandlerFunc { return func(w http.ResponseWriter, r *http.Request) error { // 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...) // TODO: see what the std lib gives us in terms of stack tracing too return m.ServeHTTP(w, r, next) } } } type middlewareResponseWriter struct { *ResponseWriterWrapper allowWrites bool } func (mrw middlewareResponseWriter) WriteHeader(statusCode int) { if !mrw.allowWrites { // technically, this is not true: middleware can write headers, // but only after the responder handler has returned; either the // responder did nothing with the response (sad face), or the // middleware wrapped the response and deferred the write panic("WriteHeader: middleware cannot write response headers") } mrw.ResponseWriterWrapper.WriteHeader(statusCode) } func (mrw middlewareResponseWriter) Write(b []byte) (int, error) { if !mrw.allowWrites { panic("Write: middleware cannot write to the response before responder") } return mrw.ResponseWriterWrapper.Write(b) } // Interface guard var _ HTTPInterfaces = (*middlewareResponseWriter)(nil)