HTTP/2 Is Now RFC 7540: What It Changes for Front End Performance
Last Thursday, May 14, HTTP/2 was published as RFC 7540, with its header compression format, HPACK, published alongside it as RFC 7541. Both are Proposed Standards from the IETF’s HTTP working group. If you build websites, the RFC numbers aren’t the interesting part. A lot of what we’ve learned to do for speed over the years, like sprite sheets, concatenated bundles and assets spread across extra hostnames, exists to work around how HTTP/1.1 uses TCP. HTTP/2 goes after exactly that.
The spec doesn’t change what HTTP means. Its abstract says HTTP’s existing semantics remain unchanged and calls HTTP/2 an alternative to the HTTP/1.1 message syntax, not a replacement that obsoletes it. What changes is how messages travel over the wire, which your application code shouldn’t notice but your build process and server setup might.
The problem it goes after
The RFC’s introduction is blunt. HTTP/1.0 allowed one outstanding request at a time per TCP connection, and HTTP/1.1’s pipelining only partly helped and still suffers from head of line blocking, where one slow response holds up everything behind it. So clients open multiple connections to get concurrency, and repetitive, verbose headers quickly fill the initial TCP congestion window on each new one.
The working group’s HTTP/2 FAQ ties this straight to front end habits. It says browsers open between four and eight connections per origin with HTTP/1, which can mean more than thirty connections for a page that pulls from several origins. It also says the industry got to a place where spriting, data: inlining, domain sharding and concatenation count as best practice, and calls those hacks a sign of problems in the protocol itself.
Frames and streams on one connection
HTTP/2 is binary. Everything on the connection is a frame, and every frame starts with the same fixed 9 octet header: a 24 bit payload length, an 8 bit type, 8 bits of flags, a reserved bit, and a 31 bit stream identifier. Each request and response exchange gets its own stream, which the spec defines as an independent, bidirectional sequence of frames. HEADERS frames carry headers, DATA frames carry bodies, and since every frame is labeled with its stream, frames from different streams can be interleaved on the same connection. Streams opened by the client get odd numbers and streams opened by the server get even ones. A frame payload can’t exceed 16,384 octets unless the receiver allows bigger frames, so a large response goes out as many DATA frames that take turns with everything else.
That’s the multiplexing. In the spec’s words, streams are largely independent of each other, so a blocked or stalled request or response doesn’t prevent progress on other streams. And because one connection is enough, section 9.1 says clients SHOULD NOT open more than one HTTP/2 connection to a given host and port.
Each side announces how many concurrent streams it will allow in a SETTINGS frame. SETTINGS_MAX_CONCURRENT_STREAMS starts with no limit, and the spec recommends not setting it below 100, which is a very different world from a handful of connections per host.
HPACK and why header bloat hurts less
With HTTP/1.1, every request repeats nearly the same headers as plain text. RFC 7541 explains that SPDY originally compressed headers with DEFLATE, which worked very well but opened the door to the CRIME attack. HPACK is a new compressor designed just for header fields.
It works with two tables. The static table is a fixed list of 61 common header fields, so :method: GET is index 2 and :scheme: https is index 7. The dynamic table starts empty and fills as the encoder chooses to add header fields it has sent, which means a header that repeats on a later request can go out as a small index instead of the full text. Literal strings can also be squeezed with a static Huffman code. One compression context and one decompression context are used for the whole connection, and the dynamic table’s size is capped by SETTINGS_HEADER_TABLE_SIZE, which starts at 4,096 octets.
Header names must be lowercase in HTTP/2, and the old request line becomes pseudo header fields like :method and :path. The Cookie header may also be split into separate fields holding one or more cookie pairs each, so one changed cookie doesn’t spoil compression for the rest.
HPACK isn’t a force field, though. Section 7 of RFC 7541 says it mitigates attacks modeled on CRIME without completely preventing them: an attacker is reduced to guessing entire header values, so high entropy values are hard to recover but low entropy ones remain vulnerable. Encoders can send sensitive fields like Cookie or Authorization as never indexed literals, which intermediaries must not re-encode in an indexed form.
Server push and prioritization
Server push lets the server answer a request the browser hasn’t made yet. If the page needs a stylesheet, the server sends a PUSH_PROMISE frame on the original request’s stream, carrying the request it is attributing to the client and a new stream number, then sends the response on that new stream. The spec says the promise SHOULD go out before any frames that reference the file, so the browser doesn’t request it in the meantime.
There are guardrails. Pushed requests must be cacheable and safe and can’t have a request body, and the server must be authoritative for what it pushes, so a server with a certificate only for example.com can’t push a response for www.example.org. The client stays in control as well. It can disable push with SETTINGS_ENABLE_PUSH set to 0, or cancel a particular push with RST_STREAM. That’s the catch for front end work: nothing in the protocol tells the server what’s already in the browser cache, so pushing files a returning visitor already has spends their bandwidth for nothing. The spec’s overview is honest about it, describing push as trading some network usage against a potential latency gain.
Prioritization is the RFC’s way of letting more important requests finish first. A client can mark a stream as depending on another stream and give it a weight between 1 and 256, with 16 as the default. Siblings should share resources in proportion to their weights, so in the spec’s example a stream with weight 4 ideally gets a third of what its weight 12 sibling gets. But the RFC is explicit that priority is only a suggestion that can’t force a peer to process streams in any order, so how much it helps depends on browsers sending sensible priorities and servers honoring them.
Getting to h2 means TLS in practice
The spec defines two ways in. For http:// URLs, a client can send an HTTP/1.1 request with Upgrade: h2c and switch protocols if the server answers with a 101. For https:// URLs, the client uses TLS with the Application Layer Protocol Negotiation extension, defined last July in RFC 7301. The client lists the protocols it supports in its ClientHello, the server picks one in its ServerHello, and HTTP/2 over TLS goes by the identifier h2. RFC 7301 points out that this happens inside the handshake without adding network round trips.
HTTP/2 itself doesn’t require encryption. The FAQ says the working group had no consensus to require it, and that some implementations have said they’ll only support HTTP/2 over encrypted connections. Browsers are the ones that matter. Microsoft’s post announcing HTTP/2 in the Windows 10 Technical Preview says the preview versions of both IE and IIS support HTTP/2 only over HTTPS. Mozilla’s HTTP/2 wiki page says Firefox will only implement HTTP/2 over TLS, and Firefox 36, released February 24, added support for the full protocol. Google’s Hello HTTP/2, Goodbye SPDY post from February said HTTP/2 would gradually roll out in Chrome 40, with SPDY and the older NPN extension going away in early 2016 in favor of ALPN. It doesn’t spell out a TLS only policy, but ALPN is a TLS extension, and the post strongly encourages server developers to move to HTTP/2 and ALPN. If you want browsers to use HTTP/2, plan on HTTPS.
The spec also raises the bar for that TLS. Section 9.2 requires TLS 1.2 or later and Server Name Indication, with TLS compression and renegotiation turned off. It adds a long black list of cipher suites that deployments SHOULD NOT use, which even includes the suite TLS 1.2 itself makes mandatory, and requires support for TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 with the P-256 curve. If a black listed suite gets negotiated, the other side may end the connection with an INADEQUATE_SECURITY error. Servers also need a TLS library with ALPN, which for OpenSSL means 1.0.2, released in January. With a 1.0.2 build locally, you can ask a server what it picks:
openssl s_client -connect example.com:443 -servername example.com -alpn h2
If the server selects HTTP/2 you’ll see ALPN protocol: h2 in the output, and No ALPN negotiated if it didn’t select anything.
What servers you can actually run today
This is where the RFC gets ahead of the software most of us deploy. nginx’s newest releases, 1.8.0 stable from April 21 and 1.9.0 mainline from April 28, include the experimental SPDY module (not built by default, and speaking SPDY 3.1 since 1.5.10), and their changelog has nothing for HTTP/2. Apache httpd’s latest release is 2.4.12, from January 29, and its change list doesn’t mention HTTP/2 either. So if you run stock nginx or Apache, turning on HTTP/2 isn’t a config change yet.
There are options for experimenting. nghttp2 tagged version 1.0.0 on May 15, implementing RFC 7540 and RFC 7541, and it includes nghttpx, a reverse proxy that accepts HTTP/2, SPDY and HTTP/1.1 and can act as a TLS terminator in front of an existing web server. The H2O web server’s 1.2.0 release from April lists support for the final version of HTTP/2, with dependency and weight based prioritization and server push. And IIS in the Windows 10 Technical Preview has HTTP/2 over HTTPS for testing.
Which HTTP/1.1 habits to revisit
Domain sharding is the clearest one. Extra hostnames existed to squeeze out more parallel connections, which HTTP/2 doesn’t need, and each one can mean another connection with its own TCP and TLS handshakes, its own HPACK tables starting from empty, and priorities that only apply within their own connection. Section 9.1.1 does leave a door open: a connection can be reused for different hostnames when the server is authoritative for them, which for https means the host resolves to the same IP address and the certificate is valid for it. A *.example.com certificate could let a.example.com and b.example.com share one connection, so shards on one IP under one certificate at least give clients the chance to fold them together. The same section warns that reuse can misdirect requests in some setups, which is what the new 421 Misdirected Request status code is for.
Concatenation and spriting are less clear cut. They exist to cut the number of requests, and multiplexing plus HPACK makes each request much cheaper. Splitting files back up helps caching, since changing one module then invalidates one small file instead of the whole bundle. Inlining small files as data: URIs is in a similar spot, and server push covers some of the same ground while letting the pushed file be cached on its own. But I wouldn’t rip out the bundler this week. Not every browser speaks HTTP/2 yet (Microsoft’s HTTP/2 support for IE is in the Windows 10 Technical Preview), and the FAQ expects HTTP/1.x to stay in use for quite some time. Everyone on the old protocol still pays the old costs, and your stock nginx or Apache isn’t serving HTTP/2 anyway.
My advice is to measure instead of assuming. The RFCs don’t promise any particular speedup, and results will depend on your pages, the browser’s priorities and the server doing the multiplexing. Put an HTTP/2 capable proxy in front of a staging copy of your site, compare waterfalls with and without sharding and bundling, and let the numbers decide.
The takeaway
HTTP/2 keeps HTTP’s meaning and replaces its plumbing: binary frames, many streams on one connection, compressed headers, optional push and advisory priorities. The biggest shift for front end developers is mental, because requests stop being the scarce resource so much of our build tooling assumes. For now it comes with HTTPS as a practical requirement and a short list of servers. Get your TLS config into shape for section 9.2, try HTTP/2 behind a proxy, and treat sharding as the first HTTP/1.1 hack to retire.