Tower-HTTP is a powerful HTTP middleware library in the Rust ecosystem, built on top of the Tower framework. It focuses on providing HTTP-specific tools and middleware to help developers easily handle common web development needs such as CORS, response compression, request tracing, and header validation. With a modular layer design, it supports a highly composable middleware stack suitable for both server and client scenarios. This guide will gradually explain the theoretical foundations, configuration methods, practical usage of Tower-HTTP, and provide complete code examples. Whether you are a beginner or an experienced developer, you will benefit from it.
1. Introduction: What are Tower and Tower-HTTP?
Basic Theory of Tower
Tower is an asynchronous service framework in Rust that defines a core abstraction: <span>Service</span> trait. This is an asynchronous function interface that describes how to handle requests (Request) and return responses (Response). The design philosophy of Tower is composability: wrapping services with “layers” to achieve middleware stacking.
- Service: the core trait, with a signature similar to
<span>async fn call(&mut self, req: Request) -> Result<Response, Error></span>. It processes input requests and produces output responses. - Layer: a functional abstraction that takes a Service and returns a new Service (with additional behavior). Layers can modify requests/responses, add logging, handle errors, etc.
- Advantages: Tower is framework-agnostic, compatible with web frameworks like Hyper, Axum, Warp; supports both client and server sides; and is asynchronous-friendly (based on Tokio).
Positioning and Value of Tower-HTTP
Tower-HTTP is the HTTP extension library of Tower, focusing on specific functionalities of the HTTP protocol. It does not provide a complete web server but acts as a “toolbox” for developers to quickly add production-grade features. Version 0.6.6 (check specific version) introduces more optimizations, such as better compression support and tracing integration.
- Core Value:
- Efficiency: All middleware are zero-cost abstractions, optimized at compile time to avoid runtime overhead.
- Reusability: Layers can be shared across projects, reducing boilerplate code.
- Production-Ready: Built-in safety (e.g., sensitive header masking), observability (Tracing, Metrics), and performance optimizations (compression, rate limiting).
- Applicable Scenarios: Building REST APIs, gRPC gateways, microservice proxies, etc. Compared to using Hyper directly, it simplifies 80% of common pain points.
Theoretically, Tower-HTTP follows the “onion model”: requests flow from the outer layer (the earliest added middleware) to the core (the core handler), and responses flow in reverse. This ensures separation of concerns: the outer layer handles cross-cutting concerns (like logging), while the core focuses on business logic.
2. Installation and Basic Usage
Installation Configuration
Add dependencies in <span>Cargo.toml</span>. All features of Tower-HTTP are disabled by default and can be enabled through feature flags to reduce binary size.
[dependencies]
tower-http = { version = "0.6.6", features = [
"trace", # Enable tracing
"compression-gzip", # Enable Gzip compression
"cors", # Enable CORS
"full" # Or enable all (not recommended for production, increases size)
] }
tower = "0.4" # Core Tower
http = "1.0" # HTTP types
http-body-util = "0.1" # Body handling
bytes = "1.0" # Byte buffer
tokio = { version = "1", features = ["full"] } # Asynchronous runtime
tracing = "0.1" # Logging tracing (optional)
- Feature Description:
<span>full</span>enables all, but in production, it is recommended to enable as needed (e.g., only use<span>trace</span>and<span>cors</span>). Run<span>cargo build</span>to verify installation.
Basic Usage: Hello World Example
Start with a simple handler and add a layer.
use tower::{ServiceBuilder, service_fn};
use http::{Request, Response};
use http_body_util::Full;
use bytes::Bytes;
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Simple handler: returns "Hello, World!"
async fn handler(_req: Request<Full<Bytes>>) -> Result<Response<Full<Bytes>>, Box<dyn Error>> {
let response = Response::builder()
.body(Full::new(Bytes::from("Hello, World!")))
.unwrap();
Ok(response)
}
// Build service using ServiceBuilder (no layers)
let service = ServiceBuilder::new().service_fn(handler);
// Simulate call (bind with Hyper server in practice)
let request = Request::builder()
.uri("http://localhost:3000/")
.body(Full::new(Bytes::new()))
.unwrap();
let response = service.call(request).await?;
println!("Response: {:?}