开始
首先不急着直接做各种其他的东西,而是将基础的东西弄好。
在 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"
然后在顶部添加 Distribution
和 Standard
的引用。
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种:
- 403 Forbidden(禁止访问)
- 401 Unauthorized(缺少凭证)
- 500 InternalServerError(内部服务器的错误)
- 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
这个 trait
。ResponseError
中定义的函数可以在文档中查看。
除此之外,还出现了新的路由配置,App::new().service(web::resource('').route())
。