httpcaddyfile: 'handle_errors' directive

Not sure I love the name of the directive; might change it later.
This commit is contained in:
Matthew Holt 2020-02-16 22:24:20 -07:00
parent bc2e406572
commit 23cc26d585
No known key found for this signature in database
GPG key ID: 2A349DD577D586A5
3 changed files with 85 additions and 41 deletions

View file

@ -35,7 +35,8 @@ func init() {
RegisterHandlerDirective("redir", parseRedir) RegisterHandlerDirective("redir", parseRedir)
RegisterHandlerDirective("respond", parseRespond) RegisterHandlerDirective("respond", parseRespond)
RegisterHandlerDirective("route", parseRoute) RegisterHandlerDirective("route", parseRoute)
RegisterHandlerDirective("handle", parseHandle) RegisterHandlerDirective("handle", parseSegmentAsSubroute)
RegisterDirective("handle_errors", parseHandleErrors)
} }
// parseBind parses the bind directive. Syntax: // parseBind parses the bind directive. Syntax:
@ -387,36 +388,21 @@ func parseRoute(h Helper) (caddyhttp.MiddlewareHandler, error) {
return sr, nil return sr, nil
} }
// parseHandle parses the route directive.
func parseHandle(h Helper) (caddyhttp.MiddlewareHandler, error) { func parseHandle(h Helper) (caddyhttp.MiddlewareHandler, error) {
var allResults []ConfigValue return parseSegmentAsSubroute(h)
for h.Next() {
for nesting := h.Nesting(); h.NextBlock(nesting); {
dir := h.Val()
dirFunc, ok := registeredDirectives[dir]
if !ok {
return nil, h.Errf("unrecognized directive: %s", dir)
} }
subHelper := h func parseHandleErrors(h Helper) ([]ConfigValue, error) {
subHelper.Dispenser = h.NewFromNextSegment() subroute, err := parseSegmentAsSubroute(h)
results, err := dirFunc(subHelper)
if err != nil { if err != nil {
return nil, h.Errf("parsing caddyfile tokens for '%s': %v", dir, err) return nil, err
} }
for _, result := range results { return []ConfigValue{
result.directive = dir {
allResults = append(allResults, result) Class: "error_route",
} Value: subroute,
} },
}, nil
return buildSubroute(allResults, h.groupCounter)
}
return nil, nil
} }
var tagCounter = 0 var tagCounter = 0

View file

@ -37,6 +37,7 @@ var directiveOrder = []string{
"uri_replace", "uri_replace",
"try_files", "try_files",
// middleware handlers that typically wrap responses
"basicauth", "basicauth",
"header", "header",
"request_header", "request_header",
@ -46,6 +47,7 @@ var directiveOrder = []string{
"handle", "handle",
"route", "route",
// handlers that typically respond to requests
"respond", "respond",
"reverse_proxy", "reverse_proxy",
"php_fastcgi", "php_fastcgi",
@ -291,6 +293,37 @@ func sortRoutes(routes []ConfigValue) {
}) })
} }
// parseSegmentAsSubroute parses the segment such that its subdirectives
// are themselves treated as directives, from which a subroute is built
// and returned.
func parseSegmentAsSubroute(h Helper) (caddyhttp.MiddlewareHandler, error) {
var allResults []ConfigValue
for h.Next() {
for nesting := h.Nesting(); h.NextBlock(nesting); {
dir := h.Val()
dirFunc, ok := registeredDirectives[dir]
if !ok {
return nil, h.Errf("unrecognized directive: %s", dir)
}
subHelper := h
subHelper.Dispenser = h.NewFromNextSegment()
results, err := dirFunc(subHelper)
if err != nil {
return nil, h.Errf("parsing caddyfile tokens for '%s': %v", dir, err)
}
for _, result := range results {
result.directive = dir
allResults = append(allResults, result)
}
}
return buildSubroute(allResults, h.groupCounter) // TODO: should we move this outside the loop?
}
return nil, nil
}
// serverBlock pairs a Caddyfile server block // serverBlock pairs a Caddyfile server block
// with a "pile" of config values, keyed by class // with a "pile" of config values, keyed by class
// name. // name.

View file

@ -474,18 +474,18 @@ func (st *ServerType) serversFromPairings(
return nil, err return nil, err
} }
if len(matcherSetsEnc) == 0 && len(p.serverBlocks) == 1 { // add the site block's route(s) to the server
// no need to wrap the handlers in a subroute if this is srv.Routes = appendSubrouteToRouteList(srv.Routes, siteSubroute, matcherSetsEnc, p, warnings)
// the only server block and there is no matcher for it
srv.Routes = append(srv.Routes, siteSubroute.Routes...) // if error routes are defined, add those too
} else { if errorSubrouteVals, ok := sblock.pile["error_route"]; ok {
srv.Routes = append(srv.Routes, caddyhttp.Route{ if srv.Errors == nil {
MatcherSetsRaw: matcherSetsEnc, srv.Errors = new(caddyhttp.HTTPErrorConfig)
HandlersRaw: []json.RawMessage{ }
caddyconfig.JSONModuleObject(siteSubroute, "handler", "subroute", warnings), for _, val := range errorSubrouteVals {
}, sr := val.Value.(*caddyhttp.Subroute)
Terminal: true, // only first matching site block should be evaluated srv.Errors.Routes = appendSubrouteToRouteList(srv.Errors.Routes, sr, matcherSetsEnc, p, warnings)
}) }
} }
} }
@ -497,6 +497,31 @@ func (st *ServerType) serversFromPairings(
return servers, nil return servers, nil
} }
// appendSubrouteToRouteList appends the routes in subroute
// to the routeList, optionally qualified by matchers.
func appendSubrouteToRouteList(routeList caddyhttp.RouteList,
subroute *caddyhttp.Subroute,
matcherSetsEnc []caddy.ModuleMap,
p sbAddrAssociation,
warnings *[]caddyconfig.Warning) caddyhttp.RouteList {
if len(matcherSetsEnc) == 0 && len(p.serverBlocks) == 1 {
// no need to wrap the handlers in a subroute if this is
// the only server block and there is no matcher for it
routeList = append(routeList, subroute.Routes...)
} else {
routeList = append(routeList, caddyhttp.Route{
MatcherSetsRaw: matcherSetsEnc,
HandlersRaw: []json.RawMessage{
caddyconfig.JSONModuleObject(subroute, "handler", "subroute", warnings),
},
Terminal: true, // only first matching site block should be evaluated
})
}
return routeList
}
// buildSubroute turns the config values, which are expected to be routes
// into a clean and orderly subroute that has all the routes within it.
func buildSubroute(routes []ConfigValue, groupCounter counter) (*caddyhttp.Subroute, error) { func buildSubroute(routes []ConfigValue, groupCounter counter) (*caddyhttp.Subroute, error) {
for _, val := range routes { for _, val := range routes {
if !directiveIsOrdered(val.directive) { if !directiveIsOrdered(val.directive) {