Loading page…
Rust walkthroughs
Loading page…
hyper::server::conn::Http and hyper::server::Server for HTTP server configuration?hyper::server::conn::Http is a low-level connection handler that gives you manual control over each individual connection's lifecycle and configuration, while hyper::server::Server is a high-level convenience wrapper that manages connection acceptance and spawning automatically. The key distinction is that Http requires you to explicitly accept incoming streams and call serve_connection for each one, making it suitable for custom connection handling, protocol upgrades, or integration with other runtimes. Server abstracts this away by providing bind and serve methods that handle the entire accept-serve loop, making it the preferred choice for standard HTTP servers where you just want to handle requests without managing connection details.
use hyper::{Body, Request, Response, Server};
use hyper::service::{make_service_fn, service_fn};
use std::convert::Infallible;
async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
Ok(Response::new(Body::from("Hello, World!")))
}
#[tokio::main]
async fn main() {
// Server::bind creates a high-level server
let addr = ([127, 0, 0, 1], 3000).into();
let make_service = make_service_fn(|_conn| async {
Ok::<_, Infallible>(service_fn(handle))
});
// Server handles everything:
// 1. Binds to address
// 2. Accepts connections
// 3. Spawns tasks for each connection
// 4. Serves HTTP on each connection
let server = Server::bind(&addr).serve(make_service);
// Run until completion (or error)
if let Err(e) = server.await {
eprintln!("Server error: {}", e);
}
}Server::bind().serve() handles the entire server lifecycle with minimal configuration.
use hyper::server::conn::Http;
use hyper::{Body, Request, Response, Version};
use hyper::service::service_fn;
use tokio::net::TcpListener;
use std::convert::Infallible;
async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
Ok(Response::new(Body::from("Hello from Http!")))
}
#[tokio::main]
async fn main() {
// Http gives low-level control
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
// Create Http connection handler
let http = Http::new();
loop {
// Manually accept each connection
let (stream, remote_addr) = listener.accept().await.unwrap();
// Manually call serve_connection for each stream
let service = service_fn(handle);
tokio::spawn(async move {
// This handles HTTP for this specific connection
if let Err(e) = http.serve_connection(stream, service).await {
eprintln!("Connection error from {}: {}", remote_addr, e);
}
});
}
}Http::new().serve_connection() requires manual connection acceptance but offers more control.
use hyper::{Body, Request, Response, Server};
use hyper::service::{make_service_fn, service_fn};
use std::convert::Infallible;
async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
Ok(Response::new(Body::from("Response")))
}
#[tokio::main]
async fn main() {
let addr = ([127, 0, 0, 1], 3000).into();
let make_service = make_service_fn(|_conn| async {
Ok::<_, Infallible>(service_fn(handle))
});
// Server::bind does this internally:
// 1. Creates TcpListener
// 2. Loops accepting connections
// 3. For each connection:
// - Creates Http connection handler
// - Calls serve_connection
// - Spawns task for handling
// You just provide the service factory
let server = Server::bind(&addr).serve(make_service);
server.await.unwrap();
}Server internally uses Http but hides the accept loop and task spawning.
use hyper::server::conn::Http;
use hyper::{Body, Request, Response};
use hyper::service::service_fn;
use tokio::net::TcpListener;
use std::time::Duration;
use std::convert::Infallible;
async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
Ok(Response::new(Body::from("Configured response")))
}
#[tokio::main]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
// Http can be configured before serving
let http = Http::new()
// HTTP/1 configuration
.http1_keepalive(true)
.http1_header_read_timeout(Duration::from_secs(10))
.http1_writev(true) // Use writev for headers
// HTTP/2 configuration
.http2_only(false) // Allow HTTP/1
.http2_max_concurrent_streams(100);
// Server has similar builder methods, but Http gives
// per-connection control
loop {
let (stream, _) = listener.accept().await.unwrap();
let service = service_fn(handle);
// Each connection can use different Http configuration
let http = http.clone();
tokio::spawn(async move {
http.serve_connection(stream, service).await.unwrap();
});
}
}Http provides fine-grained HTTP protocol configuration per connection.
use hyper::{Body, Request, Response, Server};
use hyper::service::{make_service_fn, service_fn};
use std::time::Duration;
use std::convert::Infallible;
async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
Ok(Response::new(Body::from("Response")))
}
#[tokio::main]
async fn main() {
let addr = ([127, 0, 0, 1], 3000).into();
let make_service = make_service_fn(|_conn| async {
Ok::<_, Infallible>(service_fn(handle))
});
// Server also has builder methods
let server = Server::bind(&addr)
.http1_keepalive(true)
.http1_header_read_timeout(Duration::from_secs(10))
.http2_only(false)
.http2_max_concurrent_streams(100)
.serve(make_service);
// These configure the underlying Http for all connections
server.await.unwrap();
}Server exposes similar configuration methods that apply to all connections.
use hyper::server::conn::Http;
use hyper::{Body, Request, Response, StatusCode};
use hyper::service::service_fn;
use tokio::net::TcpListener;
use std::convert::Infallible;
use tokio::io::{AsyncRead, AsyncWrite};
async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
Ok(Response::new(Body::from("HTTP response")))
}
#[tokio::main]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
let http = Http::new();
loop {
let (stream, _) = listener.accept().await.unwrap();
let service = service_fn(handle);
tokio::spawn(async move {
// serve_connection returns Ok(Upgraded) for protocol upgrades
// This allows handling WebSocket upgrades, HTTP/2 prior knowledge, etc.
let result = http.serve_connection(stream, service).await;
match result {
Ok(_) => println!("Connection completed normally"),
Err(e) => eprintln!("Connection error: {}", e),
}
// With Server, you don't have this level of control
// over the connection result
});
}
}Http returns connection results including upgrades; Server handles them internally.
use hyper::server::conn::Http;
use hyper::{Body, Request, Response, HeaderMap, StatusCode};
use hyper::service::service_fn;
use tokio::net::TcpListener;
use std::convert::Infallible;
async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
// Check for WebSocket upgrade
if let Some(ws_key) = req.headers().get("Upgrade") {
if ws_key == "websocket" {
// Return upgrade response
let mut response = Response::new(Body::empty());
*response.status_mut() = StatusCode::SWITCHING_PROTOCOLS;
response.headers_mut().insert("Upgrade", "websocket".parse().unwrap());
return Ok(response);
}
}
Ok(Response::new(Body::from("Regular HTTP response")))
}
#[tokio::main]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
let http = Http::new();
loop {
let (stream, _addr) = listener.accept().await.unwrap();
let service = service_fn(handle_request);
tokio::spawn(async move {
// serve_connection allows inspecting the upgrade result
let conn = http.serve_connection(stream, service);
// Use with_upgrades() to handle protocol upgrades
// conn.with_upgrades().await is needed for WebSocket support
if let Err(e) = conn.await {
eprintln!("Connection error: {}", e);
}
});
}
}Http::serve_connection with with_upgrades() enables WebSocket and other protocol upgrades.
use hyper::{Body, Request, Response, Server};
use hyper::service::{make_service_fn, service_fn};
use std::convert::Infallible;
use tokio::signal;
async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
Ok(Response::new(Body::from("Hello")))
}
#[tokio::main]
async fn main() {
let addr = ([127, 0, 0, 1], 3000).into();
let make_service = make_service_fn(|_conn| async {
Ok::<_, Infallible>(service_fn(handle))
});
// Server provides graceful_shutdown helper
let server = Server::bind(&addr).serve(make_service);
// with_graceful_shutdown accepts a future that signals shutdown
let graceful = server.with_graceful_shutdown(signal::ctrl_c());
if let Err(e) = graceful.await {
eprintln!("Server error: {}", e);
}
}Server::with_graceful_shutdown handles graceful shutdown; Http requires manual implementation.
use hyper::server::conn::Http;
use hyper::{Body, Request, Response};
use hyper::service::service_fn;
use tokio::net::TcpListener;
use tokio::sync::broadcast;
use std::convert::Infallible;
async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
Ok(Response::new(Body::from("Response")))
}
#[tokio::main]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
let http = Http::new();
// Shutdown signal channel
let (shutdown_tx, mut shutdown_rx) = broadcast::channel::<()>(1);
// Accept loop
loop {
tokio::select! {
// Accept new connections
accept_result = listener.accept() => {
let (stream, _) = accept_result.unwrap();
let service = service_fn(handle);
let http = http.clone();
let mut shutdown_rx = shutdown_tx.subscribe();
tokio::spawn(async move {
// Use into_owned() to allow graceful shutdown
let conn = http.serve_connection(stream, service);
tokio::select! {
_ = conn => {}
_ = shutdown_rx.recv() => {
// Graceful shutdown signal received
}
}
});
}
// Shutdown signal
_ = tokio::signal::ctrl_c() => {
println!("Shutdown signal received");
shutdown_tx.send(()).unwrap();
break;
}
}
}
// Wait for connections to finish
println!("Waiting for connections to close...");
}With Http, graceful shutdown requires manual coordination.
use hyper::server::conn::Http;
use hyper::{Body, Request, Response};
use hyper::service::service_fn;
use tokio::net::TcpListener;
use std::net::SocketAddr;
use std::convert::Infallible;
async fn handle_with_addr(
_req: Request<Body>,
remote_addr: SocketAddr
) -> Result<Response<Body>, Infallible> {
println!("Request from: {}", remote_addr);
Ok(Response::new(Body::from(format!("Hello from {}", remote_addr))))
}
#[tokio::main]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
let http = Http::new();
loop {
// Http pattern: you have access to connection info
let (stream, remote_addr) = listener.accept().await.unwrap();
// Can pass connection info to service
let service = service_fn(move |req| {
handle_with_addr(req, remote_addr)
});
tokio::spawn(async move {
http.serve_connection(stream, service).await.unwrap();
});
}
}Http gives you access to the remote address and connection details.
use hyper::{Body, Request, Response, Server};
use hyper::service::{make_service_fn, service_fn};
use std::convert::Infallible;
use std::net::SocketAddr;
async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
Ok(Response::new(Body::from("Hello")))
}
#[tokio::main]
async fn main() {
let addr = ([127, 0, 0, 1], 3000).into();
// make_service_fn provides connection info
let make_service = make_service_fn(|conn: &Server::conn::AddrStream| {
// Access remote address
let remote_addr = conn.remote_addr();
println!("New connection from: {}", remote_addr);
async move {
Ok::<_, Infallible>(service_fn(handle))
}
});
let server = Server::bind(&addr).serve(make_service);
server.await.unwrap();
}Server provides AddrStream in make_service_fn for connection info access.
use hyper::server::conn::Http;
use hyper::{Body, Request, Response};
use hyper::service::service_fn;
use std::convert::Infallible;
async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
Ok(Response::new(Body::from("Hello")))
}
// Http can work with any stream type that implements AsyncRead + AsyncWrite
// This makes it usable with:
// - Tokio TcpStream
// - Async-std TcpStream
// - Custom transports (Unix sockets, TLS streams, etc.)
// - Mock streams for testing
#[tokio::main]
async fn main() {
// For example, using with a custom transport:
// let custom_stream = MyCustomTransport::new();
// let service = service_fn(handle);
// http.serve_connection(custom_stream, service).await;
// Server requires tokio runtime and TcpListener
// Http is transport-agnostic
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
let http = Http::new();
loop {
let (stream, _) = listener.accept().await.unwrap();
let service = service_fn(handle);
tokio::spawn(http.serve_connection(stream, service));
}
}Http is transport-agnostic; Server is tokio-tied.
use hyper::server::conn::Http;
use hyper::{Body, Request, Response};
use hyper::service::service_fn;
use tokio::net::TcpListener;
use tokio_native_tls::native_tls::TlsAcceptor;
use std::convert::Infallible;
async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
Ok(Response::new(Body::from("HTTPS response")))
}
#[tokio::main]
async fn main() {
// Http works well with TLS because you control the stream
let listener = TcpListener::bind("127.0.0.1:443").await.unwrap();
let http = Http::new();
// TLS acceptor (simplified - would need proper certificate)
// let tls_acceptor = TlsAcceptor::from(...);
loop {
let (stream, _) = listener.accept().await.unwrap();
// Wrap stream with TLS
// let tls_stream = tls_acceptor.accept(stream).await.unwrap();
// Then serve HTTP over TLS stream
// http.serve_connection(tls_stream, service_fn(handle)).await;
// Server can also work with TLS, but Http gives explicit control
// over the TLS handshake timing and error handling
}
}Http provides explicit control for TLS and other transport wrapping.
use hyper::server::conn::Http;
use hyper::{Body, Request, Response};
use hyper::service::service_fn;
use tokio::net::TcpListener;
use std::time::Duration;
use std::convert::Infallible;
async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
// Simulate slow processing
tokio::time::sleep(Duration::from_secs(2)).await;
Ok(Response::new(Body::from("Done")))
}
#[tokio::main]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:3000").await.unwrap();
// Configure per-connection timeouts
let http = Http::new()
.http1_header_read_timeout(Duration::from_secs(5));
loop {
let (stream, _) = listener.accept().await.unwrap();
let service = service_fn(handle);
let http = http.clone();
tokio::spawn(async move {
// Connection will timeout if headers aren't read within 5 seconds
if let Err(e) = http.serve_connection(stream, service).await {
eprintln!("Connection error: {}", e);
}
});
}
}Http allows per-connection timeout configuration; Server applies configuration to all connections.
// Use Server::bind when:
// - Standard HTTP server use case
// - Want minimal boilerplate
// - Don't need per-connection customization
// - Don't need protocol upgrade handling
// - Don't need custom transport types
// Use Http when:
// - Need to handle protocol upgrades (WebSocket, HTTP/2 prior knowledge)
// - Want to customize each connection differently
// - Using custom transports (TLS, Unix sockets)
// - Need to inspect connection results
// - Integrating with non-standard runtimes
// - Want fine-grained connection management
// - Implementing custom graceful shutdownChoose Server for simplicity; Http for control.
// Server (high-level):
// ✓ Automatic accept loop
// ✓ Built-in graceful shutdown helper
// ✓ Simple API: bind().serve()
// ✓ Builder methods for configuration
// ✗ Less control per connection
// ✗ Tied to TcpListener
// Http (low-level):
// ✓ Per-connection configuration
// ✓ Protocol upgrade handling
// ✓ Custom transport support
// ✓ Connection result inspection
// ✓ Transport agnostic
// ✗ Manual accept loop
// ✗ More boilerplate
// ✗ Manual graceful shutdownServer for simplicity; Http for control.
Architectural relationship:
Server::bind().serve() internally creates TcpListener and uses Http::serve_connectionServer is a convenience wrapper around HttpConfiguration capabilities:
http1_keepalive, http2_only, and other HTTP optionsHttp allows different configuration per connectionServer applies configuration uniformly to all connectionsKey differences:
Server: automatic accept loop, simple API, built-in shutdownHttp: manual accept, per-connection control, protocol upgradesUse Server for:
Use Http for:
Key insight: The Server type is a high-level convenience that handles the accept-serve-spawn loop automatically, while Http is the low-level primitive that actually handles HTTP on a single connection. When you call Server::bind().serve(), it creates a TcpListener, accepts connections in a loop, and for each one creates an Http instance and calls serve_connection. If you need to customize this process—like wrapping streams with TLS before serving, handling WebSocket upgrades explicitly, or managing graceful shutdown yourself—you drop down to Http and implement the accept loop manually. The separation means Http can work with any stream type that implements AsyncRead + AsyncWrite, making it suitable for testing with mock streams or using with non-TCP transports, while Server is specifically designed for TCP listeners in production HTTP servers.