1.2 异常处理

2022-10-09 • 更新于 2022-10-14

开始

首先不急着直接做各种其他的东西,而是将基础的东西弄好。

在 workspace 的 Cargo.toml 里添加 "ch01/error-handling",添加好后,文件的内容应该如下所示:

[workspace]

members = [
    "ch01/helloworld",
    "ch01/error-handling"
]

进入 ch01 文件夹,创建项目,名称为 error-handling。在项目的 Cargo.toml 里添加 actix-web 依赖,如下:

[dependencies]
actix-web = "4.2.1"

如果需要将自己的工作空间的所有代码上传到代码管理平台,需要将创建项目同时创建的 .git 文件夹删除。注意是项目的 .git 文件夹。

再在 main.rs 中,添加主函数,以及基础的内容:

use actix_web::{HttpServer, App};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

上述的代码内容,之前基本都已经大致理解了一下。接下来开始异常处理的内容。

显示内容

刚开始暂时先不做其他的事情,而是显示一个简短的内容。看一下源码,源码中为函数 do_something 配置了路径和路由。那么我们一开始也就先做这一部分内容。

编写 do_something 函数,其中要注意到返回值的类型为 Result<HttpResponse, Error>。函数中返回一个字符串,内容为“Nothing interesting happened. Try again.”,意思大致是“没有什么有意思的事情发生,请再次尝试”。编写好的函数内容如下:

async fn do_something() -> Result<HttpResponse, Error> {
    Ok(HttpResponse::Ok().body("Nothing interesting happened. Try again."))
}

如果没有使用 rust-analyzer 的自动补充功能,此时顶部的引用应该已经如下所示:

use actix_web::{HttpServer, App, HttpResponse, Error};

注意返回值中的 Error,是 actix_web 的,而不是 std 中的 Error

接下来,要为其配置路径。查看官方例子中,发现此时用了另一种方式配置路由,即 web::resource().route()

在顶部添加 web 引用。

use actix_web::web;

然后在 App::new() 后面,为 do_something 配置路径为 /something。代码如下:

async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new().service(web::resource("/something").route(web::get().to(do_something)))
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

运行,进入网址 localhost:8080/something 看看,能显示 do_something 中返回的内容则说明代码没有问题。

添加错误

在源码中,自定义了一个名为 CustomError 的枚举类。在编写之前,先在 Cargo.toml 中添加 derive_more

[dependencies]
actix-web = "4.2.1"
derive_more = "0.99.5"

搞定,现在添加 CustomError。内容如下:

use derive_more::Display;

#[derive(Debug, Display)]
pub enum CustomError {
    #[display(fmt = "Custom Error 1")]
    CustomOne,
    #[display(fmt = "Custom Error 2")]
    CustomTwo,
    #[display(fmt = "Custom Error 3")]
    CustomThree,
    #[display(fmt = "Custom Error 4")]
    CustomFour,
}

出现了新东西 Display。找到 derive_more文档,可以看到作者对 Display 的所有描述。按我的理解,大致作用是为自己声明的结构体等提供了一个格式化字符串的方式。

这个自定义的错误中,有四种错误。不一定有实际意义。接下来,继续查看源码,发现这些错误会随机展示一个出来。源码中,为 rand::distributions::Standard 实现了 Distribution<CustomError>,即为随机生成添加了 CustomError 这个类型的实现。通过这个实现可以在调用 rand::random 时,随机生成一个此类型的值。

在 Cargo.toml 中添加 rand 这个 crate 的依赖。

[dependencies]
actix-web = "4.2.1"
derive_more = "0.99.5"
rand = "0.8"

然后在顶部添加 DistributionStandard 的引用。

use rand::{prelude::Distribution, distributions::Standard};

现在,为 Standard 实现 Distribution<CustomError>。随机生成一个在[0,4)范围内的数字,然后根据生成的数字返回不同的 CustomError

impl Distribution<CustomError> for Standard {
    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> CustomError {
        match rng.gen_range(0..4) {
            0 => CustomError::CustomOne,
            1 => CustomError::CustomTwo,
            2 => CustomError::CustomThree,
            _ => CustomError::CustomFour,
        }
    }
}

由于这个错误需要返回到网页上,所以需要实现 actix-web 的 ResponseError,来返回对应的 Http 回应。在官方例子的源码注释中有说明,返回 200 为正常回应,而返回的 Http 错误有4种:

  1. 403 Forbidden(禁止访问)
  2. 401 Unauthorized(缺少凭证)
  3. 500 InternalServerError(内部服务器的错误)
  4. 400 BadRequest(错误的请求)

可以通过这个手册来查看所有的 Http 错误。

自定义的四个错误,每一个枚举都对应上面4个错误中的一个。

use actix_web::{HttpServer, App, HttpResponse, web, Error, ResponseError};

impl ResponseError for CustomError {
    fn error_response(&self) -> HttpResponse<actix_web::body::BoxBody> {
        match self {
            CustomError::CustomOne => {
                println!("do some stuff related to CustomOne error");
                HttpResponse::Forbidden().finish()// 403
            },
            CustomError::CustomTwo => {
                println!("do some stuff related to CustomOne error");
                HttpResponse::Unauthorized().finish()// 401
            },
            CustomError::CustomThree => {
                println!("do some stuff related to CustomOne error");
                HttpResponse::InternalServerError().finish()// 500
            },
            _ => {
                println!("do some stuff related to CustomOne error");
                HttpResponse::BadRequest().finish()// 400
            }
        }
    }
}

随机产生错误

一般这些错误不会直接出现,所以需要随机触发一个。添加一个 do_something_random 的函数来随机触发一个错误。有 20% 的机率什么都不会发生。

use rand::{prelude::Distribution, distributions::Standard, thread_rng, Rng};

async fn do_something_random() -> Result<(), CustomError> {
    let mut rng = thread_rng();

    if rng.gen_bool(2.0 / 10.0) {
        Ok(())
    } else {
        Err(rand::random::<CustomError>())
    }
}

然后在 do_something 中调用 do_something_random

async fn do_something() -> Result<HttpResponse, Error> {
    do_something_random().await?;

    Ok(HttpResponse::Ok().body("Nothing interesting happened. Try again."))
}

现在来运行看看结果。多刷新几次可以看到浏览器展示不同的错误,控制台上也打印了相应的字符串。

结束

官方例子的注释说道,这个例子是用来展示如何自定义一个错误类型,来处理不同的错误。不过就目前而言确实有点抽象,可以知道的是这里的 Http 错误是由我们自己去产生的,而在 println 位置处,应该是需要之后去处理对应错误的函数。

当自定义一个错误类型的时候,为了能够使其返回给客户端/浏览器,需要实现 ResponseError 这个 traitResponseError 中定义的函数可以在文档中查看。

除此之外,还出现了新的路由配置,App::new().service(web::resource('').route())

actix-web学习笔记Rustactix-web学习笔记

学少何

不求上进的社畜……

1.3 状态

1.1 Hello world