Expand description
Serve utilities for running HTTP servers.
This module provides a convenient serve function similar to axum::serve
for easily serving Tower services with Hyper.
§When to Use This Module
- Use
servewhen you need a simple, batteries-included HTTP server - For more control over the Hyper connection builder, use
.configure_hyper() - For Lambda environments, see the
aws-lambdafeature androuting::lambda_handler
§How It Works
The serve function creates a connection acceptance loop that:
- Accepts connections via the
Listenertrait (e.g.,TcpListener) - Creates per-connection services by calling the
make_servicewithIncomingStream - Converts Tower services to Hyper using
TowerToHyperService - Spawns a task for each connection to handle HTTP requests
┌─────────┐ ┌──────────────┐ ┌──────────────┐ ┌────────┐
│Listener │─────▶│IncomingStream│─────▶│ make_service │─────▶│ Hyper │
│ accept │ │ (io + addr) │ │ (Tower) │ │ spawn │
└─────────┘ └──────────────┘ └──────────────┘ └────────┘The IncomingStream provides connection metadata to the service factory,
allowing per-connection customization based on remote address or IO type
§HTTP Protocol Selection
By default, serve uses Hyper’s automatic HTTP/1 or HTTP/2 selection with
HTTP upgrade support when both protocols are enabled. Customize settings with
.configure_hyper().
Set builder.http1_only() or builder.http2_only() in configure_hyper
to serve only that protocol. Other protocols are rejected by Hyper before
reaching the request service. Without either setting, both protocols are
automatically detected.
When both protocols are enabled, serve uses Hyper’s upgrade-capable API.
With http1_only() or http2_only(), it uses the regular connection API to
preserve the protocol restriction. HTTP/1-only upgrades require a custom
serve loop using Hyper’s native HTTP/1 builder.
§Graceful Shutdown
Graceful shutdown is zero-cost when not used - no watch channels are allocated
and no tokio::select! overhead is incurred. Call
.with_graceful_shutdown(signal) to enable it:
serve(listener, service)
.with_graceful_shutdown(async {
tokio::signal::ctrl_c().await.expect("failed to listen for Ctrl+C");
})
.awaitThis ensures in-flight requests complete before shutdown. Use
.with_shutdown_timeout(duration)
to set a maximum wait time.
§Common Patterns
§Limiting Concurrent Connections
serve limits concurrently accepted connections to 16,384 by default. Configure
or disable the limit on the server future:
serve(listener, app.into_make_service())
.max_connections(1000)
.await?;ListenerExt::limit_connections remains available for listener-level composition.
The connection permit stays attached to the socket across HTTP upgrades.
Applications that downcast upgraded IO with
hyper_util::server::conn::auto::upgrade::downcast must use
TokioIo<ConnLimiterIo<L::Io>> as the IO type. IncomingStream continues
to expose the listener’s original IO type to the service factory.
A one-minute header-read deadline starts when the connection task begins driving
Hyper and ends when Hyper delivers the first complete request head to its service.
It includes protocol negotiation and parsing for either HTTP version. Connection
service creation, Tower readiness, request middleware, and body reads are excluded.
Configure it with Serve::header_read_timeout. The same setting is applied to
Hyper’s HTTP/1 header-read timeout for subsequent requests. Header-read timeout
protection for every request is currently supported only for HTTP/1. For HTTP/2,
only the first request head on the connection is covered; later streams have no
header-read deadline. HTTP/2 keep-alive pings do not provide that protection.
§Accessing Connection Information
Use .into_make_service_with_connect_info::<T>() to access connection metadata
in your handlers:
use std::net::SocketAddr;
use aws_smithy_http_server::request::connect_info::ConnectInfo;
// In your handler:
async fn my_handler(ConnectInfo(addr): ConnectInfo<SocketAddr>) -> String {
format!("Request from: {}", addr)
}
// When serving:
serve(
listener,
app.into_make_service_with_connect_info::<SocketAddr>()
).await?;§Custom TCP Settings
Use ListenerExt::tap_io to configure TCP options:
use aws_smithy_http_server::serve::ListenerExt;
let listener = TcpListener::bind("127.0.0.1:3000")
.await?
.tap_io(|stream| {
let _ = stream.set_nodelay(true);
});
serve(listener, app.into_make_service()).await?;§Timeouts and Connection Management
§Available Timeout Types
| Timeout Type | What It Does | How to Configure |
|---|---|---|
| Header Read | Per-request protection currently supports HTTP/1 only; HTTP/2 covers only the connection’s first request head | 1 minute by default; override via .header_read_timeout() |
| Request | Time limit for processing one request | Tower’s TimeoutLayer |
| Connection Duration | Total connection lifetime limit | Custom accept loop with tokio::time::timeout |
| HTTP/2 Keep-Alive | Idle timeout between HTTP/2 requests | 20 s ping interval by default; override via .configure_hyper() with .http2().keep_alive_*() |
Examples:
examples/header_read_timeout.rs- Configure header read timeoutexamples/request_timeout.rs- Add request-level timeoutsexamples/custom_accept_loop.rs- Implement connection duration limitsexamples/http2_keepalive.rs- Configure HTTP/2 keep-aliveexamples/connection_limiting.rs- Limit concurrent connectionsexamples/request_concurrency_limiting.rs- Limit concurrent requests
§Connection Duration vs Idle Timeout
Connection duration timeout: Closes the connection after N seconds total, regardless of activity.
Implemented with tokio::time::timeout wrapping the connection future.
Idle timeout: Closes the connection only when inactive between requests.
- HTTP/2: Available via
.keep_alive_interval()and.keep_alive_timeout() - HTTP/1.1: Not available without modifying Hyper
See examples/custom_accept_loop.rs for a working connection duration timeout example.
§Connection Limiting vs Request Limiting
Connection limiting (.limit_connections()): Limits the number of TCP connections.
Use this to prevent socket/file descriptor exhaustion.
Request limiting (ConcurrencyLimitLayer): Limits in-flight requests.
Use this to prevent work queue exhaustion. With HTTP/2, one connection can have multiple
requests in flight simultaneously.
Most applications should use both - they protect different layers.
§Troubleshooting
§Type Errors
If you encounter complex error messages about trait bounds, check:
-
Service Error Type: Your service must have
Error = Infallibleⓘ// ✓ Correct - handlers return responses, not Results async fn handler() -> Response<Body> { ... } // ✗ Wrong - cannot use Result<Response, E> async fn handler() -> Result<Response<Body>, MyError> { ... } -
MakeService Wrapper: Use the correct wrapper for your service:
ⓘuse aws_smithy_http_server::routing::IntoMakeService; // For Smithy services: app.into_make_service() // For services with middleware: IntoMakeService::new(service)
§Graceful Shutdown Not Working
If graceful shutdown doesn’t wait for connections:
- Ensure you call
.with_graceful_shutdown()before.await - The signal future must be
Send + 'static - Consider adding a timeout with
.with_shutdown_timeout()
§Connection Limit Not Applied
Remember that .limit_connections() applies to the listener before passing
it to serve():
// ✓ Correct
let listener = TcpListener::bind("127.0.0.1:3000")
.await?
.limit_connections(100);
serve(listener, app.into_make_service()).await?;
// ✗ Wrong - limit_connections must be called on listener
serve(TcpListener::bind("127.0.0.1:3000").await?, app.into_make_service())
.limit_connections(100) // This method doesn't exist on Serve
.await?;§Advanced: Custom Connection Handling
If you need per-connection customization (e.g., different Hyper settings based on the remote address), you can implement your own connection loop using the building blocks provided by this module:
use aws_smithy_http_server::routing::IntoMakeService;
use aws_smithy_http_server::serve::Listener;
use hyper_util::rt::{TokioExecutor, TokioIo};
use hyper_util::server::conn::auto::Builder;
use hyper_util::service::TowerToHyperService;
use tower::ServiceExt;
let mut listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await?;
let make_service = app.into_make_service_with_connect_info::<SocketAddr>();
loop {
let (stream, remote_addr) = listener.accept().await?;
let io = TokioIo::new(stream);
// Per-connection Hyper configuration
let mut builder = Builder::new(TokioExecutor::new());
if remote_addr.ip().is_loopback() {
builder = builder.http2_only(); // Local connections use HTTP/2
} else {
builder = builder.http1().keep_alive(true); // External use HTTP/1
}
let tower_service = make_service
.ready()
.await?
.call(IncomingStream { io: &io, remote_addr })
.await?;
let hyper_service = TowerToHyperService::new(tower_service);
tokio::spawn(async move {
if let Err(err) = builder.serve_connection(io, hyper_service).await {
eprintln!("Error serving connection: {}", err);
}
});
}This approach provides complete flexibility while still leveraging the efficient Hyper and Tower integration provided by this module.
Portions of the implementation are adapted from axum (https://github.com/tokio-rs/axum), which is licensed under the MIT License. Copyright (c) 2019 Axum Contributors
Structs§
- Conn
Limiter - Return type of
ListenerExt::limit_connections. - Conn
Limiter Io - A connection counted by
ConnLimiter. - Incoming
Stream - An incoming stream that bundles connection information.
- Serve
- A server future that serves HTTP connections.
- Serve
With Graceful Shutdown - A server future with graceful shutdown enabled.
- TapIo
- Return type of
ListenerExt::tap_io.
Constants§
- DEFAULT_
HEADER_ READ_ TIMEOUT - Default timeout for receiving a complete request head.
- DEFAULT_
HTTP2_ KEEP_ ALIVE_ INTERVAL - Default interval between HTTP/2 keep-alive pings.
- DEFAULT_
HTTP2_ KEEP_ ALIVE_ TIMEOUT - Default timeout for receiving an HTTP/2 keep-alive ping acknowledgment.
- DEFAULT_
MAX_ CONNECTIONS - Default maximum number of concurrently accepted TCP connections.
- DEFAULT_
SHUTDOWN_ TIMEOUT - Graceful shutdown waits indefinitely unless a timeout is configured.
Traits§
- Listener
- Types that can listen for connections.
- Listener
Ext - Extensions to
Listener.
Functions§
- serve
- Serve the service with the supplied listener.