HTTP/2 — Binary protocols
Why HTTP/2?
HTTP/1.1 is text-based. Every request looks like this:
GET /index.html HTTP/1.1\r\n Host: example.com\r\n Accept: text/html\r\n \r\n
This has two problems. First, headers are sent as plain text on every request — even when they repeat. Second, HTTP/1.1 allows only one request at a time per TCP connection. If you need to load 10 resources, you either open 10 connections or wait for each response before sending the next request.
HTTP/2 solves both problems with a binary framing layer:
- Multiplexing — multiple requests and responses interleave on a single TCP connection.
- Header compression (HPACK) — headers are compressed using a static table and Huffman encoding.
- Binary format — no text parsing overhead.
Frame structure
Everything in HTTP/2 is a frame. Each frame has a 9-byte header:
+-----------------------------------------------+ | Length (24 bits) | +---------------+---------------+---------------+ | Type (8) | Flags (8) | +-+-------------+---------------+---------------+ |R| Stream Identifier (31 bits) | +-+---------------------------------------------+ | Frame Payload ... | +-----------------------------------------------+
- Length — payload size in bytes (max 16,384 by default).
- Type — what kind of frame this is.
- Flags — type-specific flags (e.g. END_STREAM, END_HEADERS).
- Stream ID — which stream this frame belongs to (0 for connection-level).
Frame types
| Type | ID | Purpose |
|---|---|---|
| DATA | 0 | Request/response body |
| HEADERS | 1 | HTTP headers (HPACK compressed) |
| SETTINGS | 4 | Connection configuration |
| PING | 6 | Keep-alive / latency measurement |
| GOAWAY | 7 | Graceful shutdown |
| WINDOW_UPDATE | 8 | Flow control |
| RST_STREAM | 3 | Cancel a stream |
Streams and multiplexing
A stream is a logical request/response pair within a connection. Streams have numeric IDs:
- Odd IDs (1, 3, 5, ...) — client-initiated requests.
- Even IDs (2, 4, 6, ...) — server push.
- Stream 0 — connection-level frames (SETTINGS, PING, GOAWAY).
Multiple streams can be active simultaneously. A client sends HEADERS on stream 1, then HEADERS on stream 3, without waiting for responses. The server sends responses in any order.
HPACK header compression
HTTP/2 compresses headers using a static table of 61 common header name-value pairs. For example:
| Index | Name | Value |
|---|---|---|
| 2 | :method | GET |
| 4 | :path | / |
| 8 | :status | 200 |
| 13 | :status | 404 |
Instead of sending :status: 200 as 12 bytes of text, HPACK sends a single byte: 0x88 (indexed representation of entry 8). This reduces header overhead dramatically.
Connection preface
An HTTP/2 connection starts with the client sending a 24-byte magic string:
PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
Followed by a SETTINGS frame. The server responds with its own SETTINGS and an ACK. This handshake establishes the connection parameters. In h2c (cleartext) mode, no TLS is needed.
Building an HTTP/2 server in Nyx
Nyx provides std/http2.nx for HTTP/2 servers. The API is callback-based:
import "std/http2" fn on_request(method: String, path: String, headers: Array, body: String) -> Array { if path == "/" { let hdrs: Array = ["content-type", "text/html"] return [200, hdrs, "<h1>Hello from HTTP/2!</h1>"] } let hdrs: Array = ["content-type", "text/plain"] return [404, hdrs, "Not Found"] } fn main() -> int { h2_serve(3000, 4, on_request) return 0 }
The callback receives the method, path, headers (as a flat array [key1, val1, key2, val2, ...]), and body. It returns a three-element array: [status, headers, body].
Testing with curl
Use curl --http2-prior-knowledge to connect over h2c:
$ curl --http2-prior-knowledge http://localhost:3000/ <h1>Hello from HTTP/2!</h1>
The --http2-prior-knowledge flag tells curl to use HTTP/2 directly without an upgrade handshake.
What Nyx does under the hood
The HTTP/2 implementation is split between C and Nyx:
runtime/http2.c— frame I/O, HPACK encoding/decoding, Huffman tables (all binary operations in C for performance).std/http2.nx— protocol state machine, connection preface validation, frame dispatch loop, response building (all logic in Nyx).
This hybrid approach — C for byte-level operations, Nyx for protocol logic — is the same pattern used for WebSockets, TCP, and the RESP parser.
Summary
- HTTP/2 replaces HTTP/1.1's text protocol with binary framing.
- Multiplexing allows multiple requests on one TCP connection.
- HPACK compresses headers using a static table and Huffman encoding.
- Frames have a 9-byte header: length, type, flags, stream ID.
h2_serve(port, workers, callback)starts an HTTP/2 server.- Test with
curl --http2-prior-knowledge.