HTTP Versions
| Version | Year | Major improvement |
|---|---|---|
| HTTP/0.9 | 1991 | One-line GET, HTML only |
| HTTP/1.0 | 1996 | Headers, status codes, content types |
| HTTP/1.1 | 1997 | Persistent connections, Host header, chunked |
| HTTP/2 | 2015 | Binary framing, multiplexing, HPACK header compression |
| HTTP/3 | 2022 | Runs 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=abc123Improvements 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=abc123Although 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 headerRequest for
/index.htmlis stream 1, request forstyle.cssis 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 chunkAttackers 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_STREAMflag marks the last frame of the message.DATA stream 1 length=6 END_STREAM // frame says 6, flag says done