caddy: Exceeding `request_body`'s `max_size` causes unexpected rejection behavior
Caddy version: 2.4.6
The documentation does not state how it’s handled when a request exceeds max_size. I would expect 413 Payload Too Large however 502 Bad Gateway is how Caddy responds.
May I ask, if that is expected behavior, what the rationale is for using that code?
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 16 (10 by maintainers)
I verified that 3d616e8c6d65e5617f5a918d72fb1514c9c7144e will now return HTTP 413 from
Read()if too many bytes are read. I also improved error wrapping generally.Digging into this a bit deeper again. I tried out my suggested
handle_errorsapproach. It won’t really work, because howMaxBytesReaderworks is that it will stop reading from the client, and attempt to write a response, then close the connection. Since most clients won’t stop writing their request until they’re done, they’ll never see the response before the connection is closed.So even if we could change the response code, the client would never see it. Quick google search shows that’s intended and the only way it can actually work to stop receiving data from the client. See https://stackoverflow.com/questions/35173819/request-body-too-large-causing-connection-reset-in-go for example.
This is how I tested it:
Caddyfile:
Send a
curlrequest with a big file, see the result of a closed connection:In Caddy’s logs:
First line is the
:8882server receiving the request (access logs happen after the request is completed, upstream finishes before the proxy does), second line is the:8881proxy performing the roundtrip (and logging how long it took), third line is the error being logged by the:8881HTTP server before passing it onto thehandle_errorsroutes, and the last line is the access log from:8881showing the final request and “response”.Notice that the last log line does show that Caddy attempts to write status
413as perhandle_errorsbut the client never sees it.I think all that we can really do is flesh out the docs on https://caddyserver.com/docs/caddyfile/directives/request_body to better explain how it will behave, clarifying that the client connection will be closed if too many bytes are tried to be read from the request.
Ah, sorry, I misunderstood this issue. I do think this could be handled better in the code though too. Maybe we can somehow wrap the returned error from MaxBytesReader with one of our structured errors that adds status code.
@mholt we already do this https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/requestbody/requestbody.go
The code in the v1 branch is a copy of the stdlib, but I don’t think it actually works correctly because
res.requestTooLarge()can’t be called outside of stdlib, cause it’s not exported. Pretty sure, anyways.Digging into the code a bit more, it’s not possible to yoink out the code from stdlib since it uses private/unexported functions to manipulate the HTTP response state.
There is an open issue https://github.com/golang/go/issues/30715 which is relevant here, if it were implemented (which it seems it will be, the proposal was accepted), we could add a check for this specific error to write a specific status code.
So I’ll keep this issue open and mark it as a feature request that requires an upstream change.
Have you tried my suggestion of handling it with
handle_errors? Wouldn’t that be easier?No, that’s not a reliable way to do it. Clients (bad actors) can lie and send an invalid
Content-Length. The right way is to actually prevent reading past a certain limit.Ah, okay I understand what’s going on then. So since it’s a reader wrapper, basically the thing that invokes reading the request body is what will receive the error. So the
reverse_proxymodule is what reads the body so that it can copy it to the upstream, but it encounters that error. And I think thatreverse_proxycode assumes it’s a problem with the upstream when it gets any kind of error, and writes a 502.I don’t think we can really do much about this unless we either get the Go stdlib to change the error it emits to something we can do a type assertion on to change the response code, or we re-implement the wrapper in Caddy itself to do that. It’s a really simple wrapper, so we could just do that. 🤷♂️
Please do. What’s in Caddy’s logs? Turn on the
debugglobal option to get more details. Turn on thelogdirective in your site to enable access logging.No, there’s no “expected behaviour”, really. Like I said, there is no specific HTTP status code attached to
MaxBytesReader, it just emits an Golang error, which you can then do whatever you like with inhandle_errors.Hmm. Do you have evidence in your logs that that’s the response code it writes? I’m not convinced that Caddy itself sends
502.Basically,
request_bodymax_sizeis using Golang’s built-inMaxBytesReaderwrapper, which apparently just returns an errorerrors.New("http: request body too large").Caddy is not specifically handling this error (and it’s kinda tricky to because it would involve a string comparison to check it, because it’s not a specific error type), so it’ll just trigger Caddy’s default error handling behaviour. You can use
handle_errorsto customize what you do with it, if you want. You can use theexpressionmatcher to match against the error having that message with the{http.error.message}placeholder. Maybe something like this: