likes
comments
collection
share

探索Actix Web:高性能异步 Rust Web框架

作者站长头像
站长
· 阅读数 15

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

探索Actix Web:高性能异步 Rust Web框架

http://127.0.0.1:8080/hey

探索Actix Web:高性能异步 Rust Web框架

还有个post请求:

fetch('echo', {method: 'post', body: JSON.stringify({a: 'b'}), headers: {'content-type':'application/json'}});

探索Actix Web:高性能异步 Rust Web框架

参数

参数获取

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}")] // <- define path parameters
async fn index(info: web::Path<Info>) -> Result<String> {
    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<T>只取某一个参数:

#[get("/hello/{name}")]
async fn greet(name: web::Path<String>) -> 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<MyParams>) -> 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<MyParams>) -> 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<Database>,
    params: web::Json<CreateJobDto>,
) -> Result<HttpResponse, Error> {
    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<S: 'static, B> Transform<S, ServiceRequest> for SimpleGuard
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = SimpleGuardMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(SimpleGuardMiddleware {
            service: Rc::new(service),
        }))
    }
}
pub struct SimpleGuardMiddleware<S> {
    // This is special: We need this to avoid lifetime issues.
    service: Rc<S>,
}

impl<S, B> Service<ServiceRequest> for SimpleGuardMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    dev::forward_ready!(service);

    fn call(&self, req: ServiceRequest) -> Self::Future {
        let svc = self.service.clone();

        Box::pin(async move {
            let is_valid = match req.headers().get("Authorization") {
                Some(token) => {
                    let tk = token.to_str().unwrap_or_default();
                    if tk == ("Bearer ".to_owned() + &globals::CONFIG.token) {
                        true
                    } else {
                        warn!("Authorization [{}] is invalid", tk);
                        false
                    }
                }
                None => false,
            };
            if !is_valid {
                return Err(ErrorForbidden("invalid token"));
            }

            let res = svc.call(req).await?;
            // println!("response: {:?}", res.headers());
            Ok(res)
        })
    }
}

SSOGuard

中间件怎么往下一步流转添加信息呢?

查看actix-web官方的样例,找到这样一段代码:

 #[derive(Clone)]
pub struct User {
    name: String,
}

impl User {
    fn new(name: String) -> Self {
        User { name: name }
    }
}

// 在中间件中注入额外信息
request.extensions_mut().insert(User::new("test".to_string()));

在接口中使用:

#[get("/login")]
pub async fn login(req: HttpRequest, user: ReqData<User>) -> impl Responder {
    info!("username is {:?}", user.name);
    "login"
}

#[get("/login")]
pub async fn login(req: HttpRequest, user: Option<ReqData<User>>) -> impl Responder {
    if let Some(user) = user {
        info!("username is {:?}", user.name);
    }
    "login"
}

如果不加Option类型,则如果没有找到这个信息,就会报错。

以下是一份SSO Guard的代码:

use super::user_service::get_user_info;
use actix_web::{
    dev::{self, Service},
    error::ErrorForbidden,
    error::ErrorUnauthorized,
    HttpMessage,
};
use actix_web::{
    dev::{ServiceRequest, ServiceResponse, Transform},
    Error,
};
use futures_util::future::LocalBoxFuture;
use std::{
    future::{ready, Ready},
    rc::Rc,
};

pub struct SSOGuard;

impl<S: 'static, B> Transform<S, ServiceRequest> for SSOGuard
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = SSOGuardMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(SSOGuardMiddleware {
            service: Rc::new(service),
        }))
    }
}
pub struct SSOGuardMiddleware<S> {
    // This is special: We need this to avoid lifetime issues.
    service: Rc<S>,
}

impl<S, B> Service<ServiceRequest> for SSOGuardMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    dev::forward_ready!(service);

    fn call(&self, req: ServiceRequest) -> Self::Future {
        let svc = self.service.clone();

        Box::pin(async move {
            let result = get_user_info(req.headers().to_owned()).await;
            let user_info = match result {
                Ok(userinfo) => userinfo,
                Err(err) => {
                    warn!("fetch user info failed, {}", err);
                    return Err(ErrorUnauthorized("unauthorized"));
                }
            };
            if !user_info.internal {
                warn!("outernal user try to access: {user_info:?}");
                return Err(ErrorForbidden("forbidden"));
            } else {
                info!(
                    "校验通过,得到用户信息,id: {}, name: {}",
                    &user_info.openid,
                    &user_info.get_name()
                );
            }
            req.extensions_mut().insert(user_info);
            let res = svc.call(req).await?;
            // println!("response: {:?}", res.headers());
            Ok(res)
        })
    }
}

在接口中使用:

#[get("userInfo")]
pub async fn get_user_info(user: ReqData<SSOUserInfo>) -> impl Responder {
    HttpResponse::Ok().json(user.into_inner())
}

总结

Rust是一种现代、高效的编程语言,具有静态类型系统、内存安全和并发处理等功能。学习Rust不仅可以提高你的编程技能,还可以让你编写更具可靠性和性能的软件。本文简单介绍了Actix-web这款功能强大、性能出色且易于使用的 Rust Web 框架,给出一些参数获取与校验的样例,以及如何使用中间件实现NestJS中的接口守卫功能。希望以上内容能帮助你快速掌握这款框架。

值得一提的是,随着ChatGPT的出现,我认为极大地降低了Rust的学习成本,尤其是语法层面,ChatGPT完全可以帮助你解决大部分日常使用的问题。所以,对Rust感兴趣,或者想要入手一门系统级开发语言的话,勇敢地行动吧,现在正是最好的时机。

当然,ChatGPT可以帮助你解决一些问题,但仍然需要在实践中掌握Rust的语法和开发技巧。在学习的过程中克服挑战、深入理解语言本质以及将其应用于实际项目中将给你带来更大的成就感。这是在软件开发领域中锻炼自己技能的绝佳机会。

转载自:https://juejin.cn/post/7278597227786174475
评论
请登录