Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Rust HTTP Stack

Related libraries can share protocol vocabulary while serving consumers at very different abstraction levels.

httphttp-bodyHyperReqwestAxumTower

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:

LayerGives its consumerDeliberately does not own
httpprotocol value typesI/O and runtime
http-bodystreaming body interfacecollection and decoding policy
HyperHTTP connection machinerybatteries-included application ergonomics
Reqwestconvenient client workflowserver routing
Axumrouting, extraction, responsesbespoke transport and middleware stacks
Towerservice/middleware compositionHTTP-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