0


Rust Web入门(二):Actix

本教程笔记来自 杨旭老师的 rust web 全栈教程,链接如下:

https://www.bilibili.com/video/BV1RP4y1G7KF?p=1&vd_source=8595fbbf160cc11a0cc07cadacf22951

学习 Rust Web 需要学习 rust 的前置知识可以学习杨旭老师的另一门教程

https://www.bilibili.com/video/BV1hp4y1k7SV/?spm_id_from=333.999.0.0&vd_source=8595fbbf160cc11a0cc07cadacf22951

项目的源代码可以查看 git:(注意作者使用的是 mysql 数据库而不是原教程的数据库)

https://github.com/aiai0603/rust_web_mysql

今天来入门基于 rust 的 web 框架 Actix:

Actix简单使用

Actix - Rust 的 Actor 异步并发框架

Actix 基于 Tokio 和 Future,开箱具有异步非阻塞事件驱动并发能力,其实现低层级 Actor 模型来提供无锁并发模型,而且同时提供同步 Actor,具有快速、可靠,易可扩展。

Actix 之上是高性能 Actix-web 框架,很容易上手。使用 Actix-web 开发的应用程序将在本机可执行文件中包含 HTTP 服务器。你可以把它放在另一个像 nginx 这样的 HTTP 服务器上。但即使完全不存在另一个 HTTP 服务器 (像 nginx) 的情况下,Actix-web 也足以提供 HTTP 1 和 HTTP 2 支持以及 SSL/TLS。这对于构建微服务分发非常有用。

我们需要先创建一个项目,然后引入需要的依赖,然后使用 bin 指定我们的 bin 目录

[package]
name ="stage_2"
version ="0.1.0"
edition ="2021"[dependencies]
actix-web ="3"
actix-rt ="1.1.1"[[bin]]
name ="server1"

之后我们在 src 下创建一个 bin 目录和一个 server1.rs 编写我们的框架:

对于 server1.rs 我们需要初始化一个 app 作为我们的 web 项目,然后为它配置一个路由的函数,之后再指定的端口运行我们的 app 项目。因为它是异步的,所以我们要加上 await 和 async 进行修饰并且使用 actix_rt::main 这个包

useactix_web::{web,App,HttpResponse,HttpServer,Responder};usestd::io;#[actix_rt::main]asyncfnmain()->io::Result<()>{let app =move||App::new().configure(general_routes);HttpServer::new(app).bind("127.0.0.1:3000")?.run().await}

之后我们编写我们的路由函数,它传入一个配置项,你可以在其中配置对应路由的处理方法,比如我们处理 /health 路径的 get 方法,我们就可以用如下的方式进行编写,在 to 之后提供一个函数作为我们的处理函数。

处理函数是需要实现 Responder 这个 Trait 的,所以我们的返回值需要使用 HttpResponse 相关的函数进行返回,其中 Ok() 表示 200 这个状态码,之后又使用 json 函数返回了一段 json 作为作为我们的返回值

pubfngeneral_routes(cfg:&mutweb::ServiceConfig){
    cfg.route("/health",web::get().to(health_check_handler));}pubasyncfnhealth_check_handler()->implResponder{HttpResponse::Ok().json("Actix Web Service is running!")}

现在我们的创建搭建完毕了,我们在命令行启动我们的项目,然后访问 120.0.0.1:3000 ,可以看到,Actix Web Service is running! 这句话,那么我们的项目就可以正常使用了

构建完整的 rust API

现在我们已经可以运行我们的 Actix 框架了,之后我们来尝试构建一个完整的具有增删改查功能的 api,我们再新建一个 teacher-service.rs 把这个项目设置为默认项目,并且加载我们需要的包:

[package]
name = "stage_3"
version = "0.1.0"
edition = "2021"
default-run = "teacher-service"

[dependencies]
actix-web = "3"
actix-rt = "1.1.1"
serde = { version = "1.0.132", features = ["derive"] }
chrono = { version = "0.4.19", features = ["serde"] }

[[bin]]
name = "server1"

[[bin]]
name = "teacher-service"

数据库的部分将会在下一部分讲解,我们先把我们的数据放在内存中,我们先建立一个 models.rs 它用于定义我们的数据结构, 通过刚刚引入的 serde 包,我们可以让 json 数据转化为我们的数据结构

useactix_web::web;usechrono::NaiveDateTime;useserde::{Deserialize,Serialize};#[derive(Deserialize, Serialize, Debug, Clone)]pubstructCourse{pub teacher_id:usize,pub id:Option<usize>,pub name:String,pub time:Option<NaiveDateTime>,}implFrom<web::Json<Course>>forCourse{fnfrom(course:web::Json<Course>)->Self{Course{
            teacher_id: course.teacher_id,
            id: course.id,
            name: course.name.clone(),
            time: course.time,}}}

之后我们编写一个 state.rs 封装我们全局共享的数据结构,它包括一个响应,一个访问次数和一个返回的结构体,这个内容将作为全局内容在我们的程序中共享,因为涉及到多个程序会调用 visit_count 和 courses 数据,所以我们把他们放在 Mutex 中来保证互斥调用:

usestd::sync::Mutex;usecrate::modelds::Course;pubstructAppState{pub health_check_response:String,pub visit_count:Mutex<u32>,pub courses:Mutex<Vec<Course>>,}

之后将上一步简单 get 方法的路由配置到这里,我们新建 routers.rs 来存放路由

usesuper::handlers::*;useactix_web::web;pubfngeneral_routes(cfg:&mutweb::ServiceConfig){
    cfg.route("/health",web::get().to(health_check_handler));}

然后新建一个 handlers.rs 方法来定于我们的对于路由的处理函数,这里我们可以调用全局注册的 app_state ,这个内容会在下一部分讲到。我们取出共享数据里的 访问次数和响应内容,之后返回一个 json 数据。

usesuper::state::AppState;useactix_web::{web,HttpResponse};pubasyncfnhealth_check_handler(app_state:web::Data<AppState>)->HttpResponse{println!("incoming for health check");let health_check_response =&app_state.health_check_response;letmut visit_count = app_state.visit_count.lock().unwrap();let response =format!("{} {} times", health_check_response, visit_count);*visit_count +=1;HttpResponse::Ok().json(&response)}

最后我们配置我们的主函数 teacher-service.rs ,在 3000 端口启动我们的项目,我们将一个初始化的 shared_data 配置到项目中,之后在项目的整个的流程中都可以使用它

useactix_web::{web,App,HttpServer};usestd::io;usestd::sync::Mutex;#[path = "../handlers.rs"]modhandlers;#[path = "../models.rs"]modmodelds;#[path = "../routers.rs"]modrouters;#[path = "../state.rs"]modstate;userouters::*;usestate::AppState;#[actix_rt::main]asyncfnmain()->io::Result<()>{let shared_data =web::Data::new(AppState{
        health_check_response:"I'm OK.".to_string(),
        visit_count:Mutex::new(0),
        courses:Mutex::new(vec![]),});let app =move||{App::new().app_data(shared_data.clone()).configure(general_routes)};HttpServer::new(app).bind("127.0.0.1:3000")?.run().await}

这样我们就可以在 127.0.0.1:3000 启动我们的项目,当你调用 127.0.0.1:3000/health 的时候,你可以看到输出了

I'm OK. 1 times

,每调用一次,times + 1

处理POST 请求

我们现在已经可以处理 get 请求并且返回一组预定的数据了,现在我们来尝试调用 POST 请求来新增我们的数据:

我们首先注册一个新的路由,它在一个

/courses

的空间中,表示它的所有 api 都必须使用 localhost:3000/courses 开头,我们先添加一个 localhost:3000/courses 的路由,它是 post 方法,用于新增一条数据

pubfncourse_routes(cfg:&mutweb::ServiceConfig){
    cfg.service(web::scope("/courses").route("/",web::post().to(new_course)));}

之后我们在 handlers.rs 编写它的处理函数:我们要做的是把我们收到的数据写入到 app_state 中,我们先计算出有多少个数据来计算出新增数据的 id 号作为唯一标识,然后将传入数据存入我们的全局数据中

要注意,我们需要先获取所有权,然后将数据克隆一份来计算长度,否则数据在使用完毕以后就被回收了:

usesuper::modelds::Course;usechrono::Utc;pubasyncfnnew_course(
    new_course:web::Json<Course>,
    app_state:web::Data<AppState>,)->HttpResponse{println!("Received new course");let course_count = app_state
        .courses
        .lock().unwrap().clone().into_iter().filter(|course| course.teacher_id == new_course.teacher_id).collect::<Vec<Course>>().len();let new_course =Course{
        teacher_id: new_course.teacher_id,
        id:Some(course_count +1),
        name: new_course.name.clone(),
        time:Some(Utc::now().naive_utc()),};
    app_state.courses.lock().unwrap().push(new_course);HttpResponse::Ok().json("Course added")}

我们编写一个测试来测试我们的接口:

modtests{usesuper::*;useactix_web::http::StatusCode;usestd::sync::Mutex;#[actix_rt::test]asyncfnpost_course_test(){let course =web::Json(Course{
            teacher_id:1,
            name:"Test course".into(),
            id:None,
            time:None,});let app_state:web::Data<AppState>=web::Data::new(AppState{
            health_check_response:"".to_string(),
            visit_count:Mutex::new(0),
            courses:Mutex::new(vec![]),});let resp =new_course(course, app_state).await;assert_eq!(resp.status(),StatusCode::OK);}}

动态路由

有时候我们希望我们的路径中带有我们需要的查询数据,例如,我们希望通过

/course/1

来查询对应 id 为1 的老师的课程,通过

/course/1/12

来查询对应 id 为1 的老师 id 为 12的课程,那么我们需要构建一个动态路由:

首先我们这样编写一个路由,其中的 user_id 和 course_id 可以作为参数提取到,而我们的路径可以匹配到这些路由

pubfncourse_routes(cfg:&mutweb::ServiceConfig){
    cfg.service(web::scope("/courses").route("/",web::post().to(new_course)).route("/{user_id}",web::get().to(get_courses_for_teacher)).route("/{user_id}/{course_id}",web::get().to(get_course_detail)),);}

之后我们在 handlers 里编写处理方法,通过传入参数 params 可以拿到我们的路径,我们需要构建我们的查询来返回对应的值:

pubasyncfnget_courses_for_teacher(
    app_state:web::Data<AppState>,
    params:web::Path<usize>,)->HttpResponse{let teacher_id:usize= params.0;let filtered_courses = app_state
        .courses
        .lock().unwrap().clone().into_iter().filter(|course| course.teacher_id == teacher_id).collect::<Vec<Course>>();if filtered_courses.len()>0{HttpResponse::Ok().json(filtered_courses)}else{HttpResponse::Ok().json("No courses found for teacher".to_string())}}pubasyncfnget_course_detail(
    app_state:web::Data<AppState>,
    params:web::Path<(usize,usize)>,)->HttpResponse{let(teacher_id, course_id)= params.0;let selected_course = app_state
        .courses
        .lock().unwrap().clone().into_iter().find(|x| x.teacher_id == teacher_id && x.id ==Some(course_id)).ok_or("Course not found");ifletOk(course)= selected_course {HttpResponse::Ok().json(course)}else{HttpResponse::Ok().json("Course not found".to_string())}}

我们也可以为我们编写的这两个方法添加测试:

#[actix_rt::test]asyncfnget_all_courses_success(){let app_state:web::Data<AppState>=web::Data::new(AppState{
            health_check_response:"".to_string(),
            visit_count:Mutex::new(0),
            courses:Mutex::new(vec![]),});let teacher_id:web::Path<usize>=web::Path::from(1);let resp =get_courses_for_teacher(app_state, teacher_id).await;assert_eq!(resp.status(),StatusCode::OK);}#[actix_rt::test]asyncfnget_one_course_success(){let app_state:web::Data<AppState>=web::Data::new(AppState{
            health_check_response:"".to_string(),
            visit_count:Mutex::new(0),
            courses:Mutex::new(vec![]),});let params:web::Path<(usize,usize)>=web::Path::from((1,1));let resp =get_course_detail(app_state, params).await;assert_eq!(resp.status(),StatusCode::OK);}

如果通过测试,我们将拥有一个完整的具有新增和查询功能的 api 了,我们将刚刚编写的路由注册到我们的主程序:

asyncfnmain()->io::Result<()>{let shared_data =web::Data::new(AppState{
        health_check_response:"I'm OK.".to_string(),
        visit_count:Mutex::new(0),
        courses:Mutex::new(vec![]),});let app =move||{App::new().app_data(shared_data.clone()).configure(general_routes).configure(course_routes)};HttpServer::new(app).bind("127.0.0.1:3000")?.run().await}

现在你可以通过 POSTMAN 等工具来测试新增和查询数据的 api 了,之后我们将会讲解通过数据库来持久化我们的数据,而不是用全局注入的数据结构存储数据。

标签: rust 前端 后端

本文转载自: https://blog.csdn.net/weixin_46463785/article/details/129120470
版权归原作者 摸鱼老萌新 所有, 如有侵权,请联系我们删除。

“Rust Web入门(二):Actix”的评论:

还没有评论