server: apply an HTTP/1.1 header read timeout to accepted connections#189
Draft
iainmcgin wants to merge 2 commits into
Draft
server: apply an HTTP/1.1 header read timeout to accepted connections#189iainmcgin wants to merge 2 commits into
iainmcgin wants to merge 2 commits into
Conversation
The built-in server built hyper connections without installing a timer, so hyper's header read timeout never took effect. A peer could open a connection and stall before sending a complete request, or finish a request and sit idle on a keep-alive connection, and be held open indefinitely along with its task and file descriptor. Install a TokioTimer on every accepted connection (HTTP/1 and HTTP/2 builders) in the standalone Server/BoundServer and the axum serve_tls path, and apply a configurable HTTP/1.1 header read timeout via with_header_read_timeout. It defaults to DEFAULT_HEADER_READ_TIMEOUT (30s) and accepts None to disable. The timeout bounds how long the server waits to read a complete request header block and, on keep-alive connections, the idle wait between requests, which mitigates slowloris-style connection-exhaustion. This activates the default header read timeout where it previously never fired, so an idle HTTP/1.1 keep-alive connection is now closed after 30s. HTTP/2 idle liveness (keep-alive pings) and dedicated idle-connection reaping are out of scope.
|
All contributors have signed the CLA ✍️ ✅ |
# Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The built-in server builds hyper connections without installing a timer, so
hyper's header read timeout never takes effect. A peer that opens a connection
and then stalls before sending a complete request — or that finishes a request
and then sits idle on a keep-alive connection — is held open indefinitely along
with its task and file descriptor. Today this can only be bounded by an external
load balancer or reverse proxy, leaving the server exposed to slowloris-style
connection-exhaustion.
Change
This installs a
TokioTimeron every accepted connection (both HTTP/1 andHTTP/2 builders) in the standalone
Server/BoundServerand in the axumserve_tlspath, and applies an HTTP/1.1 header read timeout.New configuration, mirroring the existing
with_tls_handshake_timeoutandwith_max_connection_ageknobs:Server::with_header_read_timeout(impl Into<Option<Duration>>)BoundServer::with_header_read_timeout(impl Into<Option<Duration>>)ServeTls::with_header_read_timeout(impl Into<Option<Duration>>)pub const DEFAULT_HEADER_READ_TIMEOUT: Duration(30 seconds)The timeout bounds how long the server waits to read a complete request header
block, measured from when hyper begins reading a new request. On a keep-alive
connection it also bounds the idle wait between requests, so a stalled or idle
peer is disconnected rather than pinning resources. It defaults to 30 seconds
(hyper's own default, now made explicit and actually active because the timer is
installed); pass
Noneto disable.This is a behavior change: previously no timer was installed, so the default
header read timeout never fired. An HTTP/1.1 keep-alive connection that sits
idle for 30 seconds without a new request is now closed. Raise the duration or
pass
Noneto opt out.Applies to HTTP/1.1 only. HTTP/2 connection liveness (keep-alive pings) and
dedicated idle-connection reaping are tracked separately (#175, #177); this PR
deliberately does not add those knobs to avoid overlapping that work.
Validation
cargo test --workspace --all-features— 652 passed.cargo clippy --workspace --all-features --all-targets -- -D warnings— clean.cargo +nightly-2026-02-27 fmt --all -- --check— clean.RUSTDOCFLAGS=-Dwarnings cargo doc -p connectrpc --all-features --no-deps— clean.cargo check -p connectrpc --no-default-features [--features server]— clean.New tests: builder default/override/disable round-trip on
ServerandBoundServer; astart_pausedtest proving a connection sending an incompleteheader block is closed once the timeout elapses; a positive control that prompt
requests are served normally; and a TLS-path test in
serve_tls.Closes #135