HTTP Versions

VersionYearMajor improvement
HTTP/0.91991One-line GET, HTML only
HTTP/1.01996Headers, status codes, content types
HTTP/1.11997Persistent connections, Host header, chunked
HTTP/22015Binary framing, multiplexing, HPACK header compression
HTTP/32022Runs over QUIC (UDP), no head-of-line blocking

HTTP 2.0 Improvements

Framing

HTTP/1.1

HTTP/1.1 uses text based parsing - find CRLF split on : handle whitespace guess where body starts. This is error-prone and wasted cycles.

GET /index.html HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0
Accept: text/html
Accept-Encoding: gzip, deflate
Cookie: session=abc123

~140 bytes of ASCII and you resend nearly all of it on the next request too

HTTP/2.0

HTTP/2.0 wraps the same request in one binary HEADERS frame (shown here without HPACK indexing, so every field is a literal):

00 00 AB 01 05 00 00 00 01                // frame header
00 07 :method          03 GET
00 07 :scheme          05 https
00 05 :path            0B /index.html
00 0A :authority       0B example.com
00 0A user-agent       0B Mozilla/5.0
00 06 accept           09 text/html
00 0F accept-encoding  0D gzip, deflate
00 06 cookie           12 session=abc123
Improvements in this format
Parsing fields

Problem

Parsing is slow, we have look for CRLF and whitespace.

Cookie: session=abc123

Solution

Provide hex encoded length of field

00 06 cookie           12 session=abc123

Although this looks more bloated it allows for much faster parsing.

Multiplexing

Problem

One connection, one request at a time. You send a request you wait for the full reponse then send the next. If you first response is slow everything behind it was, browser worked aorund this by opening ~6 parallel TCP connections per site.

Solution

Every frame is tagged with a stream ID

               // stream id
00 00 AB 01 05 00 00 00 01   // frame header

Request for /index.html is stream 1, request for style.css is stream 3, etc. frames can be interleaved on the same connection.
Simply, stream ID lets different requests share one pipe without getting mixed up.

Explicity message boundaries

Problem

In HTTP/1.1 where a message ends is inferred from headers, there were two ways:

Content-Length: 42 # read 42 bytes    or ...
Transfer-Encoding: chunked # read until a zero length chunk

Attackers could use these two to smuggle a second request inside the first.

Solution

HTTP/2 removes guessing: the frame states the exact byte length and the END_STREAM flag marks the last frame of the message.

DATA stream 1 length=6 END_STREAM  // frame says 6, flag says done
There are more… but this is the core so far need HPACK!