Skip to content

Add .trailers to Response/Request - #1940

Open
jasnell wants to merge 1 commit into
whatwg:mainfrom
jasnell:jasnell/trailers
Open

Add .trailers to Response/Request#1940
jasnell wants to merge 1 commit into
whatwg:mainfrom
jasnell:jasnell/trailers

Conversation

@jasnell

@jasnell jasnell commented Jul 11, 2026

Copy link
Copy Markdown

Adds Promise<Headers> trailers properties to Request and Response

When sending... trailers resolves to an empty, immutable Headers since we're not dealing with sending trailers in this change.

When receiving... trailers resolves to an immutable, populated/filtered Headers resolved after the body is completed. This means the trailers promise will not resolve if the body is not consumed.

const trailers = await response.trailers;

The minimal conformance requirement here is the presence of the .trailers property. An implementation which chooses not to actually support trailers would need only return a resolved promise with an immutable empty Headers. Nothing in this should be interpreted as requiring any implementation to actually implement trailers support.

Refs: #981


  • At least two implementers are interested (and none opposed):
    • Node.js
    • Cloudflare Workers
  • Tests are written and can be reviewed and commented upon at:
  • Implementation bugs are filed:
    • Chromium: …
    • Gecko: …
    • WebKit: …
    • Deno (not for CORS changes): …
  • MDN issue is filed: …
  • The top of this comment includes a clear commit message to use.

(See WHATWG Working Mode: Changes for more details.)


💥 Error: 422 Unprocessable Entity 💥

PR Preview failed to build. (Last tried on Jul 11, 2026, 5:40 PM UTC).

More

PR Preview relies on a number of web services to run. There seems to be an issue with the following one:

🚨 Spec Generator - Spec Generator is the web service used to build bikeshed/ReSpec specs

🔗 Related URL

Error output:

[
    {
        "lineNum": "1675:17",
        "messageType": "link",
        "text": "Multiple possible 'dfn' local refs for 'trailer state'.\nRandomly chose one of them; other instances might get a different random choice."
    },
    {
        "lineNum": "1679:51",
        "messageType": "link",
        "text": "Multiple possible 'dfn' local refs for 'trailer state'.\nRandomly chose one of them; other instances might get a different random choice."
    },
    {
        "lineNum": "1699:48",
        "messageType": "link",
        "text": "Multiple possible 'dfn' local refs for 'trailer state'.\nRandomly chose one of them; other instances might get a different random choice."
    },
    {
        "lineNum": "2652:4",
        "messageType": "link",
        "text": "Multiple possible 'dfn' local refs for 'trailer state'.\nRandomly chose one of them; other instances might get a different random choice."
    },
    {
        "lineNum": "2652:63",
        "messageType": "link",
        "text": "Multiple possible 'dfn' local refs for 'trailer state'.\nRandomly chose one of them; other instances might get a different random choice."
    },
    {
        "lineNum": null,
        "messageType": "failure",
        "text": "Did not generate, due to errors exceeding the allowed error level."
    }
]

This seems to be an issue with the Spec Generator service. PR Preview doesn't manage this service and so has no control over it. If you've identified an issue with it, you can report the issue to the maintainers of Spec Generator directly. Please be courteous. Thank you!

If you don't have enough information above to solve the error by yourself or if the issue doesn't seem related to Spec Generator, you can file an issue with PR Preview.

Comment thread fetch.bs
<p>A <dfn export>trailer state</dfn> is a <a for=/>struct</a> used to represent
HTTP trailer fields received after a response body
([[HTTP]], <a href="https://httpwg.org/specs/rfc9110.html#trailers">Section 6.5</a>).
It has:

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The struct here is really only to deal with cloning... wasn't sure if it would be better to go more specific / concrete or to describe things in a more generic / hand wavy way... went with more specific.

Adds `Promise<Headers>` `trailers` properties to `Request` and
`Response`

When sending... `trailers` resolves to an empty, immutable
`Headers` since we're not dealing with sending trailers in
this change.

When receiving... `trailers` resolves to an immutable,
populated/filtered `Headers` resolved after the body
is completed. This means the trailers promise will not
resolve if the body is not consumed.
@jasnell
jasnell force-pushed the jasnell/trailers branch from aca5d2b to 3cbc607 Compare July 11, 2026 17:08
@jasnell jasnell mentioned this pull request Jul 11, 2026
5 tasks
passcod added a commit to passcod/faith that referenced this pull request Aug 5, 2026
These names started as internal identifiers and ended up in the README, where
"controllable-h1" and "encoding" mean nothing to a reader.

The rows: controllable-h1 and controllable-h2 are node-h1 and node-h2. It is Node's
own server, and SCRIPTABLE already carries the it-can-misbehave-on-request idea that
"controllable" was there for.

The dimensions say what they check: framing becomes "chunked bodies", encoding
becomes "gzip", conditional becomes "conditional GET", alpn becomes "protocol
negotiation", keepalive becomes "connection reuse", header-limits becomes
"oversized headers", goaway becomes "h2 GOAWAY", altsvc becomes "HTTP/3 upgrade",
and h3 becomes "HTTP/3".

One name each, used in the table, the TAP output and matrix.json, rather than a
display label beside an identifier -- two names for one thing is how they drift.
Files and exports follow, so the module you open matches the row you read.

Also rewords the trailers dimension's note about consuming the body first. It
described faith's implementation, down to the state it polls; what a reader needs is
that trailers arrive after the body ends, which the fetch spec's trailers proposal
requires (whatwg/fetch#1940). The implementation detail was also about to be wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
passcod added a commit to passcod/faith that referenced this pull request Aug 5, 2026
…means

Closes #31, but not the way that issue proposed. Waiting for the body first is
correct: the fetch spec's trailers proposal
(whatwg/fetch#1940) says the promise resolves after the body
is completed, and so "will not resolve if the body is not consumed". So the ordering
stays; what was wrong is how the waiting was done and that nothing said so.

The wait was a `yield_now` loop over a lock, for a wait the spec makes unbounded by
design. Half a second of waiting cost half a second of CPU, kept Node's event loop
alive, and survived a test framework's own timeout -- in the conformance harness this
presented as a job that hung with no diagnosis. It is now a watch channel: trailers
that already arrived return without yielding, a waiter parks until the body ends, and
the future can be cancelled while it waits. Measured at 1ms of CPU across 500ms of
waiting, against ~500ms before.

discard() now settles the question as "none" instead of leaving it pending forever.
On a multiplexed connection it cancels the stream before trailers could arrive, and
draining an HTTP/1 body there bypasses the stream that would have collected them --
either way no trailers are coming, and a caller who discarded the body and then
awaited trailers used to wait forever.

The ordering is documented where a caller would look: the getter, wrapper.d.ts and
the README's Response.trailers section, each pointing at the spec so it reads as the
specified behaviour rather than a quirk. Nothing said it before, which is what made
the wedge easy to hit.

The test pins both halves: that the promise stays pending until the body is consumed,
that it costs no CPU while pending, that a promise held from before the read resolves
when the body ends, and that a discarded body answers null.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant