错误处理

错误处理

Actix-web使用自己的actix_web::error::Error类型和actix_web::error::ResponseError trait来从Web处理程序处理错误。

如果handler在还实现了ResponseError trait的Result中返回Error(指的是一般的Rust trait std::error::Error),则actix-web会将错误与相应的actix_web::http::StatusCode一起呈现为HTTP响应。

默认情况下会生成内部服务器错误:

pub trait ResponseError {
    fn error_response(&self) -> HttpResponse;
    fn status_code(&self) -> StatusCode;
}

Responder将兼容Result强制转换为HTTP响应:

impl<T: Responder, E: Into<Error>> Responder for Result<T, E>

上面代码中的Error是actix-web的error定义,任何实现ResponseError的errors都可以自动转换为一个。

Actix-web提供了一些常见的非actix错误的ResponseError实现。例如,如果handler以io::Error响应,则该错误将转换为HttpInternalServerError

use std::io;

fn index(_req: HttpRequest) -> io::Result<fs::NamedFile> {
    Ok(fs::NamedFile::open("static/index.html")?)
}

有关ResponseError的外部实现的完整列表,请参见actix-web API文档

自定义错误响应的示例

这是ResponseError的示例实现:

use actix_web::{error, Result};
use failure::Fail;

#[derive(Fail, Debug)]
#[fail(display = "my error")]
struct MyError {
    name: &'static str,
}

// Use default implementation for `error_response()` method
impl error::ResponseError for MyError {}

async fn index() -> Result<&'static str, MyError> {
    Err(MyError { name: "test" })
}

ResponseError具有error_response()的默认实现,它将呈现500 (服务器内部错误),并且在上面执行index处理程序时会发生这种情况。

覆盖error_response()以产生更多有用的结果:

use actix_http::ResponseBuilder;
use actix_web::{error, http::header, http::StatusCode, HttpResponse};
use failure::Fail;

#[derive(Fail, Debug)]
enum MyError {
    #[fail(display = "internal error")]
    InternalError,
    #[fail(display = "bad request")]
    BadClientData,
    #[fail(display = "timeout")]
    Timeout,
}

impl error::ResponseError for MyError {
    fn error_response(&self) -> HttpResponse {
        ResponseBuilder::new(self.status_code())
            .set_header(header::CONTENT_TYPE, "text/html; charset=utf-8")
            .body(self.to_string())
    }

    fn status_code(&self) -> StatusCode {
        match *self {
            MyError::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
            MyError::BadClientData => StatusCode::BAD_REQUEST,
            MyError::Timeout => StatusCode::GATEWAY_TIMEOUT,
        }
    }
}

async fn index() -> Result<&'static str, MyError> {
    Err(MyError::BadClientData)
}

Error 助手

Actix-web提供了一组error helper函数,这些函数可用于从其他错误中生成特定的HTTP错误代码。 在这里,我们使用map_err将未实现ResponseErrortrait的MyError转换为_400_(错误请求):

use actix_web::{error, Result};

#[derive(Debug)]
struct MyError {
    name: &'static str,
}

async fn index() -> Result<&'static str> {
    let result: Result<&'static str, MyError> = Err(MyError { name: "test error" });

    Ok(result.map_err(|e| error::ErrorBadRequest(e.name))?)
}

有关可用的错误帮助程序的完整列表,请查看actix-web错误模块的API文档

兼容故障

Actix-web提供与failure库的自动兼容性,以便将导致故障的错误自动转换为actix错误。 请记住,这些错误将使用默认的_500_状态码呈现,除非您还为其提供了自己的error_response()实现。

Error logging 错误日志

Actix在WARN日志级别记录所有错误。如果应用程序的日志级别设置为DEBUG,并且启用了RUST_BACKTRACE,则还将记录backtrace(回溯)。这些可以使用环境变量进行配置:

>> RUST_BACKTRACE=1 RUST_LOG=actix_web=debug cargo run

Error 类型使用cause的错误回溯(如果有)。如果根本的故障不提供回溯,则构造一个新的回溯,指向发生转换的点(而不是错误的根源)。

错误处理的推荐做法

考虑将应用程序产生的错误分为两大类可能是有用的:一类是面向用户的,二类不是面向用户的。

前者的一个示例是,我可能使用failure(失败)指定UserError枚举,该枚举封装了一个ValidationError,以便在用户发送错误的输入时返回:

use actix_http::ResponseBuilder;
use actix_web::{error, http::header, http::StatusCode, HttpResponse};
use failure::Fail;

#[derive(Fail, Debug)]
enum UserError {
    #[fail(display = "Validation error on field: {}", field)]
    ValidationError { field: String },
}

impl error::ResponseError for UserError {
    fn error_response(&self) -> HttpResponse {
        ResponseBuilder::new(self.status_code())
            .set_header(header::CONTENT_TYPE, "text/html; charset=utf-8")
            .body(self.to_string())
    }
    fn status_code(&self) -> StatusCode {
        match *self {
            UserError::ValidationError { .. } => StatusCode::BAD_REQUEST,
        }
    }
}

这将完全按照预期的方式运行,因为用display定义的错误消息是在明确意图下被用户读取的。

但是,并非所有错误都希望发送回错误消息-在服务器环境中会发生很多故障,我们可能希望向用户隐藏具体信息。 例如,如果数据库关闭并且客户端库开始产生连接超时错误,或者HTML模板格式不正确,并且渲染时发生错误。 在这些情况下,最好将错误映射为适合用户使用的一般错误。

这是一个使用自定义消息将内部错误映射到面向用户的InternalError的示例:

use actix_http::ResponseBuilder;
use actix_web::{error, http::header, http::StatusCode, HttpResponse};
use failure::Fail;

#[derive(Fail, Debug)]
enum UserError {
    #[fail(display = "An internal error occurred. Please try again later.")]
    InternalError,
}

impl error::ResponseError for UserError {
    fn error_response(&self) -> HttpResponse {
        ResponseBuilder::new(self.status_code())
            .set_header(header::CONTENT_TYPE, "text/html; charset=utf-8")
            .body(self.to_string())
    }
    fn status_code(&self) -> StatusCode {
        match *self {
            UserError::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
        }
    }
}

async fn index() -> Result<&'static str, UserError> {
    do_thing_that_failes().map_err(|_e| UserError::InternalError)?;
    Ok("success!")
}

通过将错误分为面对用户的错误和不面对用户的错误,我们可以确保我们不会意外地使用户暴露于应用程序内部抛出的错误中,而这些错误并非本应引起的。

Error Logging 错误日志

这是使用middleware::Logger的基本示例:

use actix_web::{error, Result};
use failure::Fail;
use log::debug;

#[derive(Fail, Debug)]
#[fail(display = "my error")]
pub struct MyError {
    name: &'static str,
}

// Use default implementation for `error_response()` method
impl error::ResponseError for MyError {}

async fn index() -> Result<&'static str, MyError> {
    let err = MyError { name: "test error" };
    debug!("{}", err);
    Err(err)
}

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
    use actix_web::{middleware::Logger, web, App, HttpServer};

    std::env::set_var("RUST_LOG", "my_errors=debug,actix_web=info");
    std::env::set_var("RUST_BACKTRACE", "1");
    env_logger::init();

    HttpServer::new(|| {
        App::new()
            .wrap(Logger::default())
            .route("/", web::get().to(index))
    })
    .bind("127.0.0.1:8088")?
    .run()
    .await
}
接下来: URL Dispatch