Skip to content

server: apply an HTTP/1.1 header read timeout to accepted connections#189

Draft
iainmcgin wants to merge 2 commits into
mainfrom
feat/server-connection-read-idle-timeouts
Draft

server: apply an HTTP/1.1 header read timeout to accepted connections#189
iainmcgin wants to merge 2 commits into
mainfrom
feat/server-connection-read-idle-timeouts

Conversation

@iainmcgin

@iainmcgin iainmcgin commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

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 TokioTimer on every accepted connection (both HTTP/1 and
HTTP/2 builders) in the standalone Server/BoundServer and in the axum
serve_tls path, and applies an HTTP/1.1 header read timeout.

New configuration, mirroring the existing with_tls_handshake_timeout and
with_max_connection_age knobs:

  • 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 None to 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 None to 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 Server and
BoundServer; a start_paused test proving a connection sending an incomplete
header 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

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.
@github-actions

github-actions Bot commented Jun 20, 2026

Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

server: apply connection-level read and idle timeouts in the built-in server

1 participant