HTTP/2 header compression now matches Chrome on the wire.
The header list going out was already correct, so nothing that inspects
headers could see any of this. The HPACK instructions carrying that list were
not Chrome's, in four ways, three of which fired on the very first request.
- Cookie crumbs were emitted in the never-indexed representation, which no
browser uses. Nothing in that form is ever stored, so a large cookie jar was
re-sent in full on every request rather than being referenced in a single
byte per crumb after the first. On a session carrying a large jar that was an
~880 byte header block on every request where a browser settles near 35, and
it persisted for the life of the connection.
- The authority pseudo-header had the same problem for the same reason.
- The path and method pseudo-headers referenced the wrong static table entry
whenever their value was not one of the two the table happens to carry, which
covers any path other than / and any method other than GET or POST.
All four verified byte for byte against captures of the browser itself rather
than against a specification. A connection that gets reused now settles into
the same small, fully-referenced header block a browser produces and holds
there, measured flat over sixty consecutive requests.
Also in this release: a per-hop redirect callback, 307 and 308 no longer lose
the request body through the root session, and a data race on the pooled
connection use counter.
Two things to know. Anyone recording the exact bytes of an outgoing header
block will see them change. And compression state is now shared across requests
on a connection the way a browser shares it, which is what makes the small
blocks possible; the never-indexed list is still configurable per profile, it
simply no longer defaults to being populated.
TLS is untouched. JA4 and the HTTP/2 settings fingerprint are unchanged.
Full notes in CHANGELOG.md.