§5.2
Chapter 05 · HTTP3 Frame Processing and Request Response Semantics

§5.2Header Encoding with QPACK and Its Operational Constraints

RFC 9204RFC 9114

Learning objective. Understand what a HEADERS frame's payload actually contains (the QPACK field-line representations and the field-section prefix) and the two settings that govern the compression-versus-blocking trade-off, without yet diving into the dynamic-table machinery of Chapter 6.

Why not just HPACK? #

HTTP/2 compressed headers with HPACK, whose dynamic table required every header block to be processed strictly in order: each block could depend on table updates in the block before it. Over TCP's single ordered stream that was fine. Over QUIC's independent streams it would be a disaster: a HEADERS block on stream 8 could not be decoded until table updates carried on stream 4 arrived, reintroducing exactly the head-of-line blocking QUIC removed (§4.4). QPACK is HPACK redesigned for out-of-order delivery: it moves dynamic -table updates onto dedicated streams and adds an explicit dependency mechanism so decoding can proceed — or deliberately, boundedly, block — rather than stall unpredictably ([RFC 9204 §2]).

What a HEADERS payload contains #

The payload of a HEADERS frame is a QPACK encoded field section: a short prefix followed by one representation per header field.

0byte offsetRequired Insert Count0 = static onlyvarintBasesign + deltavarintIndexedfield1·T·index(6)8 b · 1 BLiteral, namereference01·N·T + value0..*
Fixed-width fieldVariable-length integer (varint)Length / count prefixVariable-length value
Fig. 5.2-1A QPACK encoded field section (the HEADERS payload)RFC 9204 §4.5

Every field resolves against one of two tables ([RFC 9204 §3.1]):

  • the static table: 99 predefined entries for common fields (:method: GET, content-type: application/json, …), always available (constants.md §14);
  • the dynamic table: a connection-scoped table the encoder fills at runtime with fields this connection actually uses, built via the encoder stream (Chapter 6).

A field is then encoded as one of a few representations, distinguished by their leading bits (constants.md §14):

Leading bits Representation Meaning
1 Indexed name and value from a table entry — one byte for a common header
01 Literal, name reference name from a table entry, value spelled out
001 Literal, literal name both name and value spelled out

The prefix, a Required Insert Count and a Base, declares which dynamic-table entries the section depends on; a Required Insert Count of 0 means the section uses only the static table and can always be decoded immediately.

The two settings that govern it #

Because the dynamic table is what creates both the compression win and the blocking risk, two SETTINGS values bound it (constants.md §11):

  • SETTINGS_QPACK_MAX_TABLE_CAPACITY (0x01, default 0). The dynamic-table size the decoder is willing to keep. Zero disables the dynamic table entirely, forcing static-only encoding: no blocking is possible, at the cost of worse compression.
  • SETTINGS_QPACK_BLOCKED_STREAMS (0x07, default 0). How many request streams the decoder will allow to be blocked waiting for dynamic-table entries at once. Zero means the encoder must never emit a field section that could block.

Together they let a deployment dial the trade-off: raise the capacity and blocked-stream budget for better compression on stable connections, or keep them low (even zero) to guarantee non-blocking decode at the cost of larger headers.

Note

This is the residual head-of-line blocking of §4.4, named precisely: it lives in QPACK decompression, not in the QUIC transport, and it is opt-in and bounded. An encoder that references a just-inserted dynamic entry on a stream whose update has not yet arrived creates a blocked stream; SETTINGS_QPACK_BLOCKED_STREAMS caps how many such streams can exist, and setting it (or the table capacity) to zero removes the possibility.

In practice

editorial Start conservative. A dynamic table capacity of zero (static-only) is a perfectly reasonable default: HTTP's common headers are already in the static table, so the compression loss is modest and you get guaranteed non-blocking decode with almost no encoder complexity. Turn the dynamic table on when you have measured that header overhead matters (long custom headers repeated across many requests, typically), and even then keep SETTINGS_QPACK_BLOCKED_STREAMS small. Chapter 6 (§6.4) returns to tuning these for high-latency paths, where the update round trip changes the math.

Takeaways #

A HEADERS payload is a QPACK field section: a dependency-declaring prefix plus one representation per field, each an index into the static or dynamic table or a literal. The dynamic table buys compression at the risk of decompression-time blocking, bounded by SETTINGS_QPACK_MAX_TABLE_CAPACITY and SETTINGS_QPACK_BLOCKED_STREAMS. With the message's two payload kinds (body bytes and QPACK headers) understood, §5.3 looks at how requests and responses actually flow, and interleave, across many concurrent streams.