Actix
Actix是一个非常高效的Web框架在Rust中,这段时间非常喜欢用它写Web Server端,至少我觉得比Rocket要好用,目前Actix的处理性能要比绝大部分的Web框架都要高效,从benchmarks这里就能看出来绝大部分框架的基准测试结果对比,这个是跨语言的框架对比,Rust的Actix明显排前置位置。
我们先来看一下这个简单的例子,要求访问的时候要在Body里带JSON内容,它包含两个字段name
和age
。
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
name: String,
age: u8,
}
async fn index(info: web::Json<Info>) -> impl Responder {
HttpResponse::Ok().body(
format!("Hello {}, I'm {} years old!", info.name, info.age)
)
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/", web::to(index))
})
.bind("127.0.0.1:8088")?
.run()
.await
}
测试一下
# curl -i -d '{"name":"qttc.net", "age":10}' -H 'Content-Type: application/json' 127.0.0.1:8088
HTTP/1.1 200 OK
content-length: 33
date: Fri, 10 Apr 2020 03:44:46 GMT
Hello qttc.net, I'm 10 years old!
这是正常的情况下,但有时候也会有一些意外,比如客户端在request的时候没有Body,或者Body里JSON内容的字段缺失就会造成400 Bad Request
错误。
# curl -i -d '{"name":"qttc.net"}' -H 'Content-Type: application/json' 127.0.0.1:8088
HTTP/1.1 400 Bad Request
content-length: 0
date: Fri, 10 Apr 2020 07:22:09 GMT
# curl -i 127.0.0.1:8088
HTTP/1.1 400 Bad Request
content-length: 0
date: Fri, 10 Apr 2020 07:22:09 GMT
以上请求返回400 Bad Request
错误,但是这个错误对客户端的信息太少,不够友好,仅凭一个400错误客户端很难定位到问题,即使是作者他都有可能要一步步追代码才知道原来是参数缺失的原因引起的400错误。
自定义处理错误
为了让返回的结果更加友好,我们需要使用Actix的自定义错误处理API,我们先写下一个处理错误的函数,这个函数需要接受一个关键性的参数actix_web::error::JsonPlayloadError
,使用这个参数可以match
到Deserialize
错误,并在里面得到详细的错误信息。
fn json_error_handler(err: error::JsonPayloadError, _req: &HttpRequest) -> error::Error {
let detail = err.to_string();
let response = match &err {
error::JsonPayloadError::Deserialize(err1) => {
HttpResponse::BadRequest().content_type("text/plain").body(format!("missing fields: {}", err1.to_string()))
}
_ => HttpResponse::BadRequest().content_type("text/plain").body(detail),
};
error::InternalError::from_response(err, response).into()
}
然后,我们需要使用actix_web::App::app_data()
这个函数把错误函数json_error_handler
绑定进去
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.app_data(
web::Json::<Info>::configure(|cfg| {
cfg.error_handler(json_error_handler)
})
)
.route("/", web::to(index))
})
.bind("127.0.0.1:8088")?
.run()
.await
}
再次来试试
# curl -i -d '{"name":"qttc.net"}' -H 'Content-Type: application/json' 127.0.0.1:8088
HTTP/1.1 400 Bad Request
content-length: 55
content-type: text/plain
date: Fri, 10 Apr 2020 07:48:05 GMT
missing fields: missing field `age` at line 1 column 19
已能在Response Body里返回详细的错误信息给客户端,当然,你也可以返回JSON格式,以及你喜欢的响应状态码都可以。
fn json_error_handler(err: error::JsonPayloadError, _req: &HttpRequest) -> error::Error {
let detail = err.to_string();
let response = match &err {
error::JsonPayloadError::Deserialize(err1) => {
HttpResponse::Ok()
.json(Res {
code: -1,
message: format!("missing fields: {}", err1.to_string()),
})
}
_ => HttpResponse::BadRequest().content_type("text/plain").body(detail),
};
error::InternalError::from_response(err, response).into()
}
# curl -i -d '{"name":"qttc.net"}' -H 'Content-Type: application/json' 127.0.0.1:8088
HTTP/1.1 200 OK
content-length: 79
content-type: application/json
date: Fri, 10 Apr 2020 07:57:43 GMT
{"code":-1,"message":"missing fields: missing field `age` at line 1 column 19"}
完整的代码如下
main.rs
use actix_web::{web, App, HttpResponse, HttpServer, Responder, error, HttpRequest, FromRequest};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct Info {
name: String,
age: u8,
}
#[derive(Serialize)]
struct Res {
code: i8,
message: String,
}
async fn index(info: web::Json<Info>) -> impl Responder {
HttpResponse::Ok().body(
format!("Hello {}, I'm {} years old!", info.name, info.age)
)
}
fn json_error_handler(err: error::JsonPayloadError, _req: &HttpRequest) -> error::Error {
let detail = err.to_string();
let response = match &err {
error::JsonPayloadError::Deserialize(err1) => {
HttpResponse::Ok()
.json(Res {
code: -1,
message: format!("missing fields: {}", err1.to_string()),
})
}
_ => HttpResponse::BadRequest().content_type("text/plain").body(detail),
};
error::InternalError::from_response(err, response).into()
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.app_data(
web::Json::<Info>::configure(|cfg| {
cfg.error_handler(json_error_handler)
})
)
.route("/", web::to(index))
})
.bind("127.0.0.1:8088")?
.run()
.await
}