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 HTTP/1 with upgrade support, allowing clients to
negotiate HTTP/2 via the HTTP/1.1 Upgrade mechanism or ALPN. The protocol is
auto-detected for each connection.
You can customize this behavior with .configure_hyper():
// Force HTTP/2 only (skips upgrade negotiation)
serve(listener, app.into_make_service())
.configure_hyper(|builder| {
builder.http2_only()
})
.await?;
// Force HTTP/1 only with keep-alive
serve(listener, app.into_make_service())
.configure_hyper(|builder| {
builder.http1().keep_alive(true)
})
.await?;Performance note: When using .http2_only() or .http1(), the server skips
the HTTP/1 upgrade preface reading, which can reduce connection setup latency.
§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
Use ListenerExt::limit_connections to prevent resource exhaustion:
use aws_smithy_http_server::serve::ListenerExt;
let listener = TcpListener::bind("0.0.0.0:3000")
.await?
.limit_connections(1000); // Max 1000 concurrent connections
serve(listener, app.into_make_service()).await?;§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("0.0.0.0: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 | Time limit for reading HTTP headers | .configure_hyper() with .http1().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 | .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("0.0.0.0:3000")
.await?
.limit_connections(100);
serve(listener, app.into_make_service()).await?;
// ✗ Wrong - limit_connections must be called on listener
serve(TcpListener::bind("0.0.0.0: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("0.0.0.0: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.
Traits§
- Listener
- Types that can listen for connections.
- Listener
Ext - Extensions to
Listener.
Functions§
- serve
- Serve the service with the supplied listener.