Fix http 1.1 streaming on client connection: close#2527
Open
Greisby wants to merge 1 commit into
Open
Conversation
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.
This is a fix for async stream responses broken when client sends
Connection: closeSummary
HttpResponseImpl::makeHeaderString()skips emittingTransfer-Encoding: chunkedfor async stream responses when the client requestsConnection: close.Additionally, the async stream callback itself is skipped in close-connection mode.
This leaves the response body completely unframed: clients receive headers but zero body bytes.
Affected versions: drogon 1.9.13 (and all prior versions with the same logic).
Root cause (two bugs)
Bug 1 — Missing
Transfer-Encoding: chunkedheader for async streaming responsesIn
lib/src/HttpResponseImpl.cc,makeHeaderString()line ~753:The code appears to assume that when
Connection: closeis enabled, async streams can rely on close-delimited HTTP body framing:That framing mode is valid both HTTP/1.0 and HTTP/1.1.
However, this does not work correctly for async streaming responses because:
Bug 2 — Async stream callback skipped when Connection: close is enabled
In
lib/src/HttpServer.cc,sendResponse()line ~988 andsendResponses()line ~1069:When ifCloseConnection() is true, the async stream callback is never invoked.
As a consequence:
This may have been intentionally linked to Bug 1 in order to avoid sending incorrectly framed stream data.
However, this behavior breaks async streaming responses independently of Bug 1.
Even if Transfer-Encoding: chunked were emitted correctly, the response body would still remain empty because the callback responsible for producing the stream is skipped entirely.
Observable symptoms
[http] Stream ends prematurely at 0, should be 18446744073709551615(UINT64_MAX = "unknown length" sentinel).
0 bytes read despite 76 ms latency (headers arrived).
[isEAGAIN] write buffer is fullafter attempting to send 3 KB → client never drains the socket becauseit considers the response complete.
The bug is triggered by any HTTP/1.1 client that sends
Connection: close(ffmpeg's HTTP demuxer, many embedded clients, HTTP load balancers doing single-shot requests).Fix
HttpResponseImpl.cc:Replace the
!ifCloseConnection()guard withversion_ != Version::kHttp10:HttpServer.cc:Replace the
!ifCloseConnection()guard with the same version check (both insendResponse()andsendResponses())Rationale:
Connection: close+Transfer-Encoding: chunkedtogether (RFC 9112 §6.1 + §7.1).They are orthogonal: chunked frames the body, close terminates the connection after.
!= kHttp10guard correctly preserves the old behavior for 1.0(skip async stream, log a warning).
Transfer-Encoding; the!= kHttp10guard is future-proof for any (potential) version beyond HTTP/1.0.Therefore HTTP/1.1 async streams must continue emitting Transfer-Encoding: chunked independently of connection persistence.
Reproduction
newAsyncStreamResponseendpoint that writes data asynchronously.curl -H "Connection: close" -o out.bin http://host:port/streamout.binis 0 bytes. Server logs show write-buffer-full / EAGAIN.Connection: closeheader → works fine (chunked encoding emitted).Compatibility
Connection: close+ async stream (previously broken).Connection: closestill works correctly for non-stream responses (Content-Length framed).