HTTP/2
Serve HTTP/2 and HTTP/1.1 on the same TLS port with automatic ALPN negotiation, no extra configuration required.
Feature requirements
HTTP/2 support is included in the http2 feature and the default http3
feature:
# HTTP/2 + TLS only (no QUIC)
cargo build --no-default-features --features http2
# Default build — also includes HTTP/3
cargo buildHTTP/2 is only available over TLS. A valid certificate must be configured. See HTTPS / TLS for setup instructions.
ALPN negotiation
No extra configuration is required to serve HTTP/2. The server advertises both
h2 and http/1.1 in the TLS handshake via ALPN. The client's preference
determines which protocol is selected for each connection:
alpn_protocols = ["h2", "http/1.1"]Both protocols share the same port. Browsers, curl, and other HTTP/2-capable
clients negotiate HTTP/2 automatically.
Request pipeline
rws has its own HTTP/2 server (h2_server, since 18.0.0 — the h2 crate is
now only the HTTP/2 client of the proxies). Per connection it:
- Exchanges the connection preface and SETTINGS (at most 250 concurrent
streams, header lists up to 64 KiB — larger ones get
431). - Reads frames, decodes HPACK header blocks and assembles each stream's
Request(body included), validating it as RFC 9113 §8.3 requires. - Calls your
Application— inline for a quick bodiless request the app says it can answer inline (runs_inline, as on HTTP/1.1), otherwise in its own task. - Encodes the
Response(headers seen before become one-byte HPACK indexes) and sends it within the client's flow-control windows; file and streamed bodies go out in chunks. Everything ready at once is written in one go.
It passes h2spec's 146 conformance tests and is fuzzed (frames and HPACK).
Abusive clients are cut off: rapid stream resets, CONTINUATION floods and
oversized frames end the connection with GOAWAY.
Your application code is unchanged between HTTP/1.1 and HTTP/2.
Forbidden headers
The HTTP/2 specification (RFC 9113 §8.2.2) prohibits certain connection-level headers in responses. The following headers are stripped automatically before sending any H2 response:
connectionkeep-alivetransfer-encodingupgradeproxy-connectionte
You do not need to remove these headers from Response objects yourself.
Alt-Svc advertisement
When built without HTTP/3, HTTP/1.1 TLS responses include:
Alt-Svc: h2=":7878"This tells HTTP/1.1 clients that HTTP/2 is available on the same port. With
the default http3 build the header advertises h3 instead.
HTTP/2 reverse proxy
H2ReverseProxy provides an upstream proxy that connects to backends over
HTTP/2. It is available as middleware and wraps the normal
Middleware / Application stack:
use rust_web_server::proxy::H2ReverseProxy;
let proxy = H2ReverseProxy::new(vec!["h2://backend:8080".to_string()]);
let app = App::new().wrap(proxy);H2ReverseProxy bridges between the synchronous middleware calling
convention and the async tokio runtime via an internal block_on_isolated
helper — a scoped OS thread running its own single-threaded tokio runtime,
rather than tokio::task::block_in_place. This works under any tokio
runtime flavor (including current_thread, where block_in_place would
panic) and even with no runtime active at all.
Async handlers
The http2 feature enables App::with_async_state(S), which accepts async fn
handlers. This lets you await futures (database calls, outbound HTTP requests,
etc.) directly inside route handlers:
let app = App::with_async_state(my_state)
.get("/data", |_req, _params, _conn, state| async move {
let rows: Vec<u8> = state.db.fetch_all_json().await;
Response::json(STATUS_CODE_REASON_PHRASE.n200_ok, rows)
});The SNI hostname is extracted from the TLS handshake and available in
ConnectionInfo::sni_hostname for every HTTP/2 request, the same as for
HTTP/1.1 TLS connections.
Limits and conformance
Each HTTP/2 connection advertises SETTINGS_MAX_CONCURRENT_STREAMS = 250
(the same limit as Go's net/http), so one client can't open unbounded
simultaneous streams, and SETTINGS_MAX_HEADER_LIST_SIZE = 64 KiB, so one
request can't make the server buffer megabytes of headers (HTTP/3 uses the
same 64 KiB field-section limit; an HTTP/1.1 request head is capped at
64 KiB, else 431). CI runs h2spec
against a live server on every commit and requires all 146 tests to pass
with none skipped (ci/e2e/h2spec.sh).