diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go index e74a3fe7..b866c7d8 100644 --- a/caddyconfig/httpcaddyfile/directives.go +++ b/caddyconfig/httpcaddyfile/directives.go @@ -25,9 +25,9 @@ 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{ "rewrite", "strip_prefix", "strip_suffix", @@ -168,6 +168,8 @@ 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 @@ -185,7 +187,7 @@ func (h Helper) GroupRoutes(vals []ConfigValue) { // now that we know the group will have some effect, do it groupNum := *h.groupCounter - for i := 0; i < len(vals); i++ { + for i := range vals { if route, ok := vals[i].Value.(caddyhttp.Route); ok { route.Group = fmt.Sprintf("group%d", groupNum) vals[i].Value = route @@ -226,7 +228,12 @@ type ConfigValue struct { directive string } -func sortRoutes(handlers []ConfigValue, dirPositions map[string]int) { +func sortRoutes(handlers []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 diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go index 257e7be3..6ed4e398 100644 --- a/caddyconfig/httpcaddyfile/httptype.go +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -65,8 +65,8 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock, val, err = parseOptHTTPPort(disp) case "https_port": val, err = parseOptHTTPSPort(disp) - case "handler_order": - val, err = parseOptHandlerOrder(disp) + case "order": + val, err = parseOptOrder(disp) case "experimental_http3": val, err = parseOptExperimentalHTTP3(disp) case "storage": @@ -397,23 +397,9 @@ func (st *ServerType) serversFromPairings( siteSubroute.Routes = append(siteSubroute.Routes, cfgVal.Value.(caddyhttp.Route)) } - // set up each handler directive - the order of the handlers - // as they are added to the routes depends on user preference + // set up each handler directive, making sure to honor directive order dirRoutes := sblock.pile["route"] - handlerOrder, ok := options["handler_order"].([]string) - if !ok { - handlerOrder = defaultDirectiveOrder - } - if len(handlerOrder) == 1 && handlerOrder[0] == "appearance" { - handlerOrder = nil - } - if handlerOrder != nil { - dirPositions := make(map[string]int) - for i, dir := range handlerOrder { - dirPositions[dir] = i - } - sortRoutes(dirRoutes, dirPositions) - } + sortRoutes(dirRoutes) // add all the routes piled in from directives for _, r := range dirRoutes { diff --git a/caddyconfig/httpcaddyfile/options.go b/caddyconfig/httpcaddyfile/options.go index e87b30f8..e81528e1 100644 --- a/caddyconfig/httpcaddyfile/options.go +++ b/caddyconfig/httpcaddyfile/options.go @@ -58,27 +58,80 @@ func parseOptExperimentalHTTP3(d *caddyfile.Dispenser) (bool, error) { return true, nil } -func parseOptHandlerOrder(d *caddyfile.Dispenser) ([]string, error) { - if !d.Next() { - return nil, d.ArgErr() - } - order := d.RemainingArgs() - if len(order) == 1 && order[0] == "appearance" { - return []string{"appearance"}, nil - } - if len(order) > 0 && d.NextBlock(0) { - return nil, d.Err("cannot open block if there are arguments") - } - for d.NextBlock(0) { - order = append(order, d.Val()) +func parseOptOrder(d *caddyfile.Dispenser) ([]string, error) { + newOrder := directiveOrder + + for d.Next() { + // get directive name + if !d.Next() { + return nil, d.ArgErr() + } + dirName := d.Val() + if _, ok := registeredDirectives[dirName]; !ok { + return nil, fmt.Errorf("%s is not a registered directive", dirName) + } + + // get positional token + if !d.Next() { + return nil, d.ArgErr() + } + pos := d.Val() + + // if directive exists, first remove it + for i, d := range newOrder { + if d == dirName { + newOrder = append(newOrder[:i], newOrder[i+1:]...) + break + } + } + + // act on the positional + switch pos { + case "first": + newOrder = append([]string{dirName}, newOrder...) + if d.NextArg() { + return nil, d.ArgErr() + } + directiveOrder = newOrder + return newOrder, nil + case "last": + newOrder = append(newOrder, dirName) + if d.NextArg() { + return nil, d.ArgErr() + } + directiveOrder = newOrder + return newOrder, nil + case "before": + case "after": + default: + return nil, fmt.Errorf("unknown positional '%s'", pos) + } + + // get name of other directive + if !d.NextArg() { + return nil, d.ArgErr() + } + otherDir := d.Val() if d.NextArg() { return nil, d.ArgErr() } + + // insert directive into proper position + for i, d := range newOrder { + if d == otherDir { + if pos == "before" { + newOrder = append(newOrder[:i], append([]string{dirName}, newOrder[i:]...)...) + } else if pos == "after" { + newOrder = append(newOrder[:i+1], append([]string{dirName}, newOrder[i+1:]...)...) + } + break + } + } } - if len(order) == 0 { - return nil, d.ArgErr() - } - return order, nil + + directiveOrder = newOrder + + return newOrder, nil } func parseOptStorage(d *caddyfile.Dispenser) (caddy.StorageConverter, error) {