Skip to content

Handle HTTP/2 1xx interim responses from the origin#13351

Draft
bryancall wants to merge 3 commits into
apache:masterfrom
bryancall:http2-origin-1xx-interim
Draft

Handle HTTP/2 1xx interim responses from the origin#13351
bryancall wants to merge 3 commits into
apache:masterfrom
bryancall:http2-origin-1xx-interim

Conversation

@bryancall

Copy link
Copy Markdown
Contributor

Problem

ATS did not handle 1xx interim responses (for example 103 Early Hints) received on an outbound HTTP/2 connection to an origin. The interim response headers were decoded into the same buffer as the following final response, producing a duplicate :status pseudo-header that failed validation. ATS then reset the stream and reported it to the client as "Server closed connection while reading response header", returning a 502 even though the origin had not closed the connection.

This reproduces against any origin that sends 103 Early Hints before the final response over HTTP/2 (for example a Vercel-hosted origin on a browser navigation request).

Fix

  • After a header block is decoded on an outbound stream, detect a 1xx status, discard the interim headers, and wait for the final response. This is applied to both the HEADERS and CONTINUATION decode paths and handles multiple consecutive interim responses. The interim response is not currently forwarded to the client; the final response is delivered normally.
  • Allow CONTINUATION frames on an outbound stream in the half-closed (local) state, which is the state in which an origin response is received. Previously a response whose header block spanned a CONTINUATION frame was rejected with "continuation bad state". The per-minute CONTINUATION flood limit still applies.

Tests

Adds tests/gold_tests/h2/http2_origin_interim_response.test.py with a small hand-framed HTTP/2 origin (Proxy Verifier cannot emit interim responses) covering a single 103, multiple interim responses, 100-continue, and a CONTINUATION-split interim before the final 200. The test was confirmed failing before this change and passing after; the existing early_hints and h2origin gold tests still pass.

Fixes #13334

ATS did not handle 1xx interim responses (for example 103 Early Hints)
received on an outbound HTTP/2 connection to an origin. The interim
response headers were decoded into the same buffer as the following final
response, producing a duplicate :status pseudo-header that failed
validation. ATS then reset the stream and reported it to the client as
"Server closed connection while reading response header", returning a 502
even though the origin had not closed the connection.

After a header block is decoded on an outbound stream, detect a 1xx
status, discard the interim headers, and wait for the final response.
This is applied to both the HEADERS and CONTINUATION decode paths and
handles multiple consecutive interim responses. The interim response is
not currently forwarded to the client; the final response is delivered
normally.

Also allow CONTINUATION frames on an outbound stream in the half-closed
(local) state, which is the state in which an origin response is
received. Previously a response whose header block spanned a CONTINUATION
frame was rejected with "continuation bad state". The per-minute
CONTINUATION flood limit still applies.

Add a gold test that drives an HTTP/2 origin emitting 103, multiple
interim responses, 100-continue, and a CONTINUATION-split interim before
the final 200, verifying the client receives the 200 in each case.

Fixes apache#13334
@bryancall bryancall self-assigned this Jun 30, 2026
@bryancall bryancall added the Bug label Jun 30, 2026
@bryancall bryancall added this to the 11.0.0 milestone Jun 30, 2026
@bryancall bryancall requested a review from Copilot July 1, 2026 16:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes ATS origin-side HTTP/2 handling of informational (1xx) interim responses (e.g., 103 Early Hints) so they don’t get merged into the final response header block and trigger invalid duplicate :status parsing/validation failures. It also relaxes CONTINUATION-frame state handling for outbound (origin) streams so response headers split across CONTINUATION frames are accepted.

Changes:

  • Detect and discard decoded outbound interim (1xx) response headers on both HEADERS and CONTINUATION decode paths, then wait for the final response.
  • Allow CONTINUATION frames on outbound streams in HALF_CLOSED_LOCAL state (typical for receiving an origin response).
  • Add a gold test with a hand-framed HTTP/2 TLS origin to exercise single/multiple interim responses, 100 Continue, and CONTINUATION-split interim responses.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
src/proxy/http2/Http2ConnectionState.cc Adds interim-response detection/discard and permits CONTINUATION in HALF_CLOSED_LOCAL for outbound/origin streams.
tests/gold_tests/h2/http2_origin_interim_response.test.py New gold test driver that spins up per-mode origin processes and verifies ATS returns the final 200.
tests/gold_tests/h2/h2_interim_origin.py New minimal HTTP/2 TLS origin helper that hand-frames interim and final responses for test coverage.

Comment thread src/proxy/http2/Http2ConnectionState.cc
Comment thread src/proxy/http2/Http2ConnectionState.cc
Comment thread tests/gold_tests/h2/http2_origin_interim_response.test.py
Comment thread tests/gold_tests/h2/h2_interim_origin.py Outdated
Comment thread tests/gold_tests/h2/h2_interim_origin.py
Free and reset the encoded header-block buffer when discarding an interim
1xx response, so it is not leaked when the next HEADERS frame allocates a
new buffer. Both the HEADERS and CONTINUATION decode paths now use a
shared discard helper.

In the gold test, keep the origin helper processes alive across all runs
(StillRunningAfter), and correct the test origin's HTTP/2 SETTINGS
handshake: do not send an unsolicited SETTINGS ACK, and ACK the client's
SETTINGS frame when it is received.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread tests/gold_tests/h2/h2_interim_origin.py Outdated
The per-connection thread captured the accept-loop socket via a closure,
so concurrent connections could race and handle the wrong socket. Pass the
socket as a thread argument instead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"Server closed connection.." error may be due to HTTP/2 parser problem

2 participants