HTTP/2 (RFC 9113) and HTTP/3 (RFC 9114) are the modern versions of the Hypertext Transfer Protocol. HTTP/2 brought binary framing, multiplexing, and header compression to address the limitations of HTTP/1.1. HTTP/3 replaces TCP with QUIC (a UDP-based transport) to eliminate TCP-level head-of-line Blocking and enable features like connection migration.
Both protocols maintain the same HTTP semantics (methods, status codes, headers, URIs) as HTTP/1.1. The change is in how the bytes are formatted on the wire, not in what they mean.
HTTP/1.1 is a text protocol. HTTP/2 is a binary protocol. Every HTTP/2 communication is performed Over a single TCP connection, and all communication is split into smaller messages and frames.
A stream is a bidirectional flow of frames within a single HTTP/2 connection. Multiple streams are Multiplexed over a single TCP connection. Each stream has a unique 31-bit identifier. Client-initiated streams use odd numbers; server-initiated streams use even numbers.
Client Server
| |
|--- HEADERS [stream 1] ----------------------->| GET /style.css
|--- HEADERS [stream 3] ----------------------->| GET /script.js
|--- HEADERS [stream 5] ----------------------->| GET /image.png
| |
|<-- HEADERS [stream 1] ------------------------| 200 OK
|<-- DATA [stream 1] ---------------------------| CSS content
|<-- HEADERS [stream 3] ------------------------| 200 OK
|<-- DATA [stream 3] ---------------------------| JS content
|<-- HEADERS [stream 5] ------------------------| 200 OK
|<-- DATA [stream 5] ---------------------------| Image data
No ordering requirement exists between streams. The server can send stream 5”s data before stream 1’s, and the client reassembles each stream independently.
HTTP/2 implements flow control at the stream level and the connection level. Each side advertises a Window size (initially 65,535 bytes, configurable via SETTINGS). The sender must not send more data Than the receiver’s window allows. The receiver sends WINDOW_UPDATE frames to increase the window.
This prevents a fast sender from overwhelming a slow receiver. Without flow control, a server Sending a large response could exhaust the client’s receive buffer.
Client Server
| |
|<-- DATA [stream 1, 16KB] --------------------| (client window decreases)
|<-- DATA [stream 1, 16KB] --------------------| (client window decreases)
|--- WINDOW_UPDATE [stream 1, +32KB] ---------->| (client grants more window)
|<-- DATA [stream 1, 32KB] --------------------| (server can send more)
HTTP/1.1 sends headers as plain text, repeated on every request. For a typical page load with 30-100 Requests, headers like User-Agent``Accept``CookieAnd Accept-Encoding are sent dozens of Times, each time consuming hundreds of bytes.
HPACK (RFC 7541) compresses headers using three techniques:
Dynamic table: Header fields sent in previous messages are cached and referenced by index number
Huffman coding: Literal header values are encoded using Huffman coding, which reduces common ASCII strings by ~20-30%
First request:
:method: GET
:path: /api/users
accept: application/json
user-agent: Mozilla/5.0...
Second request (to same server):
:method: GET <- static table index 2
:path: /api/users <- dynamic table index (from first request)
accept: application/json <- dynamic table index
user-agent: Mozilla/5.0... <- dynamic table index
Second request on wire: ~20 bytes (3 index references) vs ~200 bytes (full headers)
HPACK is designed to be resistant to CRIME/BREACH-style compression oracle attacks. It does not Compress across different origins (the dynamic table is per-origin), and it does not compress cookie Values in a way that leaks information.
HTTP/2 allows the server to proactively send resources to the client before the client requests Them. The server sends a PUSH_PROMISE frame that includes the request headers for the pushed Resource. The client can reject pushed resources with a RST_STREAM frame.
Client Server
| |
|--- HEADERS [stream 1]: GET /index.html ------>|
| |
|<-- HEADERS [stream 1]: 200 OK ----------------|
|<-- DATA [stream 1]: HTML body -----------------|
|<-- PUSH_PROMISE [stream 2]: GET /style.css -->| Server pushes CSS
|<-- PUSH_PROMISE [stream 4]: GET /app.js ---->| Server pushes JS
|<-- HEADERS [stream 2]: 200 OK ----------------|
|<-- DATA [stream 2]: CSS body ------------------|
|<-- HEADERS [stream 4]: 200 OK ----------------|
|<-- DATA [stream 4]: JS body -------------------|
GOAWAY does not immediately terminate the connection. Existing streams can continue to complete. The Sender should continue processing streams with IDs less than or equal to last_stream_id. Only new Streams with IDs greater than last_stream_id are rejected.
Sent by the server to announce that it will push a resource. Contains the request headers for the Pushed resource and the stream ID that will be used for the push.