Skip to content

Add getStructured/setStructured to Headers - #1943

Open
jasnell wants to merge 5 commits into
whatwg:mainfrom
jasnell:jasnell/headers-structured
Open

Add getStructured/setStructured to Headers#1943
jasnell wants to merge 5 commits 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.)


Preview | Diff

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 Outdated
@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 Outdated
Comment thread fetch.bs Outdated
@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.

@reschke

reschke commented Aug 7, 2026

Copy link
Copy Markdown

"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.

Yes. What I was trying to say is that the API does not necessarily have to be attached to the headers object. It could be independant, but yes, it should be in FETCH.

@jasnell

jasnell commented Aug 7, 2026

Copy link
Copy Markdown
Author

Yes. What I was trying to say is that the API does not necessarily have to be attached to the headers object. It could be independant, but yes, it should be in FETCH.

Hmm... you're not wrong but I think having it on Headers is the most ergonomic for the typical use cases.

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