summaryrefslogtreecommitdiff
path: root/caddyconfig/httpcaddyfile
diff options
context:
space:
mode:
authorFrancis Lavoie <lavofr@gmail.com>2023-05-16 11:27:52 -0400
committerGitHub <noreply@github.com>2023-05-16 15:27:52 +0000
commitcbf16f6d9eb77f37d6eb588ff3e54cfdfddecc21 (patch)
treeef48ed58e9f0374df46cabecd7edf29c2be019e2 /caddyconfig/httpcaddyfile
parent13a37688dcdc1ffa8e9322dad0bffac0c0c9893a (diff)
caddyhttp: Implement named routes, `invoke` directive (#5107)
* caddyhttp: Implement named routes, `invoke` directive * gofmt * Add experimental marker * Adjust route compile comments
Diffstat (limited to 'caddyconfig/httpcaddyfile')
-rw-r--r--caddyconfig/httpcaddyfile/builtins.go22
-rw-r--r--caddyconfig/httpcaddyfile/directives.go1
-rw-r--r--caddyconfig/httpcaddyfile/httptype.go113
3 files changed, 134 insertions, 2 deletions
diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go
index 45da4a8..d6cdf84 100644
--- a/caddyconfig/httpcaddyfile/builtins.go
+++ b/caddyconfig/httpcaddyfile/builtins.go
@@ -48,6 +48,7 @@ func init() {
RegisterHandlerDirective("route", parseRoute)
RegisterHandlerDirective("handle", parseHandle)
RegisterDirective("handle_errors", parseHandleErrors)
+ RegisterHandlerDirective("invoke", parseInvoke)
RegisterDirective("log", parseLog)
RegisterHandlerDirective("skip_log", parseSkipLog)
}
@@ -764,6 +765,27 @@ func parseHandleErrors(h Helper) ([]ConfigValue, error) {
}, nil
}
+// parseInvoke parses the invoke directive.
+func parseInvoke(h Helper) (caddyhttp.MiddlewareHandler, error) {
+ h.Next() // consume directive
+ if !h.NextArg() {
+ return nil, h.ArgErr()
+ }
+ for h.Next() || h.NextBlock(0) {
+ return nil, h.ArgErr()
+ }
+
+ // remember that we're invoking this name
+ // to populate the server with these named routes
+ if h.State[namedRouteKey] == nil {
+ h.State[namedRouteKey] = map[string]struct{}{}
+ }
+ h.State[namedRouteKey].(map[string]struct{})[h.Val()] = struct{}{}
+
+ // return the handler
+ return &caddyhttp.Invoke{Name: h.Val()}, nil
+}
+
// parseLog parses the log directive. Syntax:
//
// log {
diff --git a/caddyconfig/httpcaddyfile/directives.go b/caddyconfig/httpcaddyfile/directives.go
index b8faa4a..1223013 100644
--- a/caddyconfig/httpcaddyfile/directives.go
+++ b/caddyconfig/httpcaddyfile/directives.go
@@ -65,6 +65,7 @@ var directiveOrder = []string{
"templates",
// special routing & dispatching directives
+ "invoke",
"handle",
"handle_path",
"route",
diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go
index 90c90ee..c7aeb94 100644
--- a/caddyconfig/httpcaddyfile/httptype.go
+++ b/caddyconfig/httpcaddyfile/httptype.go
@@ -52,8 +52,10 @@ type ServerType struct {
}
// Setup makes a config from the tokens.
-func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
- options map[string]any) (*caddy.Config, []caddyconfig.Warning, error) {
+func (st ServerType) Setup(
+ inputServerBlocks []caddyfile.ServerBlock,
+ options map[string]any,
+) (*caddy.Config, []caddyconfig.Warning, error) {
var warnings []caddyconfig.Warning
gc := counter{new(int)}
state := make(map[string]any)
@@ -79,6 +81,11 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
return nil, warnings, err
}
+ originalServerBlocks, err = st.extractNamedRoutes(originalServerBlocks, options, &warnings)
+ if err != nil {
+ return nil, warnings, err
+ }
+
// replace shorthand placeholders (which are convenient
// when writing a Caddyfile) with their actual placeholder
// identifiers or variable names
@@ -172,6 +179,18 @@ func (st ServerType) Setup(inputServerBlocks []caddyfile.ServerBlock,
result.directive = dir
sb.pile[result.Class] = append(sb.pile[result.Class], result)
}
+
+ // specially handle named routes that were pulled out from
+ // the invoke directive, which could be nested anywhere within
+ // some subroutes in this directive; we add them to the pile
+ // for this server block
+ if state[namedRouteKey] != nil {
+ for name := range state[namedRouteKey].(map[string]struct{}) {
+ result := ConfigValue{Class: namedRouteKey, Value: name}
+ sb.pile[result.Class] = append(sb.pile[result.Class], result)
+ }
+ state[namedRouteKey] = nil
+ }
}
}
@@ -403,6 +422,77 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options
return serverBlocks[1:], nil
}
+// extractNamedRoutes pulls out any named route server blocks
+// so they don't get parsed as sites, and stores them in options
+// for later.
+func (ServerType) extractNamedRoutes(
+ serverBlocks []serverBlock,
+ options map[string]any,
+ warnings *[]caddyconfig.Warning,
+) ([]serverBlock, error) {
+ namedRoutes := map[string]*caddyhttp.Route{}
+
+ gc := counter{new(int)}
+ state := make(map[string]any)
+
+ // copy the server blocks so we can
+ // splice out the named route ones
+ filtered := append([]serverBlock{}, serverBlocks...)
+ index := -1
+
+ for _, sb := range serverBlocks {
+ index++
+ if !sb.block.IsNamedRoute {
+ continue
+ }
+
+ // splice out this block, because we know it's not a real server
+ filtered = append(filtered[:index], filtered[index+1:]...)
+ index--
+
+ if len(sb.block.Segments) == 0 {
+ continue
+ }
+
+ // zip up all the segments since ParseSegmentAsSubroute
+ // was designed to take a directive+
+ wholeSegment := caddyfile.Segment{}
+ for _, segment := range sb.block.Segments {
+ wholeSegment = append(wholeSegment, segment...)
+ }
+
+ h := Helper{
+ Dispenser: caddyfile.NewDispenser(wholeSegment),
+ options: options,
+ warnings: warnings,
+ matcherDefs: nil,
+ parentBlock: sb.block,
+ groupCounter: gc,
+ State: state,
+ }
+
+ handler, err := ParseSegmentAsSubroute(h)
+ if err != nil {
+ return nil, err
+ }
+ subroute := handler.(*caddyhttp.Subroute)
+ route := caddyhttp.Route{}
+
+ if len(subroute.Routes) == 1 && len(subroute.Routes[0].MatcherSetsRaw) == 0 {
+ // if there's only one route with no matcher, then we can simplify
+ route.HandlersRaw = append(route.HandlersRaw, subroute.Routes[0].HandlersRaw[0])
+ } else {
+ // otherwise we need the whole subroute
+ route.HandlersRaw = []json.RawMessage{caddyconfig.JSONModuleObject(handler, "handler", subroute.CaddyModule().ID.Name(), h.warnings)}
+ }
+
+ namedRoutes[sb.block.Keys[0]] = &route
+ }
+ options["named_routes"] = namedRoutes
+
+ return filtered, nil
+}
+
// serversFromPairings creates the servers for each pairing of addresses
// to server blocks. Each pairing is essentially a server definition.
func (st *ServerType) serversFromPairings(
@@ -542,6 +632,24 @@ func (st *ServerType) serversFromPairings(
}
}
+ // add named routes to the server if 'invoke' was used inside of it
+ configuredNamedRoutes := options["named_routes"].(map[string]*caddyhttp.Route)
+ for _, sblock := range p.serverBlocks {
+ if len(sblock.pile[namedRouteKey]) == 0 {
+ continue
+ }
+ for _, value := range sblock.pile[namedRouteKey] {
+ if srv.NamedRoutes == nil {
+ srv.NamedRoutes = map[string]*caddyhttp.Route{}
+ }
+ name := value.Value.(string)
+ if configuredNamedRoutes[name] == nil {
+ return nil, fmt.Errorf("cannot invoke named route '%s', which was not defined", name)
+ }
+ srv.NamedRoutes[name] = configuredNamedRoutes[name]
+ }
+ }
+
// create a subroute for each site in the server block
for _, sblock := range p.serverBlocks {
matcherSetsEnc, err := st.compileEncodedMatcherSets(sblock)
@@ -1469,6 +1577,7 @@ type sbAddrAssociation struct {
}
const matcherPrefix = "@"
+const namedRouteKey = "named_route"
// Interface guard
var _ caddyfile.ServerType = (*ServerType)(nil)