httpcaddyfile: Replace 'handler_order' option with 'order'

This allows individual directives to be ordered relative to others,
where order matters (for example HTTP handlers). Will primarily be
useful when developing new directives, so you don't have to modify the
Caddy source code. Can also be useful if you prefer that redir comes
before rewrite, for example. Note that these are global options. The
route directive can be used to give a specific order to a specific group
of HTTP handler directives.
This commit is contained in:
Matthew Holt 2020-01-16 12:09:54 -07:00
parent 2466ed1484
commit 21643a007a
No known key found for this signature in database
GPG key ID: 2A349DD577D586A5
3 changed files with 85 additions and 39 deletions

View file

@ -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

View file

@ -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 {

View file

@ -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) {