axum 解决什么问题
主线位置:两条主线的外面。本页回答 axum 在一个 Web 程序里管哪一段:装配主线上它提供
Handler、Router、提取器这些积木,请求主线上它只负责从「拿到一个Request」到「交出一个Response」这一段。
flowchart TB fns["你的 async fn"] --> axum["axum:路由、提取器、响应"] axum --> core["axum-core:提取器与响应 trait"] axum --> svc["tower Service 与 Layer"] thttp["tower-http 中间件"] --> svc axum --> serve["axum::serve"] serve --> hutil["hyper-util auto::Builder"] hutil --> hyper["hyper:一条连接上的 HTTP"] hyper --> tokio["tokio:IO、任务、定时器"]
一句话定位
axum is an HTTP routing and request-handling library that focuses on ergonomics and modularity.
「routing and request-handling」划出了 axum 的范围:给它一个已经解析好的 http::Request,它决定交给哪个函数、怎么把请求拆成函数的参数、怎么把返回值变成 http::Response。连接、HTTP/1 和 HTTP/2 的报文、keep-alive 都不归它管,那是 hyper 卷的内容;axum::serve 只是一个很薄的 accept 循环,把每条连接交给 hyper-util。
| 事情 | 谁来做 |
|---|---|
| 接受连接,每条连接 spawn 一个任务 | axum::serve(或者你自己写) |
| 解析和编码 HTTP 报文,一个端口同时接 HTTP/1 和 HTTP/2 | hyper、hyper-util |
| 路径匹配 | matchit(axum 包了一层 PathRouter) |
| 按方法分发、405、fallback | axum 的 MethodRouter、Router |
| 把请求拆成函数参数 | axum 的提取器(trait 在 axum-core) |
| 把返回值变成响应 | IntoResponse(trait 在 axum-core) |
| 超时、压缩、CORS、日志等中间件 | tower、tower-http |
五条「High-level features」各落在哪
crate 文档和 README 开头列了五条,每条都能对上一组具体的类型:
- Route requests to handlers with a macro-free API.
- Declaratively parse requests using extractors.
- Simple and predictable error handling model.
- Generate responses with minimal boilerplate.
- Take full advantage of the [`tower`] and [`tower-http`] ecosystem of
middleware, services, and utilities.
不用宏的路由
路由表是在 main 里用普通的方法调用一步步造出来的值,不是编译期由属性宏收集的。lib.rs 第 51 行的例子:
use axum::{Router, routing::get};
// our router
let app = Router::new()
.route("/", get(root))
.route("/foo", get(get_foo).post(post_foo))
.route("/foo/bar", get(foo_bar));
// which calls one of these handlers
async fn root() {}
async fn get_foo() {}
async fn post_foo() {}
async fn foo_bar() {}
root、get_foo 都是没有任何标注的 async fn。get(root) 能编译,是因为 axum 用 impl_handler! 为「最多 16 个参数、每个参数都是提取器、返回值实现 IntoResponse」的函数实现了 Handler trait。路由表既然是普通的值,就可以写在函数里返回、按配置决定加不加某条路由、用 nest 和 merge 拼起来。代价是报错:一个参数类型不对,rustc 只能说「Handler<_, _> 不满足」,于是有了可选的 #[debug_handler](见源码地图)。
声明式的提取器
handler 的参数类型就是「我要请求里的什么」:Path<u32> 要路径参数,Query<T> 要查询串,Json<T> 要 JSON body,State<S> 要应用状态。axum 在调用你的函数之前依次运行每个参数类型的提取逻辑,任何一个失败就直接回响应,函数根本不会被调用。两个 trait FromRequestParts、FromRequest 的区别和「为什么 body 只能给最后一个参数」见 FromRequestParts 与 FromRequest。
错误也是响应
src/docs/error_handling.md 解释了 axum 的错误模型从哪来:
axum is based on [`tower::Service`] which bundles errors through its associated
`Error` type. If you have a [`Service`] that produces an error and that error
makes it all the way up to hyper, the connection will be terminated _without_
sending a response. This is generally not desirable so axum makes sure you
always produce a response by relying on the type system.
axum does this by requiring all services have [`Infallible`] as their error
type. `Infallible` is the error type for errors that can never happen.
这条要求写在类型约束里。Router::layer 要求套上中间件之后的服务满足 Error: Into<Infallible>,route_service、fallback_service 直接要求 Error = Infallible;handler 的 Future 输出的是 Response,根本没有错误通道。一个会返回 BoxError 的 tower 中间件(比如 Timeout)不能直接 .layer,要先用 HandleErrorLayer 把错误转成响应,见 layer 与 route_layer。
问: handler 写成 async fn h() -> Result<String, StatusCode>,返回 Err(StatusCode::NOT_FOUND) 难道不是错误吗?
答: 对 axum 来说不是。axum-core 为 Result 实现了 IntoResponse(into_response.rs 第 138 行),只要 T 和 E 都能变成响应:
impl<T, E> IntoResponse for Result<T, E>
where
T: IntoResponse,
E: IntoResponse,
{
fn into_response(self) -> Response {
match self {
Ok(value) => value.into_response(),
Err(err) => err.into_response(),
}
}
}
Err 分支和 Ok 分支一样被转成一个普通的 Response,发给客户端。错误文档的原话是「It doesn't matter whether you return Err(StatusCode::NOT_FOUND) or Err(StatusCode::INTERNAL_SERVER_ERROR). These are not considered errors in axum.」Result 在这里只是让你能在 handler 里用 ?;tower 意义上的「错误」(会让 hyper 直接断开连接的那种)在 axum 的服务树里不存在。
少写样板的响应
&'static str 变成 200 OK 加 content-type: text/plain; charset=utf-8,Json<T> 变成 application/json,(StatusCode, Json<T>) 这样的元组一次给出状态码和 body。这些都是 IntoResponse 的现成实现,见 IntoResponse 与 IntoResponseParts。
中间件就是 tower
第五条是 axum 自己最强调的一条。crate 文档紧接着写:
In particular, the last point is what sets `axum` apart from other libraries / frameworks.
`axum` doesn't have its own middleware system but instead uses
[`tower::Service`]. This means `axum` gets timeouts, tracing, compression,
authorization, and more, for free. It also enables you to share middleware with
applications written using [`hyper`] or [`tonic`].
落到代码上:Router::layer 的参数类型是 L: Layer<Route>,就是 tower 的 Layer;Route 是一个 BoxCloneSyncService<Request, Response, E>;Router<()> 自己也实现了 tower 的 Service<Request<B>>。axum 的 from_fn 只是「用一个 async 函数写 tower 中间件」的便利工具,产物仍然是一个 Layer。所以 tower-http 的中间件不需要知道 axum 的存在:tower-http 的 crate 文档说它的中间件只依赖 http 和 http-body 两个 crate,「compatible with any library or framework that also uses those crates, such as hyper, tonic, and warp」。
代价也来自 tower:中间件的错误类型必须能转成 Infallible(上一节),连续 .layer 的顺序和 ServiceBuilder 相反(见 tower 卷的层的顺序反了)。
不追求运行时无关
hyper 不绑定运行时,axum 绑定。lib.rs 第 18 行的「Compatibility」一节只有两句:
axum is designed to work with [tokio] and [hyper]. Runtime and
transport layer independence is not a goal, at least for the time being.
axum::serve 用的是 tokio 的 TcpListener 和 tokio::spawn,ConnectInfo、SSE 的 keep-alive 也都要 tokio feature。不过 Router 本身只是一个 tower Service:关掉默认 feature,axum 不依赖 tokio 和 hyper,Router 照样能用 oneshot 调用(源码地图里有依赖树的对比)。
性能方面,README 的「Performance」一节说 axum 是「a relatively thin layer on top of hyper and adds very little overhead」,因此「axum's performance is comparable to hyper」。它给了两个第三方基准的链接,没有给自己的数字,本卷也不引用。
和其他框架
axum 自己的文档、README、CHANGELOG 和 ECOSYSTEM.md 里,没有和 actix-web、Rocket、warp 的逐项比较;唯一的对比性说法就是上面那句「what sets axum apart from other libraries / frameworks」,指的是中间件直接用 tower。本卷也就不做逐项比较,只记两处能查到出处的交集:
Router::nest上有一个文档别名#[doc(alias = "scope")],注释是「Some other libs like actix-web use this term」。在 docs.rs 上搜scope能找到nest。这个别名由 #2756 加入,最早出现在 axum 0.7.6。- tower-http 的文档把 warp 列为能直接使用它的框架之一(上一节的引文):warp 也建在 http、http-body 这套类型上。
小结
- axum 管「
Request进来、Response出去」之间的路由和请求处理;连接与协议在 hyper,路径匹配在 matchit,中间件在 tower / tower-http。 - 路由不用宏:路由表是运行时造出来的普通值,handler 是普通
async fn,靠impl_handler!宏实现的Handlertrait 接进来;代价是报错难读。 - 所有服务的错误类型都是
Infallible:错误一旦到了 hyper 就只能断开连接,所以 axum 用类型系统保证每个请求都有响应;Result<T, E>只是两种都能变成响应的返回值。 - axum 没有自己的中间件系统:
.layer收 tower 的Layer,Route是BoxCloneSyncService,tower-http 的中间件直接可用。 - axum 绑定 tokio 和 hyper,文档明说运行时无关「not a goal」;但
Router本身只是一个 towerService。 - axum 的文档里没有和 actix-web、Rocket、warp 的逐项比较,本卷不补。