Repository navigation
Description
Context
Protocol::HTTP::Headers#add(key, value, trailer: true) decides whether a field may be a trailer by consulting POLICY[key].trailer?. This is the right place for that decision: it is a property of the field, independent of whether the Headers instance is currently sending trailers (@tail). protocol-http1 and async-http apply it to every received trailer field.
The policies that currently permit trailers are Date, Digest, ETag, ServerTiming — and Generic, the fallback for every header name without a specific policy. So any unknown field (x-api-key, x-forwarded-host, x-request-id, …) is accepted as a trailer, and because a field permitted as a trailer is treated as an ordinary field once received, it is indistinguishable to the application from a header that arrived in the head.
RFC 9110 §6.5.1: a recipient MUST NOT merge a trailer field into the header section unless the field's definition explicitly permits it; otherwise it MAY discard the field, store it separately, or forward it separately.
Question
Should Generic.trailer? return false?
- For: unknown fields have, by definition, no definition that "explicitly permits" trailer use, so accepting them is the non-conforming default. Custom authentication/routing headers are exactly the fields an application might check before reading the body and expect not to change afterwards.
- Against: it is a behaviour change for anyone sending or receiving custom trailers (e.g. gRPC-style
grpc-status/grpc-messageare trailers by design and have no policy here — they would need explicit entries). It also raises the question of what a recipient should do on rejection:addcurrently raisesInvalidTrailerError, which protocol-http1 surfaces as a failed message; the RFC's "MAY discard" suggests a softer option for the receive path.
Possible shapes
- Flip
Generic.trailer?tofalseand add explicit trailer-permitting policies for well-known trailer fields (e.g.grpc-status,grpc-message). - Keep
Genericpermissive but let consumers opt into strictness via a custom policy hash (already possible:Headers.new(fields, policy)), and document the default. - Separate "reject the message" from "discard the field" on receive, so a stricter default does not turn an unexpected custom trailer into a 400.
Related: socketry/protocol-http1#64 and socketry/async-http#259 were closed because marking received trailers with trailer! conflated this policy question with the sender-side @tail marker.
Activity