HTTP/3 Protocol
httpx.zig provides a complete, from-scratch implementation of HTTP/3 (RFC 9114) including QPACK header compression (RFC 9204) and QUIC transport framing (RFC 9000). This guide covers all HTTP/3 features available in the library.
Custom Implementation
Zig's standard library does not provide HTTP/3 or QUIC support. httpx.zig implements these protocols entirely from scratch, following RFC 9114, RFC 9204, and RFC 9000 specifications.
Platform Support
HTTP/3 support works on all platforms:
| Platform | Architecture | Status |
|---|---|---|
| Linux | x86_64, aarch64, i386, arm | ✅ |
| Windows | x86_64, aarch64, i386, arm | ✅ |
| macOS | x86_64, aarch64, i386, arm | ✅ |
| FreeBSD | x86_64, aarch64, i386, arm | ✅ |
Features
- QPACK Header Compression - Full RFC 9204 implementation with 99-entry static table
- QUIC Transport Framing - All QUIC frame types (STREAM, CRYPTO, ACK, etc.)
- Variable-Length Integers - QUIC varint encoding/decoding
- Connection IDs - Full connection ID management
- Transport Parameters - QUIC transport parameter encoding
QPACK vs HPACK
QPACK is designed for HTTP/3's out-of-order delivery:
| Feature | HPACK (HTTP/2) | QPACK (HTTP/3) |
|---|---|---|
| Static Table | 61 entries | 99 entries |
| Dynamic Table | Required in-order | Allows out-of-order |
| Blocking | Synchronous | Async with streams |
| Use Case | TCP (ordered) | QUIC (unordered) |
QPACK Static Table
const httpx = @import("httpx");
// QPACK has a larger static table
std.debug.print("QPACK static table: {d} entries\n", .{httpx.qpack.StaticTable.entries.len}); // 99
std.debug.print("HPACK static table: {d} entries\n", .{httpx.hpack.StaticTable.entries.len}); // 61
// Common static table lookups
const idx = httpx.qpack.StaticTable.findNameValue(":method", "GET");
if (idx) |index| {
std.debug.print("Found :method=GET at index {d}\n", .{index});
}QPACK Encoding
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var ctx = httpx.QpackContext.init(allocator);
defer ctx.deinit();
const headers = [_]httpx.qpack.HeaderEntry{
.{ .name = ":method", .value = "GET" },
.{ .name = ":path", .value = "/api/v3/resources" },
.{ .name = ":scheme", .value = "https" },
.{ .name = ":authority", .value = "api.example.com" },
.{ .name = "accept", .value = "application/json" },
.{ .name = "accept-encoding", .value = "gzip, deflate, br" },
};
const encoded = try httpx.qpack.encodeHeaders(&ctx, &headers, allocator);
defer allocator.free(encoded);
std.debug.print("Encoded {d} headers into {d} bytes\n", .{headers.len, encoded.len});QPACK Encoder Stream
QPACK uses separate streams for encoder/decoder instructions:
var encoder = httpx.qpack.EncoderStream.init();
// Set Dynamic Table Capacity
const cap_instruction = encoder.setDynamicTableCapacity(4096);
// Insert With Name Reference
const insert_instruction = encoder.insertWithNameReference(17, "POST"); // :method=POSTQUIC Packet Structure
Connection IDs
// Create connection IDs
var dcid = httpx.quic.ConnectionId{};
dcid.len = 8;
@memcpy(dcid.data[0..8], &[_]u8{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 });
var scid = httpx.quic.ConnectionId{};
scid.len = 4;
@memcpy(scid.data[0..4], &[_]u8{ 0xAA, 0xBB, 0xCC, 0xDD });Long Header (Initial, Handshake, 0-RTT)
const long_header = httpx.quic.LongHeader{
.packet_type = .initial,
.version = .v1,
.dcid = dcid,
.scid = scid,
};
var buf: [64]u8 = undefined;
const len = try long_header.encode(&buf);
std.debug.print("Long header: {d} bytes\n", .{len});
// Decode
const decoded = try httpx.quic.LongHeader.decode(&buf);
std.debug.print("Packet type: {s}\n", .{@tagName(decoded.header.packet_type)});Short Header (1-RTT)
const short_header = httpx.quic.ShortHeader{
.dcid = dcid,
.spin_bit = 0,
.key_phase = 0,
};
var buf: [32]u8 = undefined;
const len = try short_header.encode(&buf);Packet Types
| Type | Long Header | Description |
|---|---|---|
| Initial | ✅ | Connection establishment |
| 0-RTT | ✅ | Early data |
| Handshake | ✅ | TLS handshake completion |
| Retry | ✅ | Address validation |
| 1-RTT | ❌ (Short) | Application data |
QUIC Frames
STREAM Frame
Carries application data:
const stream_frame = httpx.quic.StreamFrame{
.stream_id = 4, // Client-initiated bidirectional stream
.offset = 0,
.data = "Hello, HTTP/3!",
.fin = false,
};
var buf: [128]u8 = undefined;
const len = try stream_frame.encode(&buf);
// Decode
const decoded = try httpx.quic.StreamFrame.decode(buf[0..len]);
std.debug.print("Data: {s}\n", .{decoded.frame.data});CRYPTO Frame
Carries TLS handshake data:
const crypto_frame = httpx.quic.CryptoFrame{
.offset = 0,
.data = &[_]u8{ 0x01, 0x00, 0x00, 0x05, 'h', 'e', 'l', 'l', 'o' },
};
var buf: [64]u8 = undefined;
const len = try crypto_frame.encode(&buf);ACK Frame
Acknowledges received packets:
const ack_frame = httpx.quic.AckFrame{
.largest_acknowledged = 42,
.ack_delay = 100,
.first_ack_range = 10,
.ack_ranges = &.{},
};
var buf: [64]u8 = undefined;
const len = try ack_frame.encode(&buf);CONNECTION_CLOSE Frame
Terminates a connection:
const close_frame = httpx.quic.ConnectionCloseFrame{
.error_code = @intFromEnum(httpx.quic.TransportError.no_error),
.frame_type = null,
.reason_phrase = "graceful shutdown",
};
var buf: [64]u8 = undefined;
const len = try close_frame.encode(false, &buf); // false = transport closeFrame Types
| Type | Value | Description |
|---|---|---|
| PADDING | 0x00 | Connection-level padding |
| PING | 0x01 | Connectivity check |
| ACK | 0x02 | Acknowledgment |
| ACK_ECN | 0x03 | ACK with ECN counts |
| RESET_STREAM | 0x04 | Abrupt stream termination |
| STOP_SENDING | 0x05 | Request sender stop |
| CRYPTO | 0x06 | TLS handshake data |
| NEW_TOKEN | 0x07 | Address validation token |
| STREAM | 0x08-0x0f | Application data |
| MAX_DATA | 0x10 | Connection flow control |
| MAX_STREAM_DATA | 0x11 | Stream flow control |
| MAX_STREAMS_BIDI | 0x12 | Bidirectional stream limit |
| MAX_STREAMS_UNI | 0x13 | Unidirectional stream limit |
| DATA_BLOCKED | 0x14 | Connection blocked |
| STREAM_DATA_BLOCKED | 0x15 | Stream blocked |
| STREAMS_BLOCKED_BIDI | 0x16 | Bidi streams blocked |
| STREAMS_BLOCKED_UNI | 0x17 | Uni streams blocked |
| NEW_CONNECTION_ID | 0x18 | New connection ID |
| RETIRE_CONNECTION_ID | 0x19 | Retire connection ID |
| PATH_CHALLENGE | 0x1a | Path validation |
| PATH_RESPONSE | 0x1b | Path validation response |
| CONNECTION_CLOSE | 0x1c | Transport close |
| CONNECTION_CLOSE_APP | 0x1d | Application close |
| HANDSHAKE_DONE | 0x1e | Handshake complete |
Variable-Length Integers
QUIC uses a variable-length integer encoding:
// Encoding
var buf: [8]u8 = undefined;
const len = try httpx.quic.encodeVarInt(15293, &buf);
std.debug.print("Encoded in {d} bytes\n", .{len});
// Decoding
const result = try httpx.quic.decodeVarInt(&buf);
std.debug.print("Value: {d}\n", .{result.value});Varint Ranges
| Bytes | Range |
|---|---|
| 1 | 0 - 63 |
| 2 | 64 - 16,383 |
| 4 | 16,384 - 1,073,741,823 |
| 8 | 1,073,741,824 - 4,611,686,018,427,387,903 |
HTTP/3 Frame Types
| Type | Value | Description |
|---|---|---|
| DATA | 0x00 | Request/response body |
| HEADERS | 0x01 | QPACK-encoded headers |
| CANCEL_PUSH | 0x03 | Cancel server push |
| SETTINGS | 0x04 | Connection settings |
| PUSH_PROMISE | 0x05 | Server push promise |
| GOAWAY | 0x07 | Connection shutdown |
| MAX_PUSH_ID | 0x0d | Maximum push ID |
HTTP/3 Unidirectional Stream Types
| Type | Value | Description |
|---|---|---|
| Control | 0x00 | Control stream |
| Push | 0x01 | Server push stream |
| QPACK Encoder | 0x02 | QPACK encoder instructions |
| QPACK Decoder | 0x03 | QPACK decoder instructions |
Transport Parameters
QUIC transport parameters can be encoded:
const params = httpx.quic.TransportParameters{
.original_destination_connection_id = null,
.max_idle_timeout = 30000,
.max_udp_payload_size = 1350,
.initial_max_data = 1048576,
.initial_max_stream_data_bidi_local = 262144,
.initial_max_stream_data_bidi_remote = 262144,
.initial_max_stream_data_uni = 262144,
.initial_max_streams_bidi = 100,
.initial_max_streams_uni = 100,
};
const encoded = try params.encode(allocator);
defer allocator.free(encoded);Running the Example
The full HTTP/3 example can be run with:
zig build example-http3_example
./zig-out/bin/http3_exampleSee Also
- Protocol API Reference - Full API documentation
- HTTP/2 Guide - HPACK and stream management
- RFC 9114 - HTTP/3 specification
- RFC 9204 - QPACK specification
- RFC 9000 - QUIC transport specification
