mirror of
https://github.com/versity/versitygw.git
synced 2026-09-22 16:04:15 +00:00
The `UnsignedStreamingPayloadTrailer_invalid_chunk_size` integration test failed intermittently in CI with `write: connection reset by peer` instead of the expected `InvalidChunkSizeError`, and needed a rerun to pass. Its tenth case sends a 36KB `aws-chunked` payload that the chunk reader rejects roughly 8KB in, so the gateway answered and closed the connection while the client was still writing the remaining 28KB. fasthttp streams request bodies (`StreamRequestBody`) and never drains what a handler leaves behind: after the handler returns it only calls `releaseRequestStream`, and with `DisableKeepalive` it breaks out of the serve loop and closes the socket with unread bytes still queued, so the kernel answers the client's in-flight writes with an RST. Go's `net/http` transport prefers a request body write error over an already received response, so on the losing side of that race the client never sees the S3 error XML at all. The same exposure applies to every early rejection, not just chunk framing: signature failures, missing buckets and policy denials are all decided before the payload is read. The new `DrainRequestBody` middleware reads and discards whatever is left of the body once the handler chain is done with it, so the client can finish its write and read the real error. It is registered right after the panic recovery middleware and before every route, so it wraps all of them. The drain is capped at 256KB, matching net/http's `maxPostHandlerReadBytes`, so a rejected multi-gigabyte upload is not streamed through the gateway just to be thrown away, and it is bounded by a one second idle deadline and a five second total deadline so a client that stops sending cannot pin a worker. Bodies with more than 256KB still unread are deliberately left alone, and those clients can still see a reset. Only `Content-Length` framed bodies are drained. fasthttp's `requestStream` reports EOF idempotently for those, but for a chunked body it goes back to the socket for another chunk header past the terminating chunk, so re-reading one the handler had already finished would block until the deadline and hold a successful response back with it. This also fixes a second problem found while testing the first. With `--keep-alive`, nothing in fasthttp sets `connectionClose` when a streamed body is left unread, and the same `bufio.Reader` is reused for the next request on that connection, so leftover upload bytes were parsed and served as a separate HTTP request: a `GET` placed inside a `PUT` body was routed, answered and written to the access log. The middleware now sets `Connection: close` whenever the drain does not reach EOF, so a connection that may still hold body bytes is never reused. `--keep-alive` is off by default and is not set by any shipped deployment artifact.