Skip to content

http2: request trailers split over HEADERS and CONTINUATION wait for their CONTINUATION, so the connection serves on - #500

Open
MDA2AV wants to merge 1 commit into
mainfrom
fix/http2-split-trailers
Open

MDA2AV wants to merge 1 commit into
mainfrom
fix/http2-split-trailers

Conversation

@MDA2AV

@MDA2AV MDA2AV commented Nov 5, 2026

Copy link
Copy Markdown
Owner

What was wrong

  • RFC 9113 6.2 lets a HEADERS frame with END_STREAM be followed by CONTINUATION frames that finish its header block. Trailers that do not fit one frame, or come from a client that splits its blocks, arrive that way.
  • ioxide.http2 dispatched such a buffered request at the HEADERS: HeadersDone was still true from the request's first header block, so TryComplete saw both flags set. The trailer block, still incomplete, was never decoded.
  • TryComplete also took the stream out of _streams, so the CONTINUATION found no stream and HandleContinuation ended the connection with GOAWAY(PROTOCOL_ERROR), and every other stream on it.

Details are in #497.

The fix

  • A HEADERS frame clears HeadersDone before it appends its block. For trailers that reopens the request's header block, so TryComplete waits for its END_HEADERS, and HandleContinuation decodes it and completes the request, as it already does for a request's first block. For a request's first block the flag was already clear.
  • One line, and no new state.
  • A request with a body reader is unchanged: its trailers are decoded aside through _discardBlock, which already follows CONTINUATION.

Behaviour

  • Split trailers merge into the request, as trailers in one frame already did, and the connection serves on. Before, the request was handed over without them and the connection ended with GOAWAY(PROTOCOL_ERROR).

API

None.

Tests

New in the Unit suite (Http2TrailerTests.cs), over its hand-driven peer:

  • h2 request: a buffered request's trailers split over HEADERS and CONTINUATION merge into it, and the connection serves on
    • A POST to /upload with 400 B of DATA, then x-checksum in HEADERS(END_STREAM) and x-digest in a CONTINUATION, then a GET to /next.
    • The handler has to get /upload with both trailers, /next has to be served, and the server must send no GOAWAY.

Against main's ioxide.http2 (the test only):

FAIL  h2 request: a buffered request's trailers split over HEADERS and CONTINUATION merge into it, and the connection serves on: the handler was handed: 1 '/upload' 400 x-checksum '' x-digest ''
145 passed, 1 failed, 0 skipped

With the fix: Unit: 146 passed.

Bench

None: the change is one store per HEADERS frame.

Fixes #497.

…their CONTINUATION, so the connection serves on

A buffered request whose trailers came as HEADERS(END_STREAM) without END_HEADERS was dispatched at
that HEADERS: HeadersDone was still true from the request's first header block. The trailer block
was never decoded, the stream left _streams, and the CONTINUATION that followed found no stream and
ended the connection with GOAWAY(PROTOCOL_ERROR) (#497).

A HEADERS frame now clears HeadersDone before it appends its block, so trailers reopen the request's
header block and the request waits for its END_HEADERS; HandleContinuation already decodes the block
and completes the request.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

http2: request trailers split over HEADERS and CONTINUATION end the connection with PROTOCOL_ERROR

1 participant