Chapter 4: HTTP/2 — The Transport Foundation

gRPC is built entirely on HTTP/2. You cannot understand gRPC's behavior — its multiplexing, streaming, flow control, or header handling — without understanding HTTP/2. This chapter gives you that foundation.

Why Not HTTP/1.1?

HTTP/1.1 (1997) has fundamental limitations that make it unsuitable for high-performance RPC:

HTTP/1.1 — One request at a time per connection: Connection 1: ──[REQ A]──────[RESP A]──[REQ B]──────[RESP B]──► Connection 2: ──[REQ C]──────[RESP C]──[REQ D]──────[RESP D]──► Connection 3: ──[REQ E]──────[RESP E]──────────────────────────► Problem: Need 6 connections for 6 concurrent requests. Each connection = TCP handshake + TLS handshake = ~100ms overhead. HTTP/2 — Many requests multiplexed on ONE connection: Connection 1: ──[A][C][E]──[B][D]──[A][C]──[E][B][D]──[A]──► ↑ ↑ ↑ ↑ ↑ Frames from different streams interleaved Result: One connection handles hundreds of concurrent requests.

HTTP/1.1 Problems

HTTP/2 Core Concepts

1. Binary Framing Layer

HTTP/2 is binary, not text. All communication is split into frames — small binary messages.

// HTTP/1.1 (text)
GET /users/123 HTTP/1.1\r\n
Host: api.example.com\r\n
Accept: application/json\r\n
\r\n

// HTTP/2 (binary frame)
┌─────────────────────────────────────┐
│ Length (24 bits) │ Type (8) │ Flags (8) │
├─────────────────────────────────────┤
│ R │      Stream Identifier (31)      │
├─────────────────────────────────────┤
│           Frame Payload ...          │
└─────────────────────────────────────┘

Frame types that matter for gRPC:

Frame TypePurposegRPC Usage
HEADERSHTTP headers (compressed)Method, path, metadata
DATARequest/response bodyProtobuf-encoded messages
RST_STREAMCancel a streamRPC cancellation
SETTINGSConnection configurationMax concurrent streams, window size
WINDOW_UPDATEFlow controlBackpressure in streaming
GOAWAYGraceful shutdownServer draining

2. Streams, Messages, and Frames

┌─────────────────── HTTP/2 Connection ───────────────────┐ │ │ │ Stream 1 (GET /users) Stream 3 (POST /orders) │ │ ┌─────────────────┐ ┌─────────────────┐ │ │ │ HEADERS frame │ │ HEADERS frame │ │ │ │ DATA frame │ │ DATA frame │ │ │ │ DATA frame │ │ DATA frame │ │ │ │ HEADERS frame │ │ HEADERS frame │ │ │ │ (trailers) │ │ (trailers) │ │ │ └─────────────────┘ └─────────────────┘ │ │ │ │ Stream 5 (streaming RPC) │ │ ┌─────────────────────────────────────────┐ │ │ │ HEADERS frame │ │ │ │ DATA frame (message 1) │ │ │ │ DATA frame (message 2) │ │ │ │ DATA frame (message 3) │ │ │ │ ...continues... │ │ │ │ HEADERS frame (trailers with status) │ │ │ └─────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────┘ Key: Streams are LOGICAL. Frames are PHYSICAL. Frames from different streams are interleaved on the wire.
💡 gRPC Mapping

Each gRPC call = one HTTP/2 stream. A streaming RPC with 1000 messages = one stream with 1000 DATA frames. Multiple concurrent RPCs = multiple streams on the same connection. This is why gRPC can handle thousands of concurrent RPCs on a single TCP connection.

3. Multiplexing

Multiple streams share one connection. Frames from different streams are interleaved:

// On the wire (one TCP connection):
[Stream 1 HEADERS] [Stream 3 HEADERS] [Stream 1 DATA] [Stream 3 DATA]
[Stream 5 HEADERS] [Stream 1 DATA] [Stream 3 DATA] [Stream 5 DATA] ...

// Each stream is reassembled independently by the receiver

This eliminates HTTP/1.1's head-of-line blocking at the application layer. (TCP-level HOL blocking still exists — that's what HTTP/3/QUIC solves.)

4. Header Compression (HPACK)

HTTP headers are repetitive. HPACK compresses them using:

Result: after the first request, subsequent requests send headers as just a few bytes (index numbers).

// First request: full headers sent (~200 bytes)
:method: POST
:path: /mypackage.MyService/MyMethod
:authority: server.example.com
content-type: application/grpc
te: trailers
grpc-timeout: 5S

// Second request to same service: mostly index references (~10 bytes)
// Only the path and timeout might differ

5. Flow Control

HTTP/2 has built-in flow control at two levels:

The receiver advertises a window size (how many bytes it's willing to accept). The sender must not exceed it. When the receiver processes data, it sends WINDOW_UPDATE frames to open the window.

Client Server │ │ │──── DATA (1000 bytes) ──────────────────►│ Window: 65535 → 64535 │──── DATA (1000 bytes) ──────────────────►│ Window: 64535 → 63535 │──── DATA (1000 bytes) ──────────────────►│ Window: 63535 → 62535 │ │ │◄─── WINDOW_UPDATE (+3000) ──────────────│ Window: 62535 → 65535 │ │ (server processed the data) │──── DATA (1000 bytes) ──────────────────►│ │ │
ℹ️ Why This Matters for gRPC

Flow control is how gRPC implements backpressure in streaming. If a server-streaming RPC produces data faster than the client can consume it, the client's window fills up, and the server automatically slows down. No application-level buffering needed.

6. Server Push (Not Used by gRPC)

HTTP/2 allows servers to push resources before the client requests them. gRPC does NOT use this feature. It's mainly for web browsers (pushing CSS/JS alongside HTML).

How gRPC Maps to HTTP/2

Every gRPC concept maps directly to HTTP/2:

gRPC ConceptHTTP/2 Mechanism
RPC callOne HTTP/2 stream (POST request)
Request messageDATA frame(s) with length-prefixed protobuf
Response messageDATA frame(s) with length-prefixed protobuf
Metadata (headers)HEADERS frame (initial)
Trailing metadataHEADERS frame (trailers) — contains grpc-status
CancellationRST_STREAM frame
Deadlinegrpc-timeout header
StreamingMultiple DATA frames on one stream
ChannelOne HTTP/2 connection (or pool)

A Complete gRPC Call on the Wire

Client Server │ │ │─── HEADERS ───────────────────────────────────►│ │ :method: POST │ │ :path: /mypackage.UserService/GetUser │ │ :authority: server:50051 │ │ content-type: application/grpc │ │ te: trailers │ │ grpc-timeout: 5S │ │ │ │─── DATA ──────────────────────────────────────►│ │ [compressed flag: 0] [length: 7] │ │ [protobuf bytes: GetUserRequest{id=123}] │ │ │ │ (END_STREAM flag set — no more client data) │ │ │ │◄── HEADERS ────────────────────────────────────│ │ :status: 200 │ │ content-type: application/grpc │ │ │ │◄── DATA ───────────────────────────────────────│ │ [compressed flag: 0] [length: 28] │ │ [protobuf bytes: User{id=123,name="Alice"}] │ │ │ │◄── HEADERS (trailers) ─────────────────────────│ │ grpc-status: 0 │ │ grpc-message: (empty — success) │ │ │
⚠️ Critical Detail: Trailers

gRPC uses HTTP/2 trailers (headers sent AFTER the body) to convey the RPC status code. This is unusual — most HTTP APIs put the status in the initial response headers. gRPC can't do that because in streaming, the server doesn't know if the RPC will succeed until it's done sending all messages. This is why gRPC requires HTTP/2 — HTTP/1.1 doesn't support trailers reliably.

Connection Management

gRPC maintains long-lived HTTP/2 connections:

HTTP/2 Limitations (and HTTP/3)

HTTP/2 still runs on TCP, which has one remaining problem: TCP-level head-of-line blocking. If a TCP packet is lost, ALL streams on that connection stall until retransmission.

HTTP/3 (QUIC) solves this by running on UDP with per-stream loss recovery. gRPC is experimenting with HTTP/3 support, but HTTP/2 remains the standard.

Key Takeaways