diff options
Diffstat (limited to 'caddyconfig/httpcaddyfile/directives.go')
-rw-r--r-- | caddyconfig/httpcaddyfile/directives.go | 124 |
1 files changed, 109 insertions, 15 deletions
diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go index 19ecb26..7acdb8c 100644 --- a/caddyconfig/httpcaddyfile/directives.go +++ b/caddyconfig/httpcaddyfile/directives.go @@ -16,6 +16,7 @@ package httpcaddyfile import ( "encoding/json" + "sort" "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig" @@ -23,27 +24,45 @@ import ( "github.com/caddyserver/caddy/v2/modules/caddyhttp" ) -// defaultDirectiveOrder specifies the order +// directiveOrder specifies the order // to apply directives in HTTP routes. -var defaultDirectiveOrder = []string{ +var directiveOrder = []string{ + "root", + + "redir", "rewrite", + "strip_prefix", "strip_suffix", "uri_replace", "try_files", + "basicauth", "headers", "request_header", "encode", "templates", + + "handle", "route", - "redir", + "respond", "reverse_proxy", "php_fastcgi", "file_server", } +// directiveIsOrdered returns true if dir is +// a known, ordered (sorted) directive. +func directiveIsOrdered(dir string) bool { + for _, d := range directiveOrder { + if d == dir { + return true + } + } + return false +} + // RegisterDirective registers a unique directive dir with an // associated unmarshaling (setup) function. When directive dir // is encountered in a Caddyfile, setupFunc will be called to @@ -92,10 +111,11 @@ func RegisterHandlerDirective(dir string, setupFunc UnmarshalHandlerFunc) { // Caddyfile tokens. type Helper struct { *caddyfile.Dispenser - options map[string]interface{} - warnings *[]caddyconfig.Warning - matcherDefs map[string]caddy.ModuleMap - parentBlock caddyfile.ServerBlock + options map[string]interface{} + warnings *[]caddyconfig.Warning + matcherDefs map[string]caddy.ModuleMap + parentBlock caddyfile.ServerBlock + groupCounter counter } // Option gets the option keyed by name. @@ -127,8 +147,8 @@ func (h Helper) JSON(val interface{}) json.RawMessage { return caddyconfig.JSON(val, h.warnings) } -// MatcherToken assumes the current token is (possibly) a matcher, and -// if so, returns the matcher set along with a true value. If the current +// MatcherToken assumes the next argument token is (possibly) a matcher, +// and if so, returns the matcher set along with a true value. If the next // token is not a matcher, nil and false is returned. Note that a true // value may be returned with a nil matcher set if it is a catch-all. func (h Helper) MatcherToken() (caddy.ModuleMap, bool, error) { @@ -165,18 +185,39 @@ func (h Helper) NewRoute(matcherSet caddy.ModuleMap, } } +// GroupRoutes adds the routes (caddyhttp.Route type) in vals to the +// same group, if there is more than one route in vals. +func (h Helper) GroupRoutes(vals []ConfigValue) { + // ensure there's at least two routes; group of one is pointless + var count int + for _, v := range vals { + if _, ok := v.Value.(caddyhttp.Route); ok { + count++ + if count > 1 { + break + } + } + } + if count < 2 { + return + } + + // now that we know the group will have some effect, do it + groupName := h.groupCounter.nextGroup() + for i := range vals { + if route, ok := vals[i].Value.(caddyhttp.Route); ok { + route.Group = groupName + vals[i].Value = route + } + } +} + // NewBindAddresses returns config values relevant to adding // listener bind addresses to the config. func (h Helper) NewBindAddresses(addrs []string) []ConfigValue { return []ConfigValue{{Class: "bind", Value: addrs}} } -// NewVarsRoute returns config values relevant to adding a -// "vars" wrapper route to the config. -func (h Helper) NewVarsRoute(route caddyhttp.Route) []ConfigValue { - return []ConfigValue{{Class: "var", Value: route}} -} - // ConfigValue represents a value to be added to the final // configuration, or a value to be consulted when building // the final configuration. @@ -197,6 +238,59 @@ type ConfigValue struct { directive string } +func sortRoutes(routes []ConfigValue) { + dirPositions := make(map[string]int) + for i, dir := range directiveOrder { + dirPositions[dir] = i + } + + // while we are sorting, we will need to decode a route's path matcher + // in order to sub-sort by path length; we can amortize this operation + // for efficiency by storing the decoded matchers in a slice + decodedMatchers := make([]caddyhttp.MatchPath, len(routes)) + + sort.SliceStable(routes, func(i, j int) bool { + iDir, jDir := routes[i].directive, routes[j].directive + if iDir == jDir { + // directives are the same; sub-sort by path matcher length + // if there's only one matcher set and one path (common case) + iRoute, ok := routes[i].Value.(caddyhttp.Route) + if !ok { + return false + } + jRoute, ok := routes[j].Value.(caddyhttp.Route) + if !ok { + return false + } + + if len(iRoute.MatcherSetsRaw) == 1 && len(jRoute.MatcherSetsRaw) == 1 { + // use already-decoded matcher, or decode if it's the first time seeing it + iPM, jPM := decodedMatchers[i], decodedMatchers[j] + if iPM == nil { + var pathMatcher caddyhttp.MatchPath + _ = json.Unmarshal(iRoute.MatcherSetsRaw[0]["path"], &pathMatcher) + decodedMatchers[i] = pathMatcher + iPM = pathMatcher + } + if jPM == nil { + var pathMatcher caddyhttp.MatchPath + _ = json.Unmarshal(jRoute.MatcherSetsRaw[0]["path"], &pathMatcher) + decodedMatchers[j] = pathMatcher + jPM = pathMatcher + } + + // if there is only one path in the matcher, sort by + // longer path (more specific) first + if len(iPM) == 1 && len(jPM) == 1 { + return len(iPM[0]) > len(jPM[0]) + } + } + } + + return dirPositions[iDir] < dirPositions[jDir] + }) +} + // serverBlock pairs a Caddyfile server block // with a "pile" of config values, keyed by class // name. |