HTTP overview
Crate hopf-http: stream-first HTTP for bind and dial. Applications program HTTP Streams, not raw sockets. HTTP/1.1, HTTP/2, and HTTP/3 adapt a transport Endpoint into the same ServerHandler / ClientHandler SPI.
Contents
Standards
| Spec | Role in Hopf |
|---|---|
| RFC 9110 / 9112 | HTTP semantics and HTTP/1.1 messaging |
| RFC 9113 | HTTP/2 framing, streams, SETTINGS |
| RFC 7541 | HPACK header compression |
| RFC 9114 | HTTP/3 over QUIC |
| RFC 9204 | QPACK |
| RFC 7540 §3.2 / prior-knowledge | Cleartext h2c (Upgrade + preface sniff) |
| RFC 8441 | Extended CONNECT (H2) — WebSocket / upgrade |
| RFC 9220 | Extended CONNECT (H3) |
| RFC 7616 (+ Basic) | Digest / Basic auth factories |
| Bearer | Authorization: Bearer factory |
Chunked transfer encoding and trailers are implemented on the Stream SPI; see Limitations for the HTTP/1.1 trailer gap.
Architecture
TcpListenerConfig / TcpConnectorConfig / Quic hooks
│
▼
ProtocolHandler adapters
(H1Endpoint | H2Endpoint | CleartextHttpEndpoint |
AlpnHttpEndpoint | H3*Connection via listen_h3/connect_h3)
│
▼
HttpStream (one request/response)
│
├── ServerHandler ↔ ServerWriter
└── ClientHandler ↔ ClientWriter
Push event order on the server (with a body):
headers → start_request_body → request_body_content* → end_request_body → request_complete
Without a body: headers → request_complete.
Client handlers write in start, then receive response_headers → body* → optional response_trailers → response_complete.
Body chunks are zero-copy for the duration of each callback. Response bodies auto-chunk when applicable. Informational 1xx responses use ServerWriter::send_informational.
Version matrix
| Version | Server entry | Client entry | Transport notes |
|---|---|---|---|
| HTTP/1.1 | H1Endpoint::server |
H1Endpoint::client |
Grammar-driven lexer tokens |
| HTTP/2 (TLS) | AlpnHttpEndpoint or H2Endpoint::server |
H2Endpoint::client |
ALPN h2 |
| h2c | CleartextHttpEndpoint |
H2Endpoint::client(..., secure: false) |
Prior-knowledge preface + Upgrade |
| HTTP/3 | listen_h3 (feature h3) |
connect_h3 |
QUIC + ALPN h3 |
Cargo features: default = [], h3 = ["dep:hopf-quic"], integration = ["h3"].
HttpLimits
Shared parser / connection limits (Gumdrop defaults):
| Field | Type | Default | Meaning |
|---|---|---|---|
max_line_length |
usize |
8192 |
Max request-line or header field-line length |
max_header_count |
usize |
100 |
Max header fields |
max_chunk_size |
usize |
10 MiB |
Max chunk-size value |
max_request_body |
usize |
16 MiB |
Max aggregate request body |
use hopf_http::HttpLimits;
let limits = HttpLimits {
max_request_body: 64 * 1024 * 1024, // raise for large uploads
..HttpLimits::default()
};Additional framing knobs live on the parsers (not HttpLimits):
| Knob | Default | Where |
|---|---|---|
| H2 max frame size | 16_384 |
H2Parser::with_max_frame_size |
| HPACK dynamic table | configurable | hopf_http::h2::hpack |
| H3 max frame | 16 MiB |
H3 parser |
Endpoint constructor flags:
| Constructor | Extra args |
|---|---|
H1Endpoint::server(factory, limits, secure) |
secure: bool |
H1Endpoint::client(factory, limits, secure) |
secure: bool |
H2Endpoint::server(factory, limits, send_settings_on_connected) |
send SETTINGS on connect |
H2Endpoint::client(factory, limits, secure) |
secure: bool |
CleartextHttpEndpoint::new(factory, limits) |
h2c auto-detect |
AlpnHttpEndpoint::new(factory, limits) |
ALPN → H2 / H1 |
Cleartext, ALPN, and H3 wiring
Cleartext (H1 + h2c)
CleartextHttpEndpoint sniffs the HTTP/2 connection preface for prior-knowledge h2c, supports the h2c Upgrade path (101), and otherwise falls back to HTTP/1.1.
use std::sync::Arc;
use hopf_core::{ProtocolHandler, Runtime, RuntimeConfig, TcpListenerConfig};
use hopf_http::{CleartextHttpEndpoint, HttpLimits, ServerHandlerFactory};
fn listen_cleartext(rt: &Runtime, factory: Arc<dyn ServerHandlerFactory>) -> std::io::Result<()> {
let limits = HttpLimits::default();
let f = Arc::clone(&factory);
rt.add_tcp_listener(TcpListenerConfig::new("127.0.0.1:8080".parse()?, move || {
Box::new(CleartextHttpEndpoint::new(Arc::clone(&f), limits)) as Box<dyn ProtocolHandler>
}))?;
Ok(())
}TLS + ALPN (H1 / H2)
Wire TLS on TcpListenerConfig / TcpConnectorConfig via hopf-core's in-tree TLS (acceptor_from_pem et al.), and use AlpnHttpEndpoint as the ProtocolHandler. Negotiated ALPN selects H2 or H1. Typical ALPN list: &[b"h2", b"http/1.1"].
use hopf_core::{ProtocolHandler, TcpListenerConfig};
use hopf_http::{AlpnHttpEndpoint, HttpLimits};
use hopf_core::acceptor_from_pem;
use std::path::Path;
use std::sync::Arc;
let acceptor = acceptor_from_pem(
Path::new("cert.pem"),
Path::new("key.pem"),
&[b"h2", b"http/1.1"],
)?;
let factory: Arc<dyn hopf_http::ServerHandlerFactory> = /* ... */;
let limits = HttpLimits::default();
let f = Arc::clone(&factory);
let cfg = TcpListenerConfig::new(addr, move || {
Box::new(AlpnHttpEndpoint::new(Arc::clone(&f), limits)) as Box<dyn ProtocolHandler>
})
.with_tls(acceptor);HTTP/3
Enable feature h3. Build a QUIC server/client config with ALPN h3 (hopf_quic::ALPN_H3), then call listen_h3 / connect_h3. See quic-h3.md.
use hopf_http::{listen_h3, HttpLimits};
use hopf_quic::{server_config_self_signed, ALPN_H3};
use std::sync::Arc;
let (server_cfg, _leaf_pem) = server_config_self_signed(&["localhost"], &[ALPN_H3])?;
let handle = listen_h3(
"127.0.0.1:4433".parse()?,
server_cfg,
factory,
HttpLimits::default(),
)?;
// QuicDriverHandle owns the UDP driver thread — shut down or drop explicitly.Upgrade seams
ServerWriter::upgrade(headers, handler) hands the connection (H1) or stream (H2/H3) to a ProtocolUpgradeHandler after a successful switch:
| Transport | Success status | Byte delivery |
|---|---|---|
| HTTP/1.1 | 101 Switching Protocols | Raw connection bytes → receive |
| HTTP/2 / HTTP/3 | 200 Extended CONNECT | Stream DATA payloads → receive |
Outbound bytes are drained via ProtocolUpgradeHandler::take_outbound. Used by hopf-websocket (RFC 6455 / 8441 / 9220) and similar upgrades. Returns false if the transport cannot upgrade (already responded, wrong version, etc.).
Shared types
| Type | Purpose |
|---|---|
Headers / Header |
Ordered, case-insensitive; pseudo-header helpers (:method, :path, :scheme, :authority, :status) |
HttpStream / HttpRole |
Stream identity |
HttpVersion |
Http10 / Http11 (H1 messaging) |
HttpError / HttpResult |
Crate errors |
reason_phrase(code) |
Status reason text |
| Auth factories | BasicAuthFactory, DigestAuthFactory, BearerAuthFactory — see server.md |
Examples
| Example | How to run |
|---|---|
examples/http-hello |
cargo run -p http-hello -- 127.0.0.1:8080 (optional --tls) |
examples/http-get |
cargo run -p http-get -- [--http2\|--h2] [--http3\|--h3] [--ca pem] [--server-name name] ADDR PATH |
examples/http3-hello |
cargo run -p http3-hello -- 127.0.0.1:4433 |
Smoke: cargo test -p hopf-http --features h3 h3_get_hello.
Limitations
- No HTTP/2 server push —
PUSH_PROMISEis rejected (client:RST_STREAM; server:GOAWAY). - H2 PRIORITY frames (RFC 7540) are ignored; hopf uses RFC 9218 Extensible Prioritization (
Priorityheader +PRIORITY_UPDATE) with urgency/incremental scheduling. - HTTP/1.1 trailers are ignored on the wire (parity gap with Gumdrop). H2/H3 trailers work as a second HEADERS frame (
ServerWriter::trailers/ClientHandler::response_trailers). - UDP / QUIC for H3 lives in
hopf-quicand is feature-gated (h3). - Several declared deps (
tractrix,rjsonparser,rmimeparser) support later protocol crates, not the Stream SPI itself.
See also
- HTTP server —
ServerHandler, writers, auth, deferred responses - HTTP client —
ClientHandler, dial patterns,http-get - QUIC / H3 — transport configs and PEM builders
- WebSocket — upgrade consumer
- gRPC — unary over Streams (needs trailers on H2/H3)