Table of Contents

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:

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 ...               |
+-----------------------------------------------+

Frame types

TypeIDPurpose
DATA0Request/response body
HEADERS1HTTP headers (HPACK compressed)
SETTINGS4Connection configuration
PING6Keep-alive / latency measurement
GOAWAY7Graceful shutdown
WINDOW_UPDATE8Flow control
RST_STREAM3Cancel a stream

Streams and multiplexing

A stream is a logical request/response pair within a connection. Streams have numeric IDs:

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:

IndexNameValue
2:methodGET
4:path/
8:status200
13:status404

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:

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

← Previous: Message queues with nyx-queue Next: Building a database — nyx-db →