Rust Web 开发 II

响应、路由、中间件

技术
技术 Rust Web 开发 后端框架

2026-04-05

上一篇文章,我简要概述了 Axum 框架的基本组成。这篇文章,我将详细展开讲讲响应、路由和中间件。

响应

在 Axum 中,处理器的返回值会被自动转换成 http::response::Response。这个转换过程由 IntoResponse 特征实现。所以只有实现 IntoResponse 的类型才能作为处理器的返回值。

一些常见类型已经实现了 IntoResponse,比如 String&'static strVec<u8>&'static [u8]StatusCode() 等等。因此它们可以直接从处理器返回。

Result<T, E> 也实现了 IntoResponse,只要 TE 同时实现 IntoResponse 就行。

use axum::{
    Router,
    body::{self, Bytes},
    routing::get,
    http::StatusCode,
    response::{IntoResponse, Response},
};

enum MyError {
    SomethingWentWrong,
    SomethingElseWentWrong,
}

impl IntoResponse for MyError {
    fn into_response(self) -> Response {
        let body = match self {
            MyError::SomethingWentWrong => "something went wrong",
            MyError::SomethingElseWentWrong => "something else went wrong",
        };

        // Axum 提供了 `(StatusCode, R: IntoResponse)` 的实现
        (StatusCode::INTERNAL_SERVER_ERROR, body).into_response()
    }
}

// 创建路由
let app = Router::new().route("/", get(handler));

// 一个处理器
async fn handler() -> Result<(), MyError> {
    Err(MyError::SomethingWentWrong)
}

关于响应还有一个重要的特征:IntoResponseParts——它负责为响应添加响应头和一些自定义扩展数据。

可以将一个或多个 IntoResponseParts 类型连续放入元组中,并且只要元组的末位元素是 IntoResponse 的(首位元素可以是 IntoResponseParts 的类型,也可以是 StatusCode 等类型),这个元组就能在处理器中返回。


如果你想追求更高的灵活性,也可以手动构造 http::response::Response

路由

路由(routing)是分发请求的中枢。所有发往服务器的请求都要先过路由这一关,它负责将请求按照其 URL 路径和请求方法进行匹配,匹配成功就让对应的中间件(如果有的话)和处理器去处理,否则就返回 404 Not Found

Axum 中,路由由 Router 实现。

使用 route 方法注册路由

一个 Router 能使用 .route 方法注册多个路由,这个方法接收一条 &str 路径和一个 MethodRouter 实例。

  1. 路径可以是静态的(例如 //foo/users/123),也可以是捕获性的,还可以是通配符

    • 捕获性的路径需使用花括号包裹路径段,它可以将对应的路径参数捕获为一个同名变量,后续可以使用提取器在处理器中使用这个变量。

      比如 /{key} 会匹配像 /mykey/1234 这样的路径,并将字符串 mykey1234 存储在变量 key 当中;同理,/users/{id}/tweets 会匹配路径 /users/1104/tweets 并将 1104 存储在变量 id 当中。

    • 通配符路径只需要在捕获性路径的捕获变量前加 * 即可,它会匹配剩余所有路径段,但不会匹配空路径段。相比捕获性路径,它只能出现在路径末尾;它和前者一样有捕获功能。

      比如 /{*key} 会匹配 /a/a/ 等路径,但不匹配 //assets/{*path} 不匹配 /assets/,但匹配 /assets/styles.css/assets/image/img.jpg 等。

  2. MethodRouter 实例通常是借助方法路由函数构造的。

    方法路由是一系列在 axum::routing::method_routing 中定义的函数,它们都接收一个处理器并返回一个 MethodRouter 实例。

    use axum::{
        routing::any,
        Router,
    };
    
    /// 这是一个处理器
    async fn handler() {}
    
    // 创建路由,匹配 `/` 路径
    //
    // 这里的 `any` 函数就是方法路由
    // 它接收一个处理器并构造出 `MethodRouter`
    let app = Router::new().route("/", any(handler));
    

    以下是常用的方法路由:

    函数名 含义
    any 匹配任意请求方法
    get 只匹配 GET 请求
    post 只匹配 POST 请求

    方法路由还允许进行链式调用。

    async fn handler() {}
    
    async fn other_handler() {}
    
    // `POST /` 请求交给 `other_handler`,其它则都由 `handler` 处理
    let app = Router::new().route("/", any(handler).post(other_handler));
    

路由的嵌套与合并

使用 .nest 方法可以将一个 Router 嵌套到另一个 Router 中,这样就可以将多个路由组合成一个更大的路由。

这在构建大型复杂 Web 应用时很有用,因为它可以允许我们将路由划分为多个模块,便于管理和维护。

use axum::{
    routing::{get, post},
    Router,
};

let user_routes = Router::new().route("/{id}", get(|| async {}));

let team_routes = Router::new().route("/", post(|| async {}));

let api_routes = Router::new()
    .nest("/users", user_routes)
    .nest("/teams", team_routes);

let app = Router::new().nest("/api", api_routes);

// 路径在路由嵌套时会被拼接起来,因此
// `GET /api/users/{id}` 会由 `user_routes` 处理
// `POST /api/teams` 会由 `team_routes` 处理

使用 .merge 方法可以将一个 Router 与另一个 Router 合并。这两个路由的路径会被平行地合并,而不是像 .nest 那样进行嵌套拼接。

同样地,这在构建大型复杂 Web 应用时对我们很有用。

use axum::{
    routing::get,
    Router,
};

let user_routes = Router::new()
    .route("/users", get(users_list))
    .route("/users/{id}", get(users_show));

let team_routes = Router::new()
    .route("/teams", get(teams_list));

let app = Router::new()
    .merge(user_routes)
    .merge(team_routes);
// 也可以写作 `user_routes.merge(team_routes)`

// 现在会匹配 `GET /users`、`GET /users/{id}`、`GET /teams`

为路由添加全局变量

参考资料