caddy: Caddyfile `handle_errors` optional status code argument
The handle_errors directive is useful for implementing custom error pages and such.
Currently, its ergonomics are not great, because matching errors by status code needs to be done via a handle with an expression matcher (see examples in the docs).
I suggest that we add an optional status code argument (or variadic multiple status codes) to handle_errors as a shortcut for wrapping the routes in a status code matcher. For example:
handle_errors 404 410 {
<directives...>
}
This would effectively be the same as:
handle_errors {
@codes expression `{http.error.status_code} in [404, 410]`
handle @codes {
<directives...>
}
}
Using handle_errors this way multiple times should simply append more routes. A handle_errors with no arguments (no matcher) should always be inserted last to ensure it acts as a fallback.
About this issue
- Original URL
- State: open
- Created 8 months ago
- Reactions: 2
- Comments: 18 (16 by maintainers)
@mholt I was referring to the arguments on the first line right after the
handle_errorsdirective (So404and410if the example washandle_errors 404 410 {...). But looks like thatparseSegmentAsSubrouteexpects the cursor to be at the token just before the block opening and there are helper functions to manipulate the cursor so I think I got this figured out. Thank you!See
handle_pathfor example:https://github.com/caddyserver/caddy/blob/18f34290d26d10b6dd62c848b6bd5180d56d7f3a/modules/caddyhttp/rewrite/caddyfile.go#L155
Basically just need to update
func parseHandleErrorsto callh.RemainingArgs()and if that slice is non-empty then you create acaddyhttp.MatchExpression(with fieldExprset) which is a string like"{http.error.status_code} in [" + strings.Join(", ", args) + "]"effectively.Bonus points (not required, but nice to have) for handling
4xxstyle strings (which means any 400-499 status, a convention we support in most places with status code matching), which should separately expand to something like"{http.error.status_code} >= 200 && {http.error.status_code} <= 299"given2xx, and join that with an||if there’s any args with noxx.So
handle_errors 2xx 404 {should produce a matcher like:In addition, the code in
httptype.godoingif errorSubrouteVals, ok := sblock.pile["error_route"]; ok {needs to be updated to sort theerror_routepile such that any of the ones with matchers go first, and ones with no matcher go last.Run
caddy adapt --prettywhile developing to see what the JSON output looks like, make sure it looks correct. You can write adapt tests as well in files incaddytest/integration/caddyfile_adaptwhich assert that for a given Caddyfile, the correct JSON is produced. You can see an example of a sort like that from earlier in that same file withsort.SliceStable(p.serverBlocks, or even more relevant, look atfunc sortRoutes(indirectives.go(maybe that function can be reused here? Not sure but worth a try).Not only 2. It can be any number of arguments or something like
4xx, see example in the Response Matcher ofreverse_proxy.You can either keep it aside in the parsing process then append it at the end after parsing all other handlers, or manage the sorting logic in your implementation of the
sort.Interface.That’s handled in the pile thing. It’ll need to loop through those and insert them as routes in the final config (instead of only grabbing one).
@francislavoie, I think I am almost there, I got individual
handle_errorsdirectives to work with both of the status codes formats. One problem I am facing now is that only the firsthanlde_errorsdirective in theCaddyFileis triggered and all the others are omitted. Can I get some guidance on where in the code we are enforcing this, because up until the code you showed above fromhttptype.goI can see all thehandle_errorsroutes inside theerrorSubrouteVals.Thanks in advance for your help!