Skip to content

Drop trailers on HTTP/1.0, and never use the Trailer field to choose framing. - #50

Merged
samuel-williams-shopify merged 7 commits into
mainfrom
http10-trailers
Nov 3, 2026
Merged

samuel-williams-shopify merged 7 commits into
mainfrom
http10-trailers

Conversation

@samuel-williams-shopify

@samuel-williams-shopify samuel-williams-shopify commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

HTTP/1.0 now drops trailers instead of refusing messages whose bodies may have them. An HTTP/1.1 request of known length is sent with Content-Length and drops its trailers too, while responses stay chunked whenever their body may have trailers. A Trailer field is now treated only as a hint and no longer affects framing. Incoming bodies report may_have_trailers() only when they are chunked.

HTTP/1.0 has no chunked framing, so it cannot carry trailers, and HTTP semantics (RFC 9110 §6.5.1) let them be discarded. Until now, the HTTP/1.0 request writer refused any body whose may_have_trailers() was true, with "trailers require HTTP/1.1 chunked framing". Every BodyReader reports true, so a streamed request could never be sent over HTTP/1.0. A response to an HTTP/1.0 client failed when its body actually produced trailers.

Outgoing messages

  • HTTP/1.0 requests: a body of known length is sent with Content-Length and its trailers are dropped, through both write_request and imperative begin_request/write_frame.
  • HTTP/1.0 request bodies of unknown length: still refused, because an HTTP/1.0 request cannot be close-delimited. The error now gives that reason: "HTTP/1.0 request requires a body length".
  • Responses to HTTP/1.0 clients: sent with their length when it is known, or close-delimited otherwise, and their trailers are dropped. A persistent connection stays reusable. A 205 whose body may have trailers now declares Content-Length: 0.
  • HTTP/1.1 requests of known length: sent with Content-Length even when the body may have trailers, which are dropped after validation, instead of being chunked. This covers write_request with a body such as BodyReader::with_length, and begin_request with a content_length. Request trailers are rare, a client MUST NOT send Transfer-Encoding unless it knows the server handles HTTP/1.1 (RFC 9112 §6.1), and length-requiring servers answer 411. Bodyless GETs and known-length uploads therefore keep main's wire format, and an imperative request with content_length and a Trailer field, which main chunked, now completes with its trailer dropped instead of failing. HTTP/1.1 requests of unknown length are still chunked and keep their trailers.
  • HTTP/1.1 responses: unchanged. A body that may have trailers is chunked even with a known length; buffered bodies and received Content-Length or close-delimited bodies report no trailers and keep Content-Length; HEAD, 204 and 304 take no framing.
  • Dropped trailers are still validated, so trailer fields are validated the same way however they are framed.
  • Tunnels: tunnel output still refuses trailer frames.

The shared BodyEncoder gains discard_trailers, which the request writer enables for every non-chunked request and the response writer enables for HTTP/1.0 output that isn't a tunnel. write_body now takes the encoder.

The Trailer field

Nobody knows ahead of time whether a message will have trailers. The Trailer field is only a SHOULD hint (gRPC, for example, omits it), so it no longer decides anything:

  • On HTTP/1.0 it no longer causes a refusal.
  • On HTTP/1.1 it no longer selects chunked framing for a body that has promised no trailers.

For responses, may_have_trailers() alone decides framing; for requests, a known length does.

Incoming bodies

On the receiving side, only chunked bodies report may_have_trailers(). That rules out HTTP/1.0 bodies, Content-Length bodies and close-delimited bodies. This already held, and new codec tests now pin it for both the server and the client, along with keeping received trailers out of the header fields. HTTP/1.0 messages with Transfer-Encoding were already refused on read, and the lane is not reused.

design/http1-trailers.md records this model, and design/fixtures.md, design/implementation.md, design/proposal.md and the base-http1 readme are updated to match.

Tests

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… chunked bodies may have trailers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the `base-http1` readme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…th versions, and that `may_have_trailers` selects chunked framing.
…ing its trailers, so imperative uploads keep their wire format.
… test that an HTTP/1.0 tunnel refuses trailers.
@samuel-williams-shopify samuel-williams-shopify changed the title Drop trailers on HTTP/1.0, and treat a Trailer field only as a hint. Drop trailers on HTTP/1.0, and never use the Trailer field to choose framing. Nov 3, 2026
@samuel-williams-shopify
samuel-williams-shopify merged commit 6739202 into main Nov 3, 2026
5 checks passed
@samuel-williams-shopify
samuel-williams-shopify deleted the http10-trailers branch November 3, 2026 03:05
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.

1 participant