Repository navigation
fix(api): keep relaying the request body after the sandbox answers - #5
Merged
Merged
Conversation
Go's HTTP/1 server drains and closes the inbound request body when the response starts, unless the handler enables full duplex. ReverseProxy hands that same body to the transport as the outbound request body, and the transport reads it twice: the Content-Length bytes, then once more expecting io.EOF. When the backend answers in between (envd's Connect process.Process/Start does when the host is loaded), the server closes the body first, the transport's request write fails, it closes the upstream connection, and ReverseProxy aborts the half-sent response: httputil: ReverseProxy read error during body copy: read tcp ...: use of closed network connection The client sees the stream cut off mid-response. See golang/go#40747. Enable full duplex on every proxied request so the body stays readable for the transport after the response begins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
matej21
marked this pull request as ready for review
October 24, 2026 10:10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The sandbox proxy now calls
http.NewResponseController(w).EnableFullDuplex()beforeReverseProxy.ServeHTTP.Why
Clients saw envd streams (
process.Process/Start) drop mid-response from time to time. On a loaded host it happened twice within a few tens of ms. The client sawsocket connection was closed unexpectedly, and edvabe logged this at the same moment:This is golang/go#40747:
ReverseProxyhands the inbound request body to the transport as the outbound body. The transport reads it twice: once for theContent-Lengthbytes, then once more expectingio.EOF.Content-Lengthbytes arrive, so it can answer between those two reads. That needs the host to be busy enough to deschedule the transport's write goroutine.http: invalid Read on closed Body. It closes the upstream connection.use of closed network connection.ReverseProxypanics withErrAbortHandler, and the server drops the client connection mid-stream.With full duplex the server leaves the body alone, and the transport finishes the request write normally. HTTP/2 requests are always full duplex. On those,
EnableFullDuplexreturnsErrNotSupported, which is why the error is ignored.The same class of failure needs only a backend that starts its response before it has read the whole body, so any proxied user-port service could hit it too.
Verification
TestProxyStreamsResponseWhileRequestBodyInFlight: the backend answers before reading the body, and the client sends the body 10 ms late, 20 attempts.response = "start\n", unexpected EOFand the log line above.go test ./internal/api/ -run TestProxyStreamsResponseWhileRequestBodyInFlight -count=50passes (1,000 requests).go test ./...andgo vet ./...pass.golangci-lintis not installed here, so it was not run.process.Process/Startstreams from a Bun client. The Go toolchain is patched only for the repro, with a 20 ms sleep before the transport's EOF read (net/http/transfer.go, viago build -overlay). That widens the race window so it shows up every time:use of closed network connectionlog line per drop.ghcr.io/contember/edvabe:0.4.0: 34/40 truncated.Not verified: an unmodified-toolchain repro of the envd-specific timing. It depends on host scheduling, and it reproduces only on busy CI runners.
🤖 Generated with Claude Code