目录
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)
,它仍将转换为响应并发送回客户端。
这是通过 StatusCode
的 IntoResponse
实现完成的。
无论您返回 Err(StatusCode::NOT_FOUND)
还是 Err(StatusCode::INTERNAL_SERVER_ERROR)
,
在 axum 中这些都不被视为错误。
而不是直接使用 StatusCode
,请使用中间的错误类型,最终可以转换为响应。
这样就可以在处理程序中使用?
运算符。
看看这些示例:
anyhow-error-response
: 用于通用的堆箱错误error-handling
: 用于特定应用程序的详细错误信息
这也适用于提取器。如果提取器不匹配请求,请求将被拒绝,并且将返回一个响应,而不会调用您的处理程序。 请参阅提取 以了解如何处理提取器故障。
向易错服务路由
如果您只是使用异步函数作为处理程序,通常无需考虑错误。
但是,如果嵌入了通用的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
。