Why Is It Designed This Way?
This chapter is the center of the case study. Each mechanism protects a specific invariant.
Why build on Tower instead of inventing Axum middleware?
HTTP middleware is service transformation: receive a request, decide when and whether to call an inner service, then transform its response or error. Tower already defines that protocol and a reusable ecosystem. Axum gains composition without coupling routing to each timeout, trace, limit, or retry implementation.
Why are handlers converted to services?
Hyper should not understand Rust function arguments such as State or Json.
The handler adapter translates one ergonomic function into the uniform service
shape used by routing and middleware. The preserved invariant is: everything
below the adapter speaks HTTP requests, futures, and responses.
Why can only the final extractor consume the body?
An HTTP request body is a stream, not clonable data. Two independent extractors
cannot both consume it without buffering and replay policy. The split between
FromRequestParts and FromRequest makes single ownership visible to the
compiler.
Why run extractors sequentially?
Extractors can depend on request extensions produced by earlier middleware or
extractors, and metadata extractors share mutable Parts. Sequential order
gives deterministic short-circuiting and avoids locking one small per-request
object. Independent external I/O belongs inside a purpose-built extractor or
handler where join! can express that independence explicitly.
Why must failures become responses?
At the HTTP boundary, invalid input, authorization denial, not-found routing, and application failure all still require a valid response. An infallible outer service guarantees Hyper does not need application-specific error knowledge.
Why is Router always ready?
The router cannot ask the selected endpoint about capacity until it sees the request path and method. Waiting for every possible endpoint would let one unready route stall unrelated routes. Axum chooses always-ready routing and requires capacity policy to be installed at a meaningful layer.
Why clone services per request or connection?
Tower services often require mutable access to call, while the server must
handle concurrent work. Cheaply cloned handles allow each in-flight operation
to own the service value it polls. Shared expensive resources live behind those
handles rather than inside a global router lock.
Why box routes after emphasizing generics?
Every combination of handler and middleware has a distinct concrete type. A router must store many unlike endpoints in one collection and keep compiler output manageable. Static types verify the boundary; internal type erasure provides heterogeneity and stable storage.
Why doesn’t Axum spawn each handler?
The caller already owns and polls a request future. Spawning again would detach cancellation and tracing context, require another join mechanism, and add scheduling overhead. Spawn only when work genuinely needs an independent lifecycle.
Why is graceful shutdown cooperative?
Forcibly stopping a future at an arbitrary instruction would violate resource and protocol invariants. The server stops admission, asks connections to drain, and waits for ownership to be released. Applications add deadlines when bounded shutdown matters more than completing every request.