Skip to main content

Module http_serve

Module http_serve 

Source
Expand description

Accept loop with client-silence reaping — the slowloris defense axum::serve cannot provide.

axum::serve (0.8) builds hyper-util’s connection builder with no timer and no header_read_timeout and exposes no way to set one, so a client that connects and sends a partial request line — or full headers and then dribbles a Content-Length body forever — holds its fd and connection task indefinitely (verified empirically: 90 s+ holds with zero reaping). On the shipped k8s/systemd exposure that is an unauthenticated fd-exhaustion vector on the proxy front door.

This module owns the accept loop instead, mirroring axum’s semantics (Router::call per connection, serve_connection_with_upgrades, graceful drain on shutdown) while adding exactly two client-silence bounds — deliberately not a whole-request deadline, because a legitimate upstream first token can take minutes and SSE responses stream far longer than any sane global timeout:

  • Header phase: hyper’s header_read_timeout (30 s), which also reaps pre-request idle keep-alive connections (hyper arms it between requests on an idle HTTP/1 connection).
  • Body phase: a per-frame gap timeout on the request body (30 s between chunks), applied via SilenceBoundedBody. A slow uploader making real progress never trips it; a stalled one is aborted with a hyper error that resets the stream.
  • HTTP/2: keep-alive pings (20 s interval / 30 s grace) reap silent peers; header_read_timeout covers the h1 preface path.

Shutdown mirrors axum::serve::with_graceful_shutdown: when the caller’s shutdown future resolves, the listener is dropped (new connects refused), every live connection gets graceful_shutdown() via [hyper_util::server::graceful::GracefulShutdown], and the returned future resolves once all connections finish — the caller keeps its own drain-budget timeout around that future, so the drain-timeout accounting in main.rs is unchanged.

Constants§

BODY_FRAME_GAP_TIMEOUT
Maximum gap between request-body frames from the client. Production value; passed by main.rs into the serve loop.
HEADER_READ_TIMEOUT
Maximum client silence while reading request headers (also arms between keep-alive requests, reaping pre-request idle connections). Production value; passed by main.rs into the serve loop.

Functions§

serve_with_client_silence_reaping
Serve router on listener with client-silence reaping, resolving after shutdown fires and every remaining connection drains. The caller wraps the post-shutdown tail in its own drain-budget timeout. on_accept runs synchronously per accepted socket (socket-option tuning — the caller owns its failure/log policy). The two silence bounds are parameters so tests can exercise reaping in milliseconds; production callers pass HEADER_READ_TIMEOUT / BODY_FRAME_GAP_TIMEOUT.