From e51e56a4944622c0a2c7d19da4bb6b9bb07c1973 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Thu, 16 Jan 2020 17:08:52 -0700 Subject: httpcaddyfile: Fix nested blocks; add handle directive; refactor The fix that was initially put forth in #2971 was good, but only for up to one layer of nesting. The real problem was that we forgot to increment nesting when already inside a block if we saw another open curly brace that opens another block (dispenser.go L157-158). The new 'handle' directive allows HTTP Caddyfiles to be designed more like nginx location blocks if the user prefers. Inside a handle block, directives are still ordered just like they are outside of them, but handler blocks at a given level of nesting are mutually exclusive. This work benefitted from some refactoring and cleanup. --- caddyconfig/httpcaddyfile/directives.go | 53 +++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 19 deletions(-) (limited to 'caddyconfig/httpcaddyfile/directives.go') diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go index b866c7d..ab7a040 100644 --- a/caddyconfig/httpcaddyfile/directives.go +++ b/caddyconfig/httpcaddyfile/directives.go @@ -16,7 +16,6 @@ package httpcaddyfile import ( "encoding/json" - "fmt" "sort" "github.com/caddyserver/caddy/v2" @@ -28,7 +27,10 @@ import ( // directiveOrder specifies the order // to apply directives in HTTP routes. var directiveOrder = []string{ + "root", + "rewrite", + "strip_prefix", "strip_suffix", "uri_replace", @@ -38,7 +40,10 @@ var directiveOrder = []string{ "request_header", "encode", "templates", + + "handle", "route", + "redir", "respond", "reverse_proxy", @@ -46,6 +51,17 @@ var directiveOrder = []string{ "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 @@ -98,7 +114,7 @@ type Helper struct { warnings *[]caddyconfig.Warning matcherDefs map[string]caddy.ModuleMap parentBlock caddyfile.ServerBlock - groupCounter *int + groupCounter counter } // Option gets the option keyed by name. @@ -130,8 +146,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) { @@ -186,14 +202,13 @@ func (h Helper) GroupRoutes(vals []ConfigValue) { } // now that we know the group will have some effect, do it - groupNum := *h.groupCounter + groupName := h.groupCounter.nextGroup() for i := range vals { if route, ok := vals[i].Value.(caddyhttp.Route); ok { - route.Group = fmt.Sprintf("group%d", groupNum) + route.Group = groupName vals[i].Value = route } } - *h.groupCounter++ } // NewBindAddresses returns config values relevant to adding @@ -202,12 +217,6 @@ 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. @@ -228,7 +237,7 @@ type ConfigValue struct { directive string } -func sortRoutes(handlers []ConfigValue) { +func sortRoutes(routes []ConfigValue) { dirPositions := make(map[string]int) for i, dir := range directiveOrder { dirPositions[dir] = i @@ -237,15 +246,21 @@ func sortRoutes(handlers []ConfigValue) { // 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(handlers)) + decodedMatchers := make([]caddyhttp.MatchPath, len(routes)) - sort.SliceStable(handlers, func(i, j int) bool { - iDir, jDir := handlers[i].directive, handlers[j].directive + 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 := handlers[i].Value.(caddyhttp.Route) - jRoute := handlers[j].Value.(caddyhttp.Route) + 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 -- cgit v1.2.3