跳转到正文
zeno's blog
返回

Rust Web:Axum Extractor Rejection-Handler 执行前的守门员

专题: Rust Web

Table of contents

Open Table of contents

Rejection 是什么

extractor 从请求中提取数据时,可能失败。失败时产生的东西叫 Rejection

Rejection 自身实现了 IntoResponse,所以它可以直接变成 HTTP 错误响应发回客户端。handler 从未被调用。

正常请求:
  Extractor 成功 → 拿到参数 → 调用 handler → 返回响应

无效请求:
  Extractor 失败 → 产生 Rejection → Rejection.into_response() → 直接返回 4xx

                                handler 从未执行

具体例子

客户端发了一个无效 JSON:

POST /items HTTP/1.1
Content-Type: application/json

{"name": "AK-47", quantity: 1}    ← 语法错误,quantity 没加引号

axum 在调用 handler 之前先调 Json::from_request()

// Json extractor 内部做的事(简化)
1.body 字节
2. serde_json::from_slice::<CreateItem>(&bytes)
3. 反序列化失败!
4. 返回 Err(JsonRejection::JsonSyntaxError)

axum 收到 Rejection:

JsonRejection::JsonSyntaxError 实现了 IntoResponse
→ 变成 400 Bad Request + "Failed to parse the request body as JSON"
→ 直接发回客户端
→ handler 函数根本没被调用

各 extractor 的 Rejection

Extractor失败场景Rejection 响应
Path<u32>路径参数不是合法数字400 “Invalid URL path parameter”
Query<T>查询参数缺失或格式错误400 “Failed to deserialize query string”
Json<T>body 不是合法 JSON400 “Failed to parse JSON”
Json<T>缺少 Content-Type header415 Unsupported Media Type
Json<T>JSON 合法但字段不匹配422 Unprocessable Entity
自定义 BearerToken缺少 Authorization 头401 Unauthorized

Extractor 的设计哲学

把”请求合不合法”的检查前置到 extractor,handler 拿到的永远是已验证的干净数据。

async fn create_item(
    State(db): State<PgPool>,          // 拿不到连接池?不可能,编译期就保证了
    Path(player_id): Path<u32>,        // 路径参数不是合法 u32?400,不进来
    BearerToken(token): BearerToken,   // 没带 Authorization 头?401,不进来
    Json(body): Json<CreateItem>,      // body 不是合法 JSON?400,不进来
) -> Result<Json<Item>, AppError> {
    // 到这里时你已经确定:
    // - player_id 是一个 u32
    // - token 是一个合法字符串
    // - body 是一个已反序列化的 CreateItem 结构体
    //
    // 不需要写任何参数校验代码,直接写业务逻辑
}

对比:没有 extractor 的世界

async fn create_item(req: Request<Body>) -> Response {
    // 手动取路径参数
    let player_id = req.uri().path().split('/').nth(4)
        .ok_or("missing player_id")?
        .parse::<u32>()
        .map_err(|_| "invalid player_id")?;

    // 手动取 token
    let auth = req.headers().get("Authorization")
        .ok_or("missing auth header")?
        .to_str()
        .map_err(|_| "invalid header")?;
    let token = auth.strip_prefix("Bearer ")
        .ok_or("not a bearer token")?;

    // 手动读 body + 反序列化
    let bytes = hyper::body::to_bytes(req.into_body()).await?;
    let body: CreateItem = serde_json::from_slice(&bytes)
        .map_err(|_| "invalid json")?;

    // 业务逻辑终于开始...(已经写了 15 行样板代码了)
}

每个 handler 都要重复这堆校验逻辑。而且错误格式不统一——你手写的 "invalid player_id" 和别人手写的可能风格完全不同。

Extractor 把样板代码变成类型签名

没有 extractor:
  你声明 "我接收一个原始 Request"
  然后手动解析、校验、返回错误

有 extractor:
  你声明 "我需要一个 Path<u32> 和一个 Json<CreateItem>"
  axum 自动: 解析 → 校验 → 失败就返回标准化错误 → 成功才调你的函数

类比:你不需要自己检查餐厅预约信息——前台(extractor)帮你确认一切合法后才带客人到你面前(handler)。


分享这篇文章:

上一篇
Rust Web:Axum 优雅停机(Graceful Shutdown)
下一篇
Rust Web:Actix-web HTTP Server 指南