跳转到内容

Extractor 类型级管线

Extractor 把“从请求中取值并验证”编码进 handler 参数类型。它不是运行时参数名绑定,而是一条由 trait 和 tuple 宏在编译期拼出的提取管线。

先给答案:Extractor 是在进入 Handler 前逐步消费并验证请求

Section titled “先给答案:Extractor 是在进入 Handler 前逐步消费并验证请求”

路由匹配先确定 Handler,随后 extractor 按参数顺序从 URI、path、headers、extensions 和 body 中取数据。每个 extractor 要么返回转换后的类型,要么提前生成拒绝响应;body 通常只能消费一次,所以 Json<T>、Bytes 等 extractor 的组合顺序会影响结果。

这套设计把解析和业务分开,并让错误在类型层被看见。排查提取失败时应先看路由是否匹配,再看 extractor 的来源、顺序、Content-Type 和错误转换,而不是直接进入 handler 逻辑。

Request<Body>
-> split into Parts + Body
-> FromRequestParts: Path / Query / State / Header...
-> FromRequest: Json / Form / Bytes / Request...
-> tuple -> Handler::call

FromRequestParts<S> 定义在 axum-core/src/extract/mod.rs:53-70,只拿 &mut Parts;FromRequest<S> 位于 extract/mod.rs:79-96,接收完整 Request。tuple 实现放在 axum-core/src/extract/tuple.rs,前面的参数走 parts,最后一个参数可以消费 body。

类型 协议 关键坐标 失败结果
Path<T> FromRequestParts axum/src/extract/path/mod.rs:153-186 路径反序列化 rejection
Query<T> FromRequestParts axum/src/extract/query.rs:41-63 query 反序列化 rejection
State<S> FromRequestParts axum/src/extract/state.rs:301-324 通常是 Infallible
Json<T> FromRequest axum/src/json.rs:97-137 content-type、语法、数据错误
Extension<T> FromRequestParts axum/src/extension.rs:72-104 extension 缺失
DefaultBodyLimit Layer/extractor 辅助 axum-core/src/extract/default_body_limit.rs:77-96 body 超限

Json<T> 先检查 JSON content type,再通过 Bytes::from_request 收集 body,最后交给 serde_path_to_error(axum/src/json.rs:99-137)。Path<T> 则从 extensions 中取得路由阶段写入的 URL 参数(extract/path/mod.rs:157-186)。

顺序为什么是类型错误而不是运行时错误

Section titled “顺序为什么是类型错误而不是运行时错误”

body 只能被一个提取器完整消费。Axum 通过 handler tuple 实现要求“前 N 个实现 FromRequestParts,最后一个实现 FromRequest”,让两个 body extractor、或把 body extractor 放在中间,尽量在编译期失败。

为什么不把所有提取器都设计成 &Request

Section titled “为什么不把所有提取器都设计成 &Request”

替代方案:所有参数只拿不可变请求引用,需要 body 时内部缓存。

为什么不行:会隐藏 body buffering、复制与大小上限,流式 body 也难表达所有权转移。

证据:两个 trait 明确分离 &mut Parts 与拥有所有权的 Request(axum-core/src/extract/mod.rs:53、:79),资源消费边界直接体现在签名中。

每个 extractor 有关联类型 Rejection: IntoResponse。提取失败不需要抛异常;tuple 管线立即把 rejection 转响应。需要统一错误格式时,应包装自定义 extractor 或实现 rejection 到领域错误的映射,而不是在每个 handler 重复 match。

场景 现象 原因 规避
Json 不是最后一个参数 Handler trait 不满足 body 所有权已被消费 parts extractor 放前,body extractor 放最后
大 body 被整体收集 内存峰值升高 Json/Bytes 是聚合式提取 设置 limit;大上传改 streaming/multipart
State 类型推导失败 编译器无法确定子状态 FromRef 路径不唯一或缺失 显式 state 类型并实现 FromRef
可选参数吞掉真实错误 业务误判为缺失 Option<T> 会改变 rejection 语义 区分 optional 与 invalid

把解析、验证、鉴权前置为带错误类型的参数转换,可让业务函数只接收已验证值。关键是把“只借用元数据”和“消费数据流”的能力拆成不同 trait,以类型系统约束副作用。

面试锚点

  • FromRequestParts 与 FromRequest 为什么分开?
  • 为什么 body extractor 必须放最后?
  • Rejection 如何变成 HTTP 响应?