The Rust HTTP Stack
Related libraries can share protocol vocabulary while serving consumers at very different abstraction levels.
The Rust HTTP ecosystem is more instructive as a lineage than as a list of competing clients and servers. Each layer chooses a different consumer and stops at a different boundary.
application policy
┌─────────────┴─────────────┐
Reqwest Axum
convenient HTTP client routing and extraction
│ │
└──────────┬────────────┘
│
Hyper
HTTP connections and protocol
│
http + http-body
protocol values + streaming body contract
Tower Service and Layer compose across the stack
http: vocabulary without transport
The http crate defines Request<T>, Response<T>, Method, Uri,
StatusCode, HeaderMap, and related values. It does not open sockets.
That separation gives unrelated clients, servers, middleware, and tests a shared protocol language. The generic body parameter is especially important: the protocol structure can remain stable while each environment selects its own body representation.
Design question: Which concepts are intrinsic HTTP values, and which belong to a network implementation or framework policy?
http-body: streaming as a contract
An HTTP body may not fit in memory and may arrive over time. http-body
represents that behavior without dictating one executor, buffer type, or
connection implementation.
This is a narrower and more infrastructural consumer API than Reqwest’s
.text() or .json(). Its value lies in allowing libraries to interoperate at
the streaming boundary.
Design question: When should an ecosystem standardize a small trait rather than a convenient concrete type?
Hyper: the protective protocol engine
Hyper implements asynchronous HTTP/1 and HTTP/2 client and server connections. Its documentation intentionally calls it lower-level and recommends Reqwest to consumers seeking a convenient HTTP client.
A Hyper consumer chooses more pieces: connection I/O, body types, executors or runtime adapters, and how response frames are collected. That is not worse API design; it serves library authors and applications that need control below Reqwest’s policy layer.
Design question: How can a low-level API prevent incorrect protocol use without claiming ownership of TLS, runtime, DNS, or application policy?
Reqwest: a batteries-included client
Reqwest adds reusable connection management and the conveniences application authors expect: URL conversion, headers, authentication, redirects, proxies, TLS choices, JSON, forms, multipart bodies, cookies, and async or blocking clients.
#![allow(unused)]
fn main() {
let user = client
.get("https://api.example.test/users/42")
.send()
.await?
.error_for_status()?
.json::<User>()
.await?;
}
The fluent chain is policy-rich precisely because Hyper and http handle
lower-level roles. Reqwest can focus on the application consumer’s mental
sequence.
Design question: Which defaults and conveniences belong in a high-level client, and where must escape hatches preserve lower-level control?
Axum: functions become HTTP handlers
Axum occupies the ergonomic server side. Handler parameters are extractors; return values implement response conversion.
#![allow(unused)]
fn main() {
async fn create_user(
Json(input): Json<CreateUser>,
) -> (StatusCode, Json<User>) {
let user = User::from(input);
(StatusCode::CREATED, Json(user))
}
}
The signature is both executable code and a request/response schema. Axum’s notable architectural choice is to reuse Tower for middleware instead of inventing a framework-specific middleware system.
Design question: How do function arguments become a declarative extraction plan while preserving ordinary async function ergonomics?
Tower: composition across domains
Tower’s core abstraction is more general than HTTP:
Request → Future<Result<Response, Error>>
Hyper connections, Axum routers, RPC clients, and many middleware components
can participate in that shape. Layer transforms one service into another,
allowing timeouts, tracing, limits, retries, or authorization to wrap business
behavior.
Design question: Is a highly generic abstraction worth its type complexity when it enables middleware reuse across an ecosystem?
What the lineage teaches
The APIs become clearer when each crate refuses responsibilities owned by the next layer:
| Layer | Gives its consumer | Deliberately does not own |
|---|---|---|
http | protocol value types | I/O and runtime |
http-body | streaming body interface | collection and decoding policy |
| Hyper | HTTP connection machinery | batteries-included application ergonomics |
| Reqwest | convenient client workflow | server routing |
| Axum | routing, extraction, responses | bespoke transport and middleware stacks |
| Tower | service/middleware composition | HTTP-specific semantics |
The recurring design lesson is abstraction by responsibility, not merely abstraction by hiding detail. Each public boundary should give one class of consumer enough control without forcing every consumer to assemble the layer below it.
Sources
- Hyper documentation describes Hyper as a lower-level building block and recommends Reqwest for a convenient client.
- Reqwest documentation describes its higher-level client conveniences.
- Axum repository describes its ergonomic, modular routing and its reuse of Tower middleware.
httpdocumentation documents the transport-independent protocol types shared by the stack.- Tower documentation documents
ServiceandLayercomposition.