diff options
Diffstat (limited to 'modules/caddyhttp/routes.go')
-rw-r--r-- | modules/caddyhttp/routes.go | 143 |
1 files changed, 99 insertions, 44 deletions
diff --git a/modules/caddyhttp/routes.go b/modules/caddyhttp/routes.go index 550b14e..0dce990 100644 --- a/modules/caddyhttp/routes.go +++ b/modules/caddyhttp/routes.go @@ -22,14 +22,73 @@ import ( "github.com/caddyserver/caddy/v2" ) -// Route represents a set of matching rules, -// middlewares, and a responder for handling HTTP -// requests. +// Route consists of a set of rules for matching HTTP requests, +// a list of handlers to execute, and optional flow control +// parameters which customize the handling of HTTP requests +// in a highly flexible and performant manner. type Route struct { - Group string `json:"group,omitempty"` - MatcherSetsRaw RawMatcherSets `json:"match,omitempty"` - HandlersRaw []json.RawMessage `json:"handle,omitempty"` - Terminal bool `json:"terminal,omitempty"` + // 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. + 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 + // within a set are AND'ed together. + MatcherSetsRaw RawMatcherSets `json:"match,omitempty" caddy:"namespace=http.matchers"` + + // The list of handlers for this route. Upon matching a request, they are chained + // together in a middleware fashion: requests flow from the first handler to the last + // (top of the list to the bottom), with the possibility that any handler could stop + // the chain and/or return an error. Responses flow back through the chain (bottom of + // the list to the top) as they are written out to the client. + // + // Not all handlers call the next handler in the chain. For example, the reverse_proxy + // handler always sends a request upstream or returns an error. Thus, configuring + // handlers after reverse_proxy in the same route is illogical, since they would never + // be executed. You will want to put handlers which originate the response at the very + // end of your route(s). The documentation for a module should state whether it invokes + // the next handler, but sometimes it is common sense. + // + // Some handlers manipulate the response. Remember that requests flow down the list, and + // responses flow up the list. + // + // For example, if you wanted to use both `templates` and `encode` handlers, you would + // need to put `templates` after `encode` in your route, because responses flow up. + // Thus, `templates` will be able to parse and execute the plain-text response as a + // template, and then return it up to the `encode` handler which will then compress it + // into a binary format. + // + // If `templates` came before `encode`, then `encode` would write a compressed, + // binary-encoded response to `templates` which would not be able to parse the response + // properly. + // + // The correct order, then, is this: + // + // [ + // {"handler": "encode"}, + // {"handler": "templates"}, + // {"handler": "file_server"} + // ] + // + // The request flows ⬇️ DOWN (`encode` -> `templates` -> `file_server`). + // + // 1. First, `encode` will choose how to `encode` the response and wrap the response. + // 2. Then, `templates` will wrap the response with a buffer. + // 3. Finally, `file_server` will originate the content from a file. + // + // The response flows ⬆️ UP (`file_server` -> `templates` -> `encode`): + // + // 1. First, `file_server` will write the file to the response. + // 2. That write will be buffered and then executed by `templates`. + // 3. Lastly, the write from `templates` will flow into `encode` which will compress the stream. + // + // 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. + Terminal bool `json:"terminal,omitempty"` // decoded values MatcherSets MatcherSets `json:"-"` @@ -54,22 +113,23 @@ type RouteList []Route func (routes RouteList) Provision(ctx caddy.Context) error { for i, route := range routes { // matchers - matcherSets, err := route.MatcherSetsRaw.Setup(ctx) + matchersIface, err := ctx.LoadModule(&route, "MatcherSetsRaw") + if err != nil { + return fmt.Errorf("loadng matchers in route %d: %v", i, err) + } + err = routes[i].MatcherSets.FromInterface(matchersIface) if err != nil { - return err + return fmt.Errorf("route %d: %v", i, err) } - routes[i].MatcherSets = matcherSets - routes[i].MatcherSetsRaw = nil // allow GC to deallocate // handlers - for j, rawMsg := range route.HandlersRaw { - mh, err := ctx.LoadModuleInline("handler", "http.handlers", rawMsg) - if err != nil { - return fmt.Errorf("loading handler module in position %d: %v", j, err) - } - routes[i].Handlers = append(routes[i].Handlers, mh.(MiddlewareHandler)) + handlersIface, err := ctx.LoadModule(&route, "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)) } - routes[i].HandlersRaw = nil // allow GC to deallocate } return nil } @@ -171,28 +231,7 @@ func (mset MatcherSet) Match(r *http.Request) bool { // RawMatcherSets is a group of matcher sets // in their raw, JSON form. -type RawMatcherSets []map[string]json.RawMessage - -// Setup sets up all matcher sets by loading each matcher module -// and returning the group of provisioned matcher sets. -func (rm RawMatcherSets) Setup(ctx caddy.Context) (MatcherSets, error) { - if rm == nil { - return nil, nil - } - var ms MatcherSets - for _, matcherSet := range rm { - var matchers MatcherSet - for modName, rawMsg := range matcherSet { - val, err := ctx.LoadModule("http.matchers."+modName, rawMsg) - if err != nil { - return nil, fmt.Errorf("loading matcher module '%s': %v", modName, err) - } - matchers = append(matchers, val.(RequestMatcher)) - } - ms = append(ms, matchers) - } - return ms, nil -} +type RawMatcherSets []caddy.ModuleMap // MatcherSets is a group of matcher sets capable // of checking whether a request matches any of @@ -202,11 +241,27 @@ type MatcherSets []MatcherSet // AnyMatch returns true if req matches any of the // matcher sets in mss or if there are no matchers, // in which case the request always matches. -func (mss MatcherSets) AnyMatch(req *http.Request) bool { - for _, ms := range mss { - if ms.Match(req) { +func (ms MatcherSets) AnyMatch(req *http.Request) bool { + for _, m := range ms { + if m.Match(req) { return true } } - return len(mss) == 0 + return len(ms) == 0 +} + +// FromInterface fills ms from an interface{} value obtained from LoadModule. +func (ms *MatcherSets) FromInterface(matcherSets interface{}) error { + for _, matcherSetIfaces := range matcherSets.([]map[string]interface{}) { + var matcherSet MatcherSet + for _, matcher := range matcherSetIfaces { + reqMatcher, ok := matcher.(RequestMatcher) + if !ok { + return fmt.Errorf("decoded module is not a RequestMatcher: %#v", matcher) + } + matcherSet = append(matcherSet, reqMatcher) + } + *ms = append(*ms, matcherSet) + } + return nil } |