Loading content...
In 2015, the web underwent one of its most significant transformations since the introduction of HTTP/1.1 in 1997. After nearly two decades of incremental improvements and workarounds, HTTP/2 emerged with a radical architectural change: the replacement of HTTP's text-based protocol with a binary framing layer.
This wasn't merely a technical optimization—it was a fundamental reimagining of how web browsers and servers communicate. The binary framing layer is the foundation upon which every other HTTP/2 improvement is built: multiplexing, header compression, server push, and stream prioritization all depend on this architectural shift.
To truly understand HTTP/2, you must first understand binary framing. Every decision, every performance gain, and every implementation detail traces back to this core innovation.
By the end of this page, you will understand: (1) Why HTTP/1.1's text-based protocol became a fundamental bottleneck, (2) How binary framing transforms HTTP messages into structured frames, (3) The complete anatomy of HTTP/2 frames including headers, types, and flags, (4) How the binary layer enables all other HTTP/2 features, and (5) The practical implications for web performance and security.
To appreciate binary framing, we must first understand why HTTP/1.1's text-based approach became problematic. HTTP/1.1 was designed in an era when web pages contained a handful of resources, connections were expensive, and simplicity was paramount.
The Text-Based Protocol:
HTTP/1.1 messages are human-readable text. A typical request looks like:
GET /index.html HTTP/1.1\r\n
Host: example.com\r\n
User-Agent: Mozilla/5.0...\r\n
Accept: text/html\r\n
Accept-Language: en-US\r\n
Connection: keep-alive\r\n
\r\n
This text-based format was intentional. Early protocol designers valued debuggability—you could literally read HTTP traffic with tools like telnet or netcat. But this simplicity came with severe costs that weren't apparent until web complexity exploded.
Modern web pages average 70+ resources (scripts, stylesheets, images, fonts, API calls). With HTTP/1.1's connection limits and head-of-line blocking, loading these resources efficiently requires complex workarounds: connection pooling, domain sharding, resource inlining, sprite sheets, and concatenation. Each workaround adds development complexity and often makes caching less effective.
The Parsing Tax:
Consider what happens when a server processes an HTTP/1.1 request:
Each step involves string operations, buffer management, and careful handling of edge cases. At scale, this text parsing becomes a measurable CPU cost. More importantly, the variable-length nature of text makes it impossible to divide a stream into independent, parallel-processable units—which is exactly what binary framing solves.
HTTP/2's binary framing layer solves HTTP/1.1's fundamental problems by introducing a structured binary format between the application (HTTP semantics) and the transport (TCP). This layer doesn't change what HTTP means—GET, POST, headers, and bodies remain conceptually identical—but completely transforms how messages are encoded and transmitted.
The Key Insight:
Instead of sending HTTP messages as monolithic text blobs, HTTP/2 breaks them into small frames with fixed headers and well-defined boundaries. This simple change enables massive improvements:
HTTP/2's binary format doesn't mean you can't debug it. Tools like Wireshark, Chrome DevTools, and nghttp2 decode frames into readable representations. The 'binary' designation refers to the wire format—the structured byte layout that enables efficient parsing and multiplexing. HTTP semantics (methods, headers, status codes) remain identical to HTTP/1.1.
The Layering Architecture:
HTTP/2 introduces a clean separation of concerns:
┌─────────────────────────────────────┐
│ HTTP Semantics (Application) │
│ GET, POST, Headers, Status Codes │
├─────────────────────────────────────┤
│ Binary Framing Layer (HTTP/2) │
│ Frames, Streams, Flow Control │
├─────────────────────────────────────┤
│ Transport Layer (TCP/TLS) │
│ Reliable, Ordered Byte Stream │
└─────────────────────────────────────┘
This layering means that application developers continue using familiar HTTP concepts—request methods, headers, response codes—while the binary framing layer handles efficient transmission transparently. The transition from HTTP/1.1 to HTTP/2 requires no changes to application logic, only infrastructure updates.
Every HTTP/2 frame follows a precise binary structure. Understanding this structure is essential for grasping how HTTP/2 achieves its performance characteristics.
The Fixed Frame Header:
Every HTTP/2 frame begins with a 9-byte header:
+-----------------------------------------------+
| Length (24 bits) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0 to 2^24-1 bytes) |
+---------------------------------------------------------------+
This fixed structure is the key to HTTP/2's parsing efficiency. Upon receiving data, a parser knows immediately:
| Field | Size | Description | Constraints |
|---|---|---|---|
| Length | 24 bits | Length of the frame payload (excluding the 9-byte header) | Max 16,384 bytes by default; can be increased via SETTINGS |
| Type | 8 bits | Defines the format and semantics of the frame | 10 defined types (0x0-0x9); unknown types must be ignored |
| Flags | 8 bits | Boolean flags specific to the frame type | Undefined flags must be ignored; preserves extensibility |
| R (Reserved) | 1 bit | Reserved for future use | Must be 0; receivers ignore this bit |
| Stream Identifier | 31 bits | Unique identifier for the stream this frame belongs to | 0 for connection-level frames; odd=client-initiated, even=server-initiated |
Why This Structure Matters:
The fixed 9-byte header provides several critical properties:
Predictable Parsing — No scanning for delimiters. Read 9 bytes, extract fields with bit operations, read Length payload bytes. Done.
Stream Demultiplexing — The Stream Identifier allows frames from different requests/responses to be interleaved. A single TCP connection can carry thousands of concurrent streams.
Efficient Buffering — Knowing the exact payload length before reading allows optimal buffer allocation—no over-allocation or reallocation during parsing.
Protocol Extensibility — Unknown frame types and flags must be ignored, allowing the protocol to evolve without breaking existing implementations.
Connection vs. Stream Frames — Stream ID 0 indicates connection-level frames (SETTINGS, PING, GOAWAY), while non-zero IDs indicate stream-specific frames.
By default, frame payloads are limited to 16,384 bytes (16KB). This limit balances efficiency (larger frames reduce header overhead) against interleaving fairness (smaller frames allow finer-grained multiplexing). The SETTINGS frame allows endpoints to negotiate larger limits, up to 16MB (2^24-1 bytes), though most implementations use conservative values.
HTTP/2 defines ten frame types, each serving a specific purpose in the protocol. Understanding these types reveals how HTTP semantics map to the binary layer.
Core Frame Types:
| Type | ID | Purpose | Key Details |
|---|---|---|---|
| DATA | 0x0 | Carries request/response body data | Subject to flow control; can be split across multiple frames |
| HEADERS | 0x1 | Carries HTTP headers (request/response) | Compressed with HPACK; may include priority information |
| PRIORITY | 0x2 | Specifies stream priority | Deprecated in favor of HEADERS priority; rarely used now |
| RST_STREAM | 0x3 | Immediately terminates a stream | Contains error code; enables fast error recovery |
| SETTINGS | 0x4 | Configuration parameters for connection | Must be acknowledged; sent in both directions |
| PUSH_PROMISE | 0x5 | Initiates server push | Contains promised request headers; reserves stream ID |
| PING | 0x6 | Connection liveness and RTT measurement | 8-byte opaque data; must be echoed back |
| GOAWAY | 0x7 | Graceful connection shutdown | Contains last processed stream ID and error code |
| WINDOW_UPDATE | 0x8 | Flow control window adjustment | Increments available window for stream or connection |
| CONTINUATION | 0x9 | Continues a HEADERS or PUSH_PROMISE | Used when headers exceed frame size limit |
DATA Frames (0x0):
DATA frames carry the actual content being transferred—the response body for a webpage, the file being downloaded, or the JSON payload of an API response.
+---------------+
|Pad Length? (8)|
+---------------+-----------------------------------------------+
| Data (*) |
+---------------------------------------------------------------+
| Padding (*) |
+---------------------------------------------------------------+
Key characteristics:
HEADERS Frames (0x1):
HEADERS frames carry HTTP headers—both request headers (method, path, authority) and response headers (status code, content-type). Headers are compressed using HPACK.
+---------------+
|Pad Length? (8)|
+-+-------------+-----------------------------------------------+
|E| Stream Dependency? (31) |
+-+-------------+-----------------------------------------------+
| Weight? (8) |
+-+-------------+-----------------------------------------------+
| Header Block Fragment (*) |
+---------------------------------------------------------------+
| Padding (*) |
+---------------------------------------------------------------+
Critical flags:
When headers are too large to fit in a single frame (after HPACK compression), CONTINUATION frames carry the remaining header block fragments. Between a HEADERS frame without END_HEADERS and the final CONTINUATION with END_HEADERS, no other frames for that stream may be interleaved—this ensures the header block can be decompressed correctly.
The Stream Identifier in each frame header enables HTTP/2's most revolutionary feature: multiplexing. A stream represents a single request-response exchange, and multiple streams share a single TCP connection.
Stream Identifier Rules:
| Initiator | Stream IDs | Example |
|---|---|---|
| Client | Odd numbers | 1, 3, 5, 7, ... |
| Server | Even numbers | 2, 4, 6, 8, ... |
| Connection-level | 0 | SETTINGS, PING, GOAWAY |
This simple numbering scheme eliminates coordination—clients and servers can independently create streams without risk of ID collision.
Stream Lifecycle:
Every stream progresses through a well-defined state machine:
+--------+
send PP | | recv PP
,--------+ idle +--------.___
/ | | \
v +--------+ v
+----------+ | +----------+
| | | send H / | |
,------+ reserved | | recv H | reserved +-----.
| | (local) | | | (remote) | |
| +----------+ v +----------+ |
| | +--------+ | |
| | recv ES | | send ES | |
| send H | ,-------+ open +-------. | recv H |
| | / | | \ | |
| v v +--------+ v v |
| +----------+ | +----------+ |
| | half | | | half | |
| | closed | | send R / | closed | |
| | (remote) | | recv R | (local) | |
| +----------+ | +----------+ |
| | | | |
| | send ES / | recv ES / | |
| | send R / v send R / | |
| | recv R +--------+ recv R | |
| send R / `----------->| |<-----------' send R /|
| recv R | closed | recv R |
`----------------------->| |<----------------------'
+--------+
Key states:
The SETTINGS_MAX_CONCURRENT_STREAMS parameter limits active streams per connection (default: unlimited, recommended: 100). Stream identifiers are never reused—once a stream closes, its ID is permanently retired. This prevents confusion when delayed frames arrive for closed streams. After exhausting 2^31-1 stream IDs, a new connection must be established.
The binary framing layer provides dramatic parsing efficiency improvements over HTTP/1.1's text format. These improvements compound at scale, making HTTP/2 significantly more efficient for high-traffic servers.
Parsing Comparison:
Performance Implications:
The binary format enables several optimizations:
Zero-Copy Parsing: Frame boundaries are known immediately—no scanning required. Parsers can work directly on the receive buffer without copying data.
Parallelizable Processing: Once frames are demultiplexed by stream ID, they can be processed in parallel. HTTP/1.1's sequential nature prevents this.
Reduced Syscalls: Knowing exact frame sizes means fewer read() calls with precise buffer sizes, reducing kernel/user-space transitions.
Hardware Acceleration: Fixed-length binary formats are amenable to SIMD instructions and hardware offloading—important for high-performance servers.
Predictable Memory: Fixed header sizes and known payload lengths allow optimal memory allocation strategies, reducing GC pressure in managed runtimes.
1234567891011121314151617181920212223242526272829
// HTTP/2 frame header parsing - remarkably simpletypedef struct { uint32_t length; // 24 bits uint8_t type; uint8_t flags; uint32_t stream_id; // 31 bits (R bit ignored)} http2_frame_header_t; int parse_frame_header(const uint8_t *buf, http2_frame_header_t *hdr) { // Extract 24-bit length (big-endian) hdr->length = (buf[0] << 16) | (buf[1] << 8) | buf[2]; // Single byte fields hdr->type = buf[3]; hdr->flags = buf[4]; // Extract 31-bit stream ID (ignore R bit) hdr->stream_id = ((buf[5] & 0x7F) << 24) | (buf[6] << 16) | (buf[7] << 8) | buf[8]; // Validate length against settings if (hdr->length > max_frame_size) { return FRAME_SIZE_ERROR; } return SUCCESS;}HTTP/2 uses big-endian (network byte order) for all multi-byte fields. This is consistent with TCP/IP conventions and ensures interoperability across different CPU architectures. Modern compilers provide efficient byte-swapping intrinsics (like __builtin_bswap32) that compile to single CPU instructions on little-endian machines.
Every HTTP/2 connection begins with a connection preface—a specific sequence that confirms both endpoints support HTTP/2 and establishes initial settings. This handshake ensures protocol compatibility before any frames are exchanged.
Client Connection Preface:
The client sends:
PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
The magic string serves multiple purposes:
123456789
// Client connection preface (hex representation)0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a// Decoded: "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" // Followed immediately by SETTINGS frame:0x000000 // Length: 0 (empty settings = use defaults)0x04 // Type: SETTINGS (4)0x00 // Flags: none0x00000000 // Stream ID: 0 (connection-level)Server Connection Preface:
The server responds with its own SETTINGS frame. This SETTINGS frame may include:
Both sides must acknowledge received SETTINGS with a SETTINGS frame with the ACK flag set. Until acknowledgment, the sender must respect the receiver's previous settings.
| Setting | ID | Default | Range | Purpose |
|---|---|---|---|---|
| HEADER_TABLE_SIZE | 0x1 | 4096 | 0 to 2^32-1 | HPACK dynamic table size |
| ENABLE_PUSH | 0x2 | 1 (enabled) | 0 or 1 | Allow server push |
| MAX_CONCURRENT_STREAMS | 0x3 | unlimited | 0 to 2^31-1 | Maximum active streams |
| INITIAL_WINDOW_SIZE | 0x4 | 65535 | 0 to 2^31-1 | Flow control window |
| MAX_FRAME_SIZE | 0x5 | 16384 | 16384 to 2^24-1 | Maximum frame payload |
| MAX_HEADER_LIST_SIZE | 0x6 | unlimited | 0 to 2^32-1 | Maximum header size |
In practice, HTTP/2 is negotiated during TLS handshake using ALPN (Application-Layer Protocol Negotiation). The client advertises 'h2' in its ClientHello, and the server confirms support. This allows protocol selection before any HTTP data is exchanged, avoiding extra round trips. Clear-text HTTP/2 (h2c) uses HTTP Upgrade instead, but is rarely deployed given security concerns.
Binary framing isn't just a performance optimization—it's the foundation that enables every other HTTP/2 feature. Let's trace how each major feature depends on the frame structure:
1. Multiplexing (enabled by Stream Identifiers)
The Stream Identifier in every frame header allows frames from different requests to interleave on a single connection. Without binary framing, demultiplexing would require complex parsing of text message boundaries—exactly the problem HTTP/1.1 pipelining couldn't solve.
2. Header Compression (enabled by HEADERS/CONTINUATION frames)
HPACK compression depends on a well-defined header block boundary. HEADERS and CONTINUATION frames provide this: the decompressor knows exactly which bytes constitute the compressed header block, enabling stateful compression across requests.
3. Server Push (enabled by PUSH_PROMISE frames)
Server push requires reserving stream IDs and sending request headers for resources the client hasn't requested. The PUSH_PROMISE frame type provides this exactly—the binary format ensures unambiguous communication of the pushed request.
4. Flow Control (enabled by WINDOW_UPDATE frames)
Precise flow control requires communicating exact byte counts. WINDOW_UPDATE frames carry 31-bit window increments—impossible to express efficiently in text format without ambiguity.
5. Stream Prioritization (enabled by PRIORITY information)
Priority dependencies and weights are numeric values that influence frame scheduling. The binary encoding in HEADERS and PRIORITY frames communicates these precisely.
Every HTTP/2 optimization—from multiplexing to header compression—is made possible by the binary framing layer. This is why understanding frames is essential: you cannot truly understand HTTP/2's performance characteristics without understanding how data is structured at the wire level. The following pages in this module will build on this foundation, exploring each feature in detail.
Binary framing affects security in several important ways:
Attack Surface Reduction:
HTTP/1.1's text format enabled numerous parsing-based attacks:
HTTP/2's binary format constrains these attacks:
While binary framing reduces some attacks, HTTP/2's complexity introduces new concerns: HPACK compression creates state-based attacks, flow control can be abused for DoS, and implementation bugs in complex state machines have led to vulnerabilities. HTTP/2 servers must implement strict frame validation and resource limits to prevent abuse.
Practical Requirement: TLS
While HTTP/2 technically supports clear-text operation (h2c), all major browsers require TLS for HTTP/2. This effectively makes HTTP/2 always encrypted in practice, providing:
The binary format works harmoniously with TLS—both are designed for efficient processing of structured byte streams. The protocol negotiation via ALPN integrates cleanly, establishing HTTP/2 during the TLS handshake without additional round trips.
Binary framing represents a fundamental shift in how HTTP messages are structured and transmitted. This architectural change enables every other HTTP/2 improvement.
What's Next:
With binary framing understood, we can now explore how HTTP/2 leverages this foundation. The next page examines Multiplexing—how multiple requests and responses interleave on a single connection, solving HTTP/1.1's head-of-line blocking problem and dramatically reducing page load times.
You now understand HTTP/2's binary framing layer—the architectural foundation that enables all other protocol improvements. The fixed frame structure, well-defined frame types, and stream identification system form the infrastructure upon which modern web performance is built.