Skip to content

Add getStructured/setStructured to Headers - #1943

Open
jasnell wants to merge 1 commit into
whatwg:mainfrom
jasnell:jasnell/headers-structured
Open

Add getStructured/setStructured to Headers#1943
jasnell wants to merge 1 commit into
whatwg:mainfrom
jasnell:jasnell/headers-structured

Conversation

@jasnell

@jasnell jasnell commented Jul 21, 2026

Copy link
Copy Markdown

Adds new APIs to the Headers class for getting/setting structured header fields.

Structured fields are defined in RFC 8941. Newer HTTP header definitions build on it. Fetch's handling of all header values as strings works but loses some of the utility. This commit adds new getStructured/setStructured APIS to Headers for getting/setting header field values as structured fields. The existing get/set/append/etc are left untouched. Header iteration is left untouched. It remains possible to get all fields as strings.

There are currently ~36 standard headers that use structured header fields:

Dictionary

Header Spec Description
Priority RFC 9218 HTTP response prioritization (urgency, incremental delivery)
Signature RFC 9421 HTTP message signatures
Signature-Input RFC 9421 Metadata for message signatures (covered components, key ID, etc.)
Accept-Signature RFC 9421 §5.1 Requests that recipient apply a signature
Content-Digest RFC 9530 Integrity digest over HTTP message content
Repr-Digest RFC 9530 Integrity digest over HTTP representation
Want-Content-Digest RFC 9530 Requests Content-Digest with algorithm preferences
Want-Repr-Digest RFC 9530 Requests Repr-Digest with algorithm preferences
CDN-Cache-Control RFC 9213 Targeted cache directives for CDN caches
Use-As-Dictionary RFC 9842 Marks a response as a compression dictionary

List

Header Spec Description
Cache-Status RFC 9211 Per-cache handling report (hit, fwd, ttl, etc.)
Proxy-Status RFC 9209 Per-intermediary handling report with error details
Accept-CH RFC 8942 Advertises server support for Client Hints
Client-Cert-Chain RFC 9440 Client certificate chain from TLS-terminating proxy
Accept-Query RFC 10008 Accepted media types for HTTP QUERY body
Cache-Groups RFC 9875 Associates cached responses with named groups
Cache-Group-Invalidation RFC 9875 Invalidates all responses in named cache groups

Item

Header Spec Description
Client-Cert RFC 9440 End-entity client certificate (Byte Sequence)
Capsule-Protocol RFC 9297 Enables the Capsule Protocol on an HTTP stream (Boolean)
Deprecation RFC 9745 Signals resource deprecation (Date)
Available-Dictionary RFC 9842 Client has a compression dictionary available
Dictionary-ID RFC 9842 Assigns a stable ID to a compression dictionary response
Concealed-Auth-Export RFC 9729 Exported keying material for concealed HTTP auth
Cross-Origin-Embedder-Policy HTML Standard Controls cross-origin resource loading policy
Cross-Origin-Embedder-Policy-Report-Only HTML Standard COEP in report-only mode
Cross-Origin-Opener-Policy HTML Standard Controls browsing context group sharing
Cross-Origin-Opener-Policy-Report-Only HTML Standard COOP in report-only mode
Origin-Agent-Cluster HTML Standard Requests origin-keyed agent cluster (Boolean)
Sec-Fetch-Dest Fetch Metadata Request destination type (Token)
Sec-Fetch-Mode Fetch Metadata Request mode (Token)
Sec-Fetch-Site Fetch Metadata Request-vs-target origin relationship (Token)
Sec-Fetch-User Fetch Metadata User activation (Boolean)
Sec-Purpose Fetch Standard Request purpose, e.g. prefetch (Token)

Examples

Reading the Priority header (Dictionary)

// Priority: u=0, i
const p = response.headers.getStructured("Priority", "dictionary");
const urgency = p?.get("u")?.value ?? 3;     // 0
const incremental = p?.get("i")?.value ?? false; // true

Compared with strings:

const raw = response.headers.get("Priority"); // "u=0, i"
// ... now what? Split on comma? Parse key=value? Handle quoting?
// Every app rolls its own parser and gets edge cases wrong.

Reading Cache-Status (List)

// Cache-Status: ReverseProxy;hit, CDN;fwd=miss;stored;ttl=3600
const cs = response.headers.getStructured("Cache-Status", "list");
for (const entry of cs) {
  console.log(entry.value);                    // "ReverseProxy", "CDN"
  console.log(entry.params.get("hit"));        // true, undefined
  console.log(entry.params.get("fwd"));        // undefined, "miss"
  console.log(entry.params.get("ttl"));        // undefined, 3600
}

Reading Content-Digest (Dictionary with Byte Sequences)

// Content-Digest: sha-256=:X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=:
const digest = response.headers.getStructured("Content-Digest", "dictionary");
const hash = digest?.get("sha-256")?.value;    // Uint8Array

Reading Sec-Purpose (Item)

// Sec-Purpose: prefetch
const purpose = request.headers.getStructured("Sec-Purpose", "item");
if (purpose?.value === "prefetch") {
  // serve a lighter response
}

Writing Priority (Dictionary — plain object form)

// Sets: Priority: u=0, i
request.headers.setStructured("Priority", "dictionary", {
  u: { value: 0 },
  i: { value: true }
});

Writing Cache-Status (List with parameters)

// Sets: Cache-Status: MyProxy;hit;ttl=7200
response.headers.setStructured("Cache-Status", "list", [
  { value: "MyProxy", params: { hit: true, ttl: 7200 } }
]);

Writing Content-Digest (Dictionary with Byte Sequence)

const body = await response.arrayBuffer();
const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", body));

// Sets: Content-Digest: sha-256=:base64encodedhash=:
response.headers.setStructured("Content-Digest", "dictionary", {
  "sha-256": { value: hash }
});

Graceful fallback when unsupported

// getStructured returns null if the header is absent, malformed,
// or if the implementation doesn't support structured field parsing.
const priority = request.headers.getStructured("Priority", "dictionary");
const urgency = priority?.get("u")?.value ?? 3;  // always works, defaults to 3

Token vs String serialization

// Strings matching token syntax serialize as unquoted tokens:
headers.setStructured("Example", "item", { value: "foo" });
// Sets: Example: foo

// Strings that don't match token syntax serialize as quoted strings:
headers.setStructured("Example", "item", { value: "hello world" });
// Sets: Example: "hello world"

All input forms for dictionaries and parameters (HeadersInit pattern)

// Plain object (most ergonomic)
headers.setStructured("Priority", "dictionary", {
  u: { value: 3 },
  i: { value: true }
});

// Map (preserves insertion order explicitly)
headers.setStructured("Priority", "dictionary", new Map([
  ["u", { value: 3 }],
  ["i", { value: true }]
]));

// Sequence of pairs
headers.setStructured("Priority", "dictionary", [
  ["u", { value: 3 }],
  ["i", { value: true }]
]);

// Parameters accept the same forms:
headers.setStructured("Cache-Status", "list", [
  { value: "cdn", params: { hit: true, ttl: 3600 } },       // plain object
  { value: "origin", params: new Map([["fwd", "miss"]]) },   // Map
  { value: "edge", params: [["stored", true]] }              // sequence
]);

  • At least two implementers are interested (and none opposed):
  • 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 21, 2026, 12:38 AM 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": "8762:15",
        "messageType": "fatal",
        "text": "Saw a [[ opening a biblio or section autolink, but couldn't parse the following contents. If you didn't intend this to be a biblio autolink, escape the initial [ as &bs[;"
    },
    {
        "lineNum": "7994:12",
        "messageType": "warning",
        "text": "The var 'result' (in global scope) is only used once.\nIf this is not a typo, please add an ignore='' attribute to the <var>."
    },
    {
        "lineNum": "8291:16",
        "messageType": "warning",
        "text": "The var 'bareItem' (in algorithm 'convert a structured field item to a JavaScript object') is only used once.\nIf this is not a typo, please add an ignore='' attribute to the <var>."
    },
    {
        "lineNum": "8003:3",
        "messageType": "lint",
        "text": "RFC2119 keyword in non-normative section (use: might, can, has to, or override with <span class=allow-2119>):  must be one of \""
    },
    {
        "lineNum": "8885:1",
        "messageType": "lint",
        "text": "RFC2119 keyword in non-normative section (use: might, can, has to, or override with <span class=allow-2119>): Parsing structured fields and converting the result to\nJavaScript objects is entirely optional. Implementations that do not\nsupport structured field parsing are fully compliant with this\nspecification by having "
    },
    {
        "lineNum": "8885:1",
        "messageType": "lint",
        "text": "RFC2119 keyword in non-normative section (use: might, can, has to, or override with <span class=allow-2119>):  method itself is required.\n\n"
    },
    {
        "lineNum": "8929:1",
        "messageType": "lint",
        "text": "RFC2119 keyword in non-normative section (use: might, can, has to, or override with <span class=allow-2119>): Serializing structured fields is entirely optional.\nImplementations that do not support structured field serialization are\nfully compliant with this specification. The minimum conformance\nrequirement is that "
    },
    {
        "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.

Adds new APIs to the Headers class for getting/setting
structured header fields.
@panva

panva commented Jul 26, 2026

Copy link
Copy Markdown

@jasnell are you building up towards RFC 9421: HTTP Message Signatures support? I'd be happy to help.

@jasnell

jasnell commented Jul 26, 2026

Copy link
Copy Markdown
Author

@panva ... That's definitely one of the items on the agenda, yes.

@mnot

mnot commented Jul 28, 2026

Copy link
Copy Markdown
Member

Would it be useful to have a spec of a canonical mapping of SF to JSON?

@reschke

reschke commented Jul 28, 2026

Copy link
Copy Markdown

... like the one used in the test suite?

@jasnell

jasnell commented Jul 28, 2026

Copy link
Copy Markdown
Author

Would it be useful to have a spec of a canonical mapping of SF to JSON?

I would think so, yes

@reschke

reschke commented Aug 2, 2026

Copy link
Copy Markdown

Any reason why this needs to be baked in into headers? Woun't something based on strings be simpler?

@jasnell

jasnell commented Aug 2, 2026

Copy link
Copy Markdown
Author

"Something based on strings" is just what Headers already provides. Using an additional parser/serializer is obviously possible but adds an additional dependency which is what this is aiming to eliminate.

Comment thread fetch.bs
Comment on lines +8017 to +8020
constrained profile of string rather than a separate type. When serializing
via {{Headers/setStructured()}}, strings that conform to
<a>structured field token</a> syntax are serialized as tokens; all other
strings are serialized as quoted structured field strings.

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.

This string->token implicit conversion is not going to work for some uses of the API. Is it possible to override the type? For instance, by adding a value as {value: "v", sfType: "string"} or something like that? Otherwise, there are cases where a SF-defined value will expect a string and get a token that it will choke one.

@martinthomson

Copy link
Copy Markdown
Contributor

What is the thinking about this sort of thing?

const cc = response.headers.getStructured("Cache-Control", "dictionary");

Presumably, this just runs the string through the identified SF parser, which might work out fine (or not), caveat emptor and all that jazz.

Comment thread fetch.bs
Comment on lines +8440 to +8445
<li><p>If <var>jsValue</var> is an
<a href="https://tc39.es/ecma262/#sec-isinteger">integral number</a>,
return a <a>structured field integer</a> with <var>jsValue</var>'s value.

<li><p>Otherwise, return a <a>structured field decimal</a> with
<var>jsValue</var>'s value.

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.

What do you do with values outside of the range that SF requires support for? Would 6e110 be presented with all 110 digits?

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.

No, in that case the correct response would be to return null. The fallback would be to just use the existing get method.

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.

Is that because 6e110 doesn't fit in a double? I'm thinking of those cases where the range of permissible values in a JS Number is outside the range of permissible SF integers. What then? Translate, and let the application enjoy the (possible) surprise (possible because there is nothing preventing an implementation from accepting more than the minimal range).

You need this to be explicit in the algorithm.

Comment thread fetch.bs
Comment on lines +8929 to +8935
<p class=note>Serializing structured fields is entirely optional.
Implementations that do not support structured field serialization are
fully compliant with this specification. The minimum conformance
requirement is that {{Headers/setStructured()}} does not throw for valid
inputs &mdash; an implementation that silently ignores the call is compliant.
Whether to actually serialize and set the header is an implementation
decision.

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.

This seems like it could be an interoperability nightmare. I think that you need to support SF serialization if you add this.

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.

I've been going back and forth on this, to be honest. But, you're right.

@jasnell

jasnell commented Aug 4, 2026

Copy link
Copy Markdown
Author

Presumably, this just runs the string through the identified SF parser, which might work out fine (or not), caveat emptor and all that jazz

Yes. If it can be parsed as the specified type, then it will be. Otherwise null is returned.

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.

5 participants