Skip to main content

什么是Axum提取器

一个处理函数(handler)是一个异步函数,它以任意数量的提取器(`extract`)作为参数

提取器(extract)是实现了 FromRequestFromRequestParts 的类型。

例如,Json是一个提取器用于消耗请求主体并将其反序列化为某种目标类型

use axum::{
extract::Json,
routing::post,
handler::Handler,
Router,
};
use serde::Deserialize;

#[derive(Deserialize)]
struct CreateUser {
email: String,
password: String,
}

async fn create_user(Json(payload): Json<CreateUser>) {
// ...
}

let app = Router::new().route("/users", post(create_user));

常见提取器

一些常用的提取器包括:

use axum::{
extract::{Request, Json, Path, Extension, Query},
routing::post,
http::header::HeaderMap,
body::{Bytes, Body},
Router,
};
use serde_json::Value;
use std::collections::HashMap;

// `Path`提供了**路径参数**并对其进行反序列化。
async fn path(Path(user_id): Path<u32>) {}

// `Query`会提供**查询参数**并对其进行反序列化。
async fn query(Query(params): Query<HashMap<String, String>>) {}

// `HeaderMap`提供了所有标头信息。
async fn headers(headers: HeaderMap) {}

// `String`消耗**请求正文**并确保它是有效的utf-8
async fn string(body: String) {}

// `Bytes`提供**原始请求正文**。
async fn bytes(body: Bytes) {}

// 我们已经使用了`Json`来解析**请求体**作为json
async fn json(Json(payload): Json<Value>) {}

// `Request`提供了**整个请求**,以实现最大控制。
async fn request(request: Request) {}

// `Extension` 从**请求扩展**中提取数据
// 这通常**用于与处理程序共享状态**
async fn extension(Extension(state): Extension<State>) {}

#[derive(Clone)]
struct State { /* ... */ }

let app = Router::new()
.route("/path/:user_id", post(path))
.route("/query", post(query))
.route("/string", post(string))
.route("/bytes", post(bytes))
.route("/json", post(json))
.route("/request", post(request))
.route("/extension", post(extension));

应用多个提取器

您还可以应用多个提取器:

use axum::{
extract::{Path, Query},
routing::get,
Router,
};
use uuid::Uuid;
use serde::Deserialize;

let app = Router::new().route("/users/:id/things", get(get_user_things));

#[derive(Deserialize)]
struct Pagination {
page: usize,
per_page: usize,
}

impl Default for Pagination {
fn default() -> Self {
Self { page: 1, per_page: 30 }
}
}

async fn get_user_things(
Path(user_id): Path<Uuid>,
pagination: Option<Query<Pagination>>,
) {
let Query(pagination) = pagination.unwrap_or_default();

// ...
}

提取器的顺序

提取器始终按照函数参数的顺序运行,即从左到右

请求体是一个只能消耗一次的异步流。 因此,您只能有一个消耗请求体的提取器。

Axum 通过要求这样的提取器作为处理程序接受的最后一个参数来强制执行这一点

示例

use axum::{extract::State, http::{Method, HeaderMap}};

async fn handler(
// `Method` 和 `HeaderMap` **不会消耗请求主体**,因此它们可以放在参数列表中的任何位置(但在 `body` 之前)
method: Method,
headers: HeaderMap,
// `State`也是一种提取器,因此它需要放在`body`之前。
State(state): State<AppState>,
// `String`消耗请求主体,因此必须是最后一个提取器。
body: String,
) {
// ...
}
warning

如果"String"不是最后一个提取器,则我们会收到编译错误。

use axum::http::Method;

async fn handler(
// this doesn't work since `String` must be the last argument
body: String,
method: Method,
) {
// ...
}
warning

这也意味着您不能两次消耗请求正文。

use axum::Json;
use serde::Deserialize;

#[derive(Deserialize)]
struct Payload {}

async fn handler(
// `String` and `Json` both consume the request body
// so they cannot both be used
string_body: String,
json_body: Json<Payload>,
) {
// ...
}

axum通过要求最后一个提取器实现FromRequest以及其他所有提取器实现FromRequestParts来强制执行此规定。

可选的提取器

在 axum 中定义的所有提取器将在请求不匹配时拒绝

如果您希望将提取器设置为可选,可以将其包装在 Option 中:

use axum::{
extract::Json,
routing::post,
Router,
};
use serde_json::Value;

async fn create_user(payload: Option<Json<Value>>) {
if let Some(payload) = payload {
// We got a valid JSON payload
} else {
// Payload wasn't valid JSON
}
}

let app = Router::new().route("/users", post(create_user));

将提取器(extractor)包装在 Result 中使其变成可选的,并提供提取失败的原因:

use axum::{
extract::{Json, rejection::JsonRejection},
routing::post,
Router,
};
use serde_json::Value;

async fn create_user(payload: Result<Json<Value>, JsonRejection>) {
match payload {
Ok(payload) => {
// 我们收到了一个有效的 JSON 负载
}
Err(JsonRejection::MissingJsonContentType(_)) => {
// 请求没有 `Content-Type: application/json` 头部
}
Err(JsonRejection::JsonDataError(_)) => {
// 无法将正文反序列化为目标类型
}
Err(JsonRejection::JsonSyntaxError(_)) => {
// 主体体内语法错误
}
Err(JsonRejection::BytesRejection(_)) => {
// 无法提取请求正文
}
Err(_) => {
// `JsonRejection` 标记为 `#[non_exhaustive]`,因此匹配模式必须包括一个捕获所有情况的情况。
}
}
}

let app = Router::new().route("/users", post(create_user));

自定义提取器响应

如果提取器失败,它将返回一个带有错误的响应,您的处理程序将不会被调用。

要自定义错误响应,您有两个选择:

  • 使用 Result<T, T::Rejection> 作为您的提取器,如“Optional extractors”中所示。 如果您仅在单个处理程序中使用提取器,则这将效果良好。
  • 创建自己的提取器,在其 FromRequest 实现中调用 axum 的一个内置提取器,但对于拒绝返回不同的响应。 有关更多详细信息, 请参阅 customize-extractor-error 示例。

访问内部错误

axum的内置提取器不会直接暴露内部错误。

这为我们提供了更大的灵活性,并允许我们在不破坏公共API的情况下更改内部实现。

例如,这意味着虽然 Json 是使用 serde_json 实现的, 但它并不直接公开包含在 JsonRejection::JsonDataError 中的 serde_json::Error 。 但是,仍然可以通过 std::error::Error 的方法来访问:

use std::error::Error;
use axum::{
extract::{Json, rejection::JsonRejection},
response::IntoResponse,
http::StatusCode,
};
use serde_json::{json, Value};

async fn handler(
result: Result<Json<Value>, JsonRejection>,
) -> Result<Json<Value>, (StatusCode, String)> {
match result {
// if the client sent valid JSON then we're good
Ok(Json(payload)) => Ok(Json(json!({ "payload": payload }))),

Err(err) => match err {
JsonRejection::JsonDataError(err) => {
Err(serde_json_error_response(err))
}
JsonRejection::JsonSyntaxError(err) => {
Err(serde_json_error_response(err))
}
// handle other rejections from the `Json` extractor
JsonRejection::MissingJsonContentType(_) => Err((
StatusCode::BAD_REQUEST,
"Missing `Content-Type: application/json` header".to_string(),
)),
JsonRejection::BytesRejection(_) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
"Failed to buffer request body".to_string(),
)),
// we must provide a catch-all case since `JsonRejection` is marked
// `#[non_exhaustive]`
_ => Err((
StatusCode::INTERNAL_SERVER_ERROR,
"Unknown error".to_string(),
)),
},
}
}

// 尝试提取内部 'serde_path_to_error::Error<serde_json::Error>'
// 如果成功,我们可以提供更具体的错误。
// `Json`使用`serde_path_to_error`,因此错误将被包装在`serde_path_to_error::Error`中。
fn serde_json_error_response<E>(err: E) -> (StatusCode, String)
where
E: Error + 'static,
{
if let Some(err) = find_error_source::<serde_path_to_error::Error<serde_json::Error>>(&err) {
let serde_json_err = err.inner();
(
StatusCode::BAD_REQUEST,
format!(
"Invalid JSON at line {} column {}",
serde_json_err.line(),
serde_json_err.column()
),
)
} else {
(StatusCode::BAD_REQUEST, "Unknown error".to_string())
}
}

// 尝试将 `err` 下转型为 `T`,如果失败则递归尝试将 `err` 的源头(source)下转型
fn find_error_source<'a, T>(err: &'a (dyn Error + 'static)) -> Option<&'a T>
where
T: Error + 'static,
{
if let Some(err) = err.downcast_ref::<T>() {
Some(err)
} else if let Some(source) = err.source() {
find_error_source(source)
} else {
None
}
}

请注意,虽然这种方法有效,但如果axum在内部使用不同的错误类型进行更改,将来可能会出现故障。 这种更改可能在没有主要破坏版本的情况下发生。

定义自定义提取器

您也可以通过实现 FromRequestPartsFromRequest定义自己的提取器

实现FromRequestParts

如果您的提取器不需要访问请求正文,请实现FromRequestParts

use axum::{
async_trait,
extract::FromRequestParts,
routing::get,
Router,
http::{
StatusCode,
header::{HeaderValue, USER_AGENT},
request::Parts,
},
};

struct ExtractUserAgent(HeaderValue);

#[async_trait]
impl<S> FromRequestParts<S> for ExtractUserAgent
where
S: Send + Sync,
{
type Rejection = (StatusCode, &'static str);

async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
if let Some(user_agent) = parts.headers.get(USER_AGENT) {
Ok(ExtractUserAgent(user_agent.clone()))
} else {
Err((StatusCode::BAD_REQUEST, "`User-Agent` header is missing"))
}
}
}

async fn handler(ExtractUserAgent(user_agent): ExtractUserAgent) {
// ...
}

let app = Router::new().route("/foo", get(handler));

实现 FromRequest

如果您的提取器需要消耗请求主体,则必须实现FromRequest

use axum::{
async_trait,
extract::{Request, FromRequest},
response::{Response, IntoResponse},
body::{Bytes, Body},
routing::get,
Router,
http::{
StatusCode,
header::{HeaderValue, USER_AGENT},
},
};

struct ValidatedBody(Bytes);

#[async_trait]
impl<S> FromRequest<S> for ValidatedBody
where
Bytes: FromRequest<S>,
S: Send + Sync,
{
type Rejection = Response;

async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let body = Bytes::from_request(req, state)
.await
.map_err(IntoResponse::into_response)?;

// do validation...

Ok(Self(body))
}
}

async fn handler(ValidatedBody(body): ValidatedBody) {
// ...
}

let app = Router::new().route("/foo", get(handler));

无法同时实现FromRequestFromRequestParts

请注意,除非为相同类型同时直接实现 FromRequestFromRequestParts, 否则您将无法使用您的提取器,除非它包装另一个提取器。

warning
use axum::{
Router,
routing::get,
extract::{FromRequest, Request, FromRequestParts},
http::request::Parts,
body::Body,
async_trait,
};
use std::convert::Infallible;

// 不包装其他提取器的某些提取器
struct MyExtractor;

// `MyExtractor`同时实现了`FromRequest`接口
#[async_trait]
impl<S> FromRequest<S> for MyExtractor
where
S: Send + Sync,
{
type Rejection = Infallible;

async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
// ...
}
}

// 以及 `FromRequestParts`
#[async_trait]
impl<S> FromRequestParts<S> for MyExtractor
where
S: Send + Sync,
{
type Rejection = Infallible;

async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
// ...
}
}

let app = Router::new().route(
"/",
// 当我们实际使用`MyExtractor`在处理程序函数中时会出现失败。
// 这是由于Rust类型系统的限制。
// 解决方法是实现`FromRequest`或`FromRequestParts`中的一个,如果你的提取器不包装另一个提取器。
// 有关如何包装其他提取器,请参见“包装提取器”。.
get(|_: MyExtractor| async {}),
);

FromRequestFromRequestParts实现中访问其他提取器

在定义自定义提取器时,您经常需要在您的实现中访问另一个提取器。

use axum::{
async_trait,
extract::{Extension, FromRequestParts},
http::{StatusCode, HeaderMap, request::Parts},
response::{IntoResponse, Response},
routing::get,
Router,
};

#[derive(Clone)]
struct State {
// ...
}

struct AuthenticatedUser {
// ...
}

#[async_trait]
impl<S> FromRequestParts<S> for AuthenticatedUser
where
S: Send + Sync,
{
type Rejection = Response;

async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
// 你可以直接调用它们 ...
let headers = HeaderMap::from_request_parts(parts, state)
.await
.map_err(|err| match err {})?;

// ... 使用从 `RequestExt` / `RequestPartsExt` 中提取或使用 `extract` / `extract_with_state` 方法
use axum::RequestPartsExt;
let Extension(state) = parts.extract::<Extension<State>>()
.await
.map_err(|err| err.into_response())?;

unimplemented!("actually perform the authorization")
}
}

async fn handler(user: AuthenticatedUser) {
// ...
}

let state = State { /* ... */ };

let app = Router::new().route("/", get(handler)).layer(Extension(state));

请求体限制

出于安全原因,默认情况下,Bytes 将不接受大于 2MB 的主体。

这也适用于内部使用 Bytes 的提取器,如 StringJsonForm

有关更多详情,包括如何禁用此限制,请参阅 DefaultBodyLimit

包装提取器

如果你想编写一个通用地包装另一个提取器(可能会或不会消耗请求正文)的提取器, 你应该同时实现 FromRequestFromRequestParts

use axum::{
Router,
body::Body,
routing::get,
extract::{Request, FromRequest, FromRequestParts},
http::{HeaderMap, request::Parts},
async_trait,
};
use std::time::{Instant, Duration};

// 一个包装另一个并测量运行时间的提取器
struct Timing<E> {
extractor: E,
duration: Duration,
}

// 我们必须实现`FromRequestParts`
#[async_trait]
impl<S, T> FromRequestParts<S> for Timing<T>
where
S: Send + Sync,
T: FromRequestParts<S>,
{
type Rejection = T::Rejection;

async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let start = Instant::now();
let extractor = T::from_request_parts(parts, state).await?;
let duration = start.elapsed();
Ok(Timing {
extractor,
duration,
})
}
}

// 以及 `FromRequest`
#[async_trait]
impl<S, T> FromRequest<S> for Timing<T>
where
S: Send + Sync,
T: FromRequest<S>,
{
type Rejection = T::Rejection;

async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let start = Instant::now();
let extractor = T::from_request(req, state).await?;
let duration = start.elapsed();
Ok(Timing {
extractor,
duration,
})
}
}

async fn handler(
// 这里使用了`FromRequestParts`的实现
_: Timing<HeaderMap>,
// 这里使用了 `FromRequest` 实现
_: Timing<String>,
) {}

日志拒绝

所有内置提取器都将记录拒绝以便更轻松地进行调试。

要查看日志,请为axum启用跟踪功能(默认已启用)和axum::rejection=trace跟踪目标,

例如使用RUST_LOG=info,axum::rejection=trace cargo run

重新导出

pub use crate::Json;	      // json
pub use crate::Extension;
pub use crate::form::Form; // form

模块

  • connect_info(tokio): 连接信息提取器从客户端获取连接信息。
  • multipart(multipart): 解析multipart/form-data请求的提取器,通常与文件上传一起使用。
  • path: 从URL获取捕获项并使用serde进行解析的提取器。
  • rejection: 拒绝响应类型。
  • ws(ws): 处理WebSocket连接。

结构体

  • ConnectInfo(tokio): 用于获取由Connected产生的连接信息的提取器。
  • DefaultBodyLimit: 用于配置默认请求体限制的层
  • Host: 解析请求的主机名的提取器。
  • MatchedPath(matched-path): 访问匹配请求的路由器中的路径。
  • Multipart(multipart): 用于解析multipart/form-data请求(通常与文件上传一起使用)的提取器。
  • NestedPath: 访问嵌套路由匹配的路径
  • OriginalUri(original-uri): 提取器,用于获取原始请求 URI,无论嵌套如何
  • Path: 从URL中捕获并使用serde解析它们的提取器
  • Query(query): 将查询字符串反序列化为某种类型的提取器
  • RawForm: 提取器,用于提取原始表单请求。
  • RawPathParams: 从 URL 中获取捕获内容而不进行反序列化的提取器。
  • RawQuery: 提取器,提取原始查询字符串,不解析它
  • State: 状态提取器
  • WebSocketUpgrade(ws): 用于建立WebSocket连接的提取器

Traits

  • FromRef: 用于进行引用到值的转换,因此不会消耗输入值。
  • FromRequest: 可以从请求中创建的类型。
  • FromRequestParts: 可以从请求部分创建的类型。

类型别名

  • Request: 对 http::Request 的类型别名,默认的body类型是Body,是 axum 中最常用的body类型

派生宏

  • FromRef 宏: 为结构体中的每个字段派生 FromRef 的实现。
  • FromRequest 宏: 派生 FromRequest 的实现。
  • FromRequestParts 宏: 派生 FromRequestParts 的实现。