A minimal, safe, hand-written HTTP protocol family library for C++23. Supports HTTP/1.1, HTTP/2, and HTTP/3 framing.
llhttp is generated by llparse through a multi-stage pipeline (llparse → TypeScript → C). This layered code generation makes the final C code hard to audit, debug, and maintain. Security guarantees are difficult to verify when the source of truth is buried behind two intermediate representations.
Zane-HTTPParser is written by hand in plain C++. Every byte of the parser is explicit, auditable, and free of generated machinery.
- State-machine design: clear, predictable parsing phases (method → URL → headers → body)
- Bounds-checked: all reads verify against the input buffer length
- Content-Length support: body collected when
Content-Lengthheader is present - Case-insensitive header normalization: field names stored in lowercase
- Zero external dependencies: only the C++23 standard library
- Binary frame layer: frame header (9-byte) parsing and construction for all frame types
- HPACK: full Huffman-coded header compression (RFC 7541), static + dynamic table
- Connection management: preface exchange, stream tracking, SETTINGS negotiation
- Build helpers:
buildSettings,buildGoaway,buildResponse(HEADERS + DATA)
- QUIC varint encoding: variable-length integer encode/decode (RFC 9000)
- HTTP/3 frame layer: frame type and length decoding, payload extraction
- QPACK: header compression adapted for QUIC (RFC 9204), encoder/decoder streams
- Connection management: control stream, SETTINGS, GOAWAY, stream type detection
- QUIC transport abstraction:
QUICTransportinterface for attaching any QUIC backend
| Field | Type | Description |
|---|---|---|
m_method |
std::string |
HTTP method (GET, POST, …) |
m_url |
std::string |
Raw request target |
m_pathname |
std::string |
URL path before ? |
m_headers |
std::map<std::string, std::string> |
Lowercased header fields |
m_body |
std::vector<uint8_t> |
Request body bytes |
| Method | Returns | Description |
|---|---|---|
execute(data, len) |
int32_t |
Feed raw bytes. Returns 0 on success, non-zero on error |
isComplete() |
bool |
True when a full request has been parsed |
hasError() |
bool |
True when a parse error occurred |
errorMessage() |
const std::string& |
Human-readable error description |
result() |
const Request& |
Parsed request (valid only when isComplete()) |
reset() |
void |
Re-initialize for a new request |
std::string buildResponse(int32_t status, const std::string& status_text,
const std::map<std::string, std::string>& headers,
const uint8_t* body, size_t body_len);Builds a raw HTTP/1.1 response string. Auto-adds Content-Length if neither it nor Transfer-Encoding is present.
DATA, HEADERS, PRIORITY, RST_STREAM, SETTINGS, PUSH_PROMISE, PING, GOAWAY, WINDOW_UPDATE, CONTINUATION
| Method | Returns | Description |
|---|---|---|
feed(data, size, on_frame, on_error) |
int32_t |
Feed raw bytes, process preface + frames |
buildSettings(settings_map) |
vector<uint8_t> |
Build a SETTINGS frame |
buildGoaway(last_stream_id, error) |
vector<uint8_t> |
Build a GOAWAY frame |
buildResponse(stream_id, headers, body, end_stream) |
vector<uint8_t> |
Build HEADERS + DATA frames |
reset() |
void |
Reset to initial state |
prefaceReceived() |
bool |
True after client preface is received |
clientPreface() |
const uint8_t* |
Client connection preface bytes |
clientPrefaceLen() |
size_t |
Length of client preface |
| Method | Returns | Description |
|---|---|---|
encode(headers) |
vector<uint8_t> |
Encode headers into HPACK binary |
decode(data, size, out) |
int32_t |
Decode HPACK binary into header pairs |
setMaxTableSize(size) |
void |
Update dynamic table capacity from SETTINGS |
maxTableSize() |
uint32_t |
Current max table size |
reset() |
void |
Clear dynamic table |
DATA, HEADERS, CANCEL_PUSH, SETTINGS, PUSH_PROMISE, GOAWAY, MAX_PUSH_ID, DUPLICATE_PUSH
| Method | Returns | Description |
|---|---|---|
init() |
void |
Initialize, send SETTINGS on control stream |
feedStream(stream_id, data, size, fin, on_frame, on_error) |
int32_t |
Feed data on a QUIC stream |
buildResponse(stream_id, headers, body, fin) |
vector<uint8_t> |
Build HEADERS + DATA for a stream |
buildSettings(settings_map) |
vector<uint8_t> |
Build a SETTINGS frame |
buildGoaway(last_stream_id) |
vector<uint8_t> |
Build a GOAWAY frame |
reset() |
void |
Reset to initial state |
initialized() |
bool |
True after init() completes |
| Method | Returns | Description |
|---|---|---|
encode(headers) |
vector<uint8_t> |
Encode headers into QPACK binary |
decode(data, size, out) |
int32_t |
Decode QPACK binary into header pairs |
feedEncoderStream(data, size) |
int32_t |
Process encoder stream instructions |
buildDecoderStream(increment) |
vector<uint8_t> |
Generate decoder stream instruction |
setMaxTableCapacity(cap) |
void |
Set max table capacity from SETTINGS |
reset() |
void |
Clear dynamic table |
| Method | Description |
|---|---|
sendStreamData(stream_id, data, size, fin) |
Send data on a given stream |
sendControlData(data, size) |
Send data on the control stream |
closeWithError(error_code, reason) |
Close connection with error |
isConnected() |
Returns true if still active |
| File | Protocol |
|---|---|
http_parser.hpp/.cpp |
HTTP/1.1 request parser |
http2.hpp/.cpp |
HTTP/2 framing, HPACK, connection |
http3.hpp/.cpp |
HTTP/3 framing, QPACK, connection |
Same as Zane.
Doug McIlroy: Make each program do one thing well. To do a new job, build afresh rather than complicate old programs by adding new "features".
We extracted this library for exactly that reason.