Actix Web 是一款基于 Rust 语言开发的高性能 Web 框架。它通过异步编程模型、强大的请求路由、中间件支持,为开发者提供了丰富的工具和选项,是构建可伸缩、高并发的 Web 应用程序的理想选择。
初始化
新建工程actix-test。
cargo new actix-test
cd actix-test
Cargo.toml增加依赖:
[dependencies]
actix-web = "4"
接口代码
修改src/main.ts:
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello world!")
}
#[post("/echo")]
async fn echo(req_body: String) -> impl Responder {
HttpResponse::Ok().body(req_body)
}
async fn manual_hello() -> impl Responder {
HttpResponse::Ok()
.cookie(
Cookie::build("name", "value")
.domain("localhost")
.path("/")
.secure(false)
.http_only(true)
.finish(),
)
.body("Hey there!")
}
#[actix_web::main]
async fn main() -> std::io::Result {
HttpServer::new(|| {
App::new()
.service(hello)
.service(echo)
.route("/hey", web::get().to(manual_hello))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
验证
使用cargo run,运行服务。
http://127.0.0.1:8080
http://127.0.0.1:8080/hey
还有个post请求:
fetch('echo', {method: 'post', body: JSON.stringify({a: 'b'}), headers: {'content-type':'application/json'}});
参数
参数获取
path
url路径中的参数中可以使用web::Path
来整合成一个Struct
对象:
use actix_web::{get, web, App, HttpServer, Result};
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
user_id: u32,
friend: String,
}
/// extract path info using serde
#[get("/users/{user_id}/{friend}")] // Result {
Ok(format!(
"Welcome {}, user_id {}!",
info.friend, info.user_id
))
}
这里用到了serde::Deserialize,所以需要在toml里添加依赖:
serde = {version = "1", features =["derive"]}
要使用derive,就必须这样添加features,否则会报
cannot find derive macro Deserializ in this scope
也可以用web::Path
只取某一个参数:
#[get("/hello/{name}")]
async fn greet(name: web::Path) -> impl Responder {
format!("Hello {name}!")
}
Query
Query与path类似,可以获取/hello?name=test&age=16
中的name与age。
#[derive(serde::Deserialize)]
struct MyParams {
name: String,
age: u16,
}
#[get("/hello")]
async fn hello(params: web::Query) -> String {
format!("Hello, {}! You are {} years old.", params.name, params.age)
}
POST
对于POST请求,可以使用web::Json
获取Body中的参数:
#[derive(serde::Deserialize)]
struct MyParams {
name: String,
age: u8,
}
#[post("/hello")]
async fn hello(params: web::Json) -> impl Responder {
HttpResponse::Ok().body(format!(
"Hello, {}! You are {} years old.",
params.name, params.age
))
}
参数校验
参数校验通常发生在POST请求里,因为它的参数通常比较复杂。如果每个接口都写一遍参数校验处理,那就太Low了。可以使用validator来简化代码。
use serde::{Deserialize, Serialize};
use validator::Validate;
#[derive(Deserialize, Debug, Serialize, Validate)]
pub struct CreateJobDto {
#[validate(length(min = 10, max = 100))]
pub old_image: String,
#[validate(length(min = 10, max = 100))]
pub new_image: String,
pub status: JobStatus,
pub user_id: String,
}
use actix_web::error::ErrorBadRequest;
use actix_web::Error;
#[post("createJob")]
pub async fn create_job(
db: web::Data,
params: web::Json,
) -> Result {
params.validate().map_err(ErrorBadRequest)?;
match job_service::create_job(&db, params.into_inner()).await {
Ok(id) => Ok(HttpResponse::Ok().body(id)),
Err(err) => {
if err.to_string().contains("E11000 duplicate key error") {
Ok(HttpResponse::BadRequest().body("old_image重复"))
} else {
log::error!("error: {}", err);
Ok(HttpResponse::InternalServerError().body(err.to_string()))
}
}
}
}
Guard
config.service(
web::scope("/api/user")
.guard(guard::Header("x-guarded", "secret"))
.service(auth_controller::login));
这里的Guard与NestJS中意义上不同的是,它只是保护接口,被过滤掉的接口只会响应404。
所以要做到NestJS中的接口保护,只能写中间件。下面来具体介绍如何实现。
简单Guard
这是一个不需要注入用户信息的普通Guard:
use crate::globals;
use actix_web::{
dev::{self, Service},
error::ErrorForbidden,
};
use actix_web::{
dev::{ServiceRequest, ServiceResponse, Transform},
Error,
};
use futures_util::future::LocalBoxFuture;
use std::{
future::{ready, Ready},
rc::Rc,
};
pub struct SimpleGuard;
impl Transform for SSOGuard
where
S: Service,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse;
type Error = Error;
type InitError = ();
type Transform = SSOGuardMiddleware;
type Future = Ready;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(SSOGuardMiddleware {
service: Rc::new(service),
}))
}
}
pub struct SSOGuardMiddleware {
// This is special: We need this to avoid lifetime issues.
service: Rc,
}
impl Service for SSOGuardMiddleware
where
S: Service + 'static,
S::Future: 'static,
B: 'static,
{
type Response = ServiceResponse;
type Error = Error;
type Future = LocalBoxFuture