Support HTTP/1.1 request pipelining on the downstream session#876
Open
CodyPubNub wants to merge 2 commits intocloudflare:mainfrom
Open
Support HTTP/1.1 request pipelining on the downstream session#876CodyPubNub wants to merge 2 commits intocloudflare:mainfrom
CodyPubNub wants to merge 2 commits intocloudflare:mainfrom
Conversation
Add opt-in HTTP/1.1 pipelining via HttpSession::set_pipelining_enabled(). When enabled, pipelined requests on a keep-alive connection are served sequentially in request order per RFC 9112 §9.3.2, matching nginx behavior on the same traffic shape. Default (off) is unchanged: the second pipelined request is dropped or surfaced as a 400 by the body-pump idle branch. Non-adopters are untouched: ServerSession::finish() and the H1 HttpSession::reuse() keep their pre-pipelining Result<Option<Stream>> signatures and discard any captured pipelined prefix. Adopters call finish_reuse() / reuse_pipelined() to receive ReusedHttpConnection. Covers both wire shapes: same-segment overread (both requests arrive in one read) via reuse_pipelined(), and two-segment overread (request N+1 arrives while request N's response is still being written) via read_body_or_idle()'s idle branch stashing non-zero reads into the body reader's overread surface. abort_on_close / half_closed are untouched so FIN handling is unchanged. HttpPersistentSettings carries pipelining_enabled + pipelined_prefix across keep-alive reuses; read_request() consumes the prefix first. Resolves cloudflare#377, cloudflare#673
Minimal ProxyHttp that opts in via set_pipelining_enabled(true) in early_request_filter. Matches the reproducer shape from cloudflare#377: pipelined GETs on one connection now both return responses.
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.
Resolves #377, #673
What
Adds an opt-in HTTP/1.1 pipelining mode on the downstream session that serves pipelined requests sequentially in request order per RFC 9112 §9.3.2. Default behavior is unchanged.
HttpSession::set_pipelining_enabled(true)(mirrors the field+accessor shape of existing setters likekeepalive_timeout,abort_on_close,proxy_tasks_enabled). No newProxyHttptrait method.ServerSession::finish()and H1HttpSession::reuse()preserve their pre-pipeliningResult<Option<Stream>>signatures and discard any captured pipelined prefix — non-adopters are unaffected.finish_reuse()/reuse_pipelined()to receive aReusedHttpConnection(opaque struct; fields private;into_parts()escape hatch).ReusedHttpStream::from_reused_connection()is the canonical converter that carries the pipelined prefix throughHttpPersistentSettingsinto the next session.Wire shapes covered
Both observed pipelining wire shapes are handled:
reuse_pipelined()hands the body-reader overread back as the pipelined prefix for the next session.read_body_or_idle()'s idle branch stashes non-zero idle reads onto the body reader's overread surface instead of raisingConnectError("Sent data after end of body").abort_on_close/half_closedare untouched so FIN handling is unchanged — the new branch is exclusive to the pipelining-bytes case.HttpPersistentSettingscarriespipelining_enabled+ the prefix across keep-alive reuses;read_request()consumes the prefix first before pulling more bytes from the stream.Positioning
Restores nginx parity for the traffic shape in #377 without inheriting nginx's upstream re-pipelining behavior. Each pipelined request gets its own independent upstream selection and its response is fully written before the next request begins — matching Envoy, AWS CloudFront, Google Classic LB, Akamai, and IIS.
Tests
cargo test -p pingora-core test_pipelining— 12 unit tests covering:Content-Length: 0)read_request()consumes a complete prefix without touching the streamread_request()falls through to the stream for partial prefixesConnectErrorwhen pipelining is disabledtake_body_overread+set_pipelined_prefixPlus an
examples/pipelining.rsthat opts in fromearly_request_filter. Minimal reproducer:Before this PR the same command returns a single
HTTP/1.1 200and the connection is marked un-reusable.Runtime cost for non-adopters
booland oneOption<BytesMut>onHttpSession(comparable scale to existing fields likeabort_on_close,proxy_tasks_enabled).self.pipelining_enabled && self.pipelined_idle_bytes_stashed(two bool reads) inside an already-async idle poll.HttpPersistentSettings, constructed once per keep-alive boundary.ProxyHttptrait.If a cargo feature gate is preferred (e.g. matching the
connection_filterpattern), happy to add one in a follow-up commit on this PR.