Skip to main content

Axum的错误处理模块

本文主要介绍:错误处理模型和实用程序

axum的错误处理模型

axum基于tower::Service构建,通过其关联的Error类型捆绑错误。

如果您有一个产生错误的Service,并且该错误一直传播到超文本传输协议(HTTP)层,连接将在没有发送响应的情况下终止。

这通常是不可取的,所以axum确保依靠类型系统始终生成响应。

axum通过要求所有服务将Infallible作为其错误类型来实现这一点。

Infallible是永远不会发生错误的错误类型

这意味着,如果您定义了像这样的处理程序:

use axum::http::StatusCode;

async fn handler() -> Result<String, StatusCode> {
// ...
}

虽然看起来可能会因为 StatusCode 失败,但实际上这并不是一个“错误”。 如果此处理程序返回 Err(some_status_code),它仍将转换为响应并发送回客户端。 这是通过 StatusCodeIntoResponse 实现完成的。

无论您返回 Err(StatusCode::NOT_FOUND) 还是 Err(StatusCode::INTERNAL_SERVER_ERROR), 在 axum 中这些都不被视为错误

而不是直接使用 StatusCode请使用中间的错误类型,最终可以转换为响应。 这样就可以在处理程序中使用?运算符。

看看这些示例:

这也适用于提取器。如果提取器不匹配请求,请求将被拒绝,并且将返回一个响应,而不会调用您的处理程序。 请参阅提取 以了解如何处理提取器故障。

向易错服务路由

如果您只是使用异步函数作为处理程序,通常无需考虑错误。 但是,如果嵌入了通用的Service或应用中间件, 这可能会产生错误,您需要告诉axum如何将这些错误转换为响应。

use axum::{
Router,
body::Body,
http::{Request, Response, StatusCode},
error_handling::HandleError,
};

async fn thing_that_might_fail() -> Result<(), anyhow::Error> {
// ...
}

// 这个服务可能会因为 `anyhow::Error` 失败
let some_fallible_service = tower::service_fn(|_req| async {
thing_that_might_fail().await?;
Ok::<_, anyhow::Error>(Response::new(Body::empty()))
});

let app = Router::new().route_service(
"/",
// 我们无法直接路由到 `some_fallible_service`,因为它可能会失败。
// 我们必须使用 `handle_error`,将其错误转换为响应,并将其错误类型从 `anyhow::Error` 更改为 `Infallible`。
HandleError::new(some_fallible_service, handle_anyhow_error),
);

// 通过将错误转换为实现`IntoResponse`的内容来处理错误
async fn handle_anyhow_error(err: anyhow::Error) -> (StatusCode, String) {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {err}"),
)
}

应用可失败中间件

同样,axum要求您处理来自中间件的错误。 这是通过HandleErrorLayer完成的:

use axum::{
Router,
BoxError,
routing::get,
http::StatusCode,
error_handling::HandleErrorLayer,
};
use std::time::Duration;
use tower::ServiceBuilder;

let app = Router::new()
.route("/", get(|| async {}))
.layer(
ServiceBuilder::new()
// `timeout` will produce an error if the handler takes
// too long so we must handle those
.layer(HandleErrorLayer::new(handle_timeout_error))
.timeout(Duration::from_secs(30))
);

async fn handle_timeout_error(err: BoxError) -> (StatusCode, String) {
if err.is::<tower::timeout::error::Elapsed>() {
(
StatusCode::REQUEST_TIMEOUT,
"Request took too long".to_string(),
)
} else {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Unhandled internal error: {err}"),
)
}
}

运行提取器以进行错误处理

HandleErrorLayer 也支持运行提取器:

use axum::{
Router,
BoxError,
routing::get,
http::{StatusCode, Method, Uri},
error_handling::HandleErrorLayer,
};
use std::time::Duration;
use tower::ServiceBuilder;

let app = Router::new()
.route("/", get(|| async {}))
.layer(
ServiceBuilder::new()
// `timeout`如果处理程序花费太长时间就会产生错误,因此我们必须处理这些情况。
.layer(HandleErrorLayer::new(handle_timeout_error))
.timeout(Duration::from_secs(30))
);

async fn handle_timeout_error(
// `Method`和`Uri`是提取器,因此可以在这里使用。
method: Method,
uri: Uri,
// 最后一个参数必须是错误本身
err: BoxError,
) -> (StatusCode, String) {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("`{method} {uri}` failed with {err}"),
)
}

模块

  • future: Future types

结构体

  • HandleError: 一个Service适配器,通过将错误转换为响应来处理错误。
  • HandleErrorLayer: 通过将错误转换为响应来处理错误的 HandleError,这是一个Service适配器的Layer