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 不是合法 JSON | 400 “Failed to parse JSON” |
Json<T> | 缺少 Content-Type header | 415 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)。