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

One Request, Fully Traced

Consider this handler:

async fn create_user(
    State(db): State<Arc<Database>>,
    Path(team): Path<String>,
    Json(input): Json<CreateUser>,
) -> Result<(StatusCode, Json<User>), ApiError> { /* ... */ }

1. A connection gets its own task

serve awaits Listener::accept, constructs a service for the connection, adapts Tower’s service to Hyper, and asks its Executor to run the connection future. The default executor calls tokio::spawn.

Source: serve/mod.rs:563

2. Hyper calls the router as a service

Hyper owns HTTP parsing and connection behavior. When a request is available, the adapter calls Router<()> through Tower’s Service<Request<B>>. Axum normalizes the body to its own Body and enters call_with_state.

Source: routing/mod.rs:599

3. Routing refines the destination twice

PathRouter matches the URI path, stores captured parameters in request extensions, then forwards to an endpoint. MethodRouter selects the GET, POST, or other method service. A failed path or method becomes a fallback response rather than an escaped routing error.

Sources: path_router.rs:325, method_routing.rs:1200

4. The handler adapter splits the request

The macro-generated Handler implementation separates Parts from Body. It runs State and Path through FromRequestParts, in argument order. Each may inspect or mutate metadata, but cannot consume the body.

The final Json argument implements FromRequest and receives the reconstructed whole request. This type distinction makes “the body can be consumed once” a compile-time rule.

Source: handler/mod.rs:221

5. The handler future is awaited

Only after all extraction succeeds does Axum call create_user. Awaiting the database does not block the worker thread: the request future returns Pending, and Tokio may use that thread to poll other ready tasks.

No new task is created merely because the handler is async. It is one nested future inside Hyper’s connection machinery.

6. One concrete response type leaves the boundary

The handler may return many Rust types, but IntoResponse normalizes them. Both Ok((StatusCode, Json<User>)) and Err(ApiError) implement IntoResponse, so Result<T, E> can become the single HTTP Response expected by the service.

Source: into_response.rs:141

accept socket → spawn connection → Hyper parses request → Router::call
  → match path → match method → extract parts → consume body
  → await handler → IntoResponse → Hyper writes response body

This is sequential dependency inside one request. Concurrency appears because many connection and request futures can be suspended and polled independently.