Rust Web入门(二):Actix

这篇具有很好参考价值的文章主要介绍了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 这个包

use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use std::io;
#[actix_rt::main]
async fn main() -> 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 作为作为我们的返回值

pub fn general_routes(cfg: &mut web::ServiceConfig) {
    cfg.route("/health", web::get().to(health_check_handler));
}

pub async fn health_check_handler() -> impl Responder {
    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 数据转化为我们的数据结构

use actix_web::web;
use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Course {
    pub teacher_id: usize,
    pub id: Option<usize>,
    pub name: String,
    pub time: Option<NaiveDateTime>,
}

impl From<web::Json<Course>> for Course {
    fn from(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 中来保证互斥调用:

use std::sync::Mutex;

use crate::modelds::Course;

pub struct AppState {
    pub health_check_response: String,
    pub visit_count: Mutex<u32>,
    pub courses: Mutex<Vec<Course>>,
}

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

use super::handlers::*;
use actix_web::web;
pub fn general_routes(cfg: &mut web::ServiceConfig) {
    cfg.route("/health", web::get().to(health_check_handler));
}

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

use super::state::AppState;
use actix_web::{web, HttpResponse};

pub async fn health_check_handler(app_state: web::Data<AppState>) -> HttpResponse {
    println!("incoming for health check");
    let health_check_response = &app_state.health_check_response;
    let mut 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 配置到项目中,之后在项目的整个的流程中都可以使用它

use actix_web::{web, App, HttpServer};
use std::io;
use std::sync::Mutex;

#[path = "../handlers.rs"]
mod handlers;
#[path = "../models.rs"]
mod modelds;
#[path = "../routers.rs"]
mod routers;
#[path = "../state.rs"]
mod state;

use routers::*;
use state::AppState;

#[actix_rt::main]
async fn main() -> 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 方法,用于新增一条数据

pub fn course_routes(cfg: &mut web::ServiceConfig) {
    cfg.service(
        web::scope("/courses")
            .route("/", web::post().to(new_course))
    );
}

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

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

use super::modelds::Course;
use chrono::Utc;
pub async fn new_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")
}

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

mod tests {
    use super::*;
    use actix_web::http::StatusCode;
    use std::sync::Mutex;

    #[actix_rt::test]
    async fn post_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 可以作为参数提取到,而我们的路径可以匹配到这些路由

pub fn course_routes(cfg: &mut web::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 可以拿到我们的路径,我们需要构建我们的查询来返回对应的值:

pub async fn get_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())
    }
}

pub async fn get_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");
    if let Ok(course) = selected_course {
        HttpResponse::Ok().json(course)
    } else {
        HttpResponse::Ok().json("Course not found".to_string())
    }
}

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

  #[actix_rt::test]
    async fn get_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]
    async fn get_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 了,我们将刚刚编写的路由注册到我们的主程序:

async fn main() -> 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 了,之后我们将会讲解通过数据库来持久化我们的数据,而不是用全局注入的数据结构存储数据。文章来源地址https://www.toymoban.com/news/detail-555417.html

到了这里,关于Rust Web入门(二):Actix的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【跟小嘉学 Rust 编程】三十三、Rust的Web开发框架之一: Actix-Web的基础

    【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学 Rust 编程】六、枚举

    2024年02月04日
    浏览(53)
  • Rust Web小项目

    监听TCP链接 获取请求数据 响应Http请求 返回一个http数据 hello.html如上所示 访问其他路径报故障 404.html 代码重构 多线程 最简单的 这样来一个请求就会创建一个线程 但是如果被攻击,就会一直创建很多线程 加入线程池 main.rs lib.rs 优雅地停机 这只是一个简单的小工程,目的是

    2024年01月24日
    浏览(40)
  • 【rust/入门】windows安装rust gnu环境(折腾)

    首先说明,我是rust入门选手,之前都是在wsl写rust,突然想在windows下装下rust。 windows版本:windows11 22H2 原文 换源 看到教程我陷入了沉默, (官方推荐) 打开 Microsoft C++ Build Tools 我开始不解,这是让我安装vs studio? 我装个rust你让我装几个G的环境? 再见,告辞!我是菜鸡! 等等

    2024年02月13日
    浏览(50)
  • rust入门系列之Rust介绍及开发环境搭建

    Rust基本介绍 网站: https://www.rust-lang.org/ rust是什么 开发rust语言的初衷是: 在软件发展速度跟不上硬件发展速度,无法在语言层面充分的利用硬件多核cpu不断提升的性能和 在系统界别软件开发上,C++出生比较早,内存管理容易出现安全问题的背景下。 为了解决开发系统界别软

    2024年02月12日
    浏览(65)
  • 【小沐学Web】Rust实现Web服务器

    https://www.rust-lang.org/ Rust: 一种使每个人都能够构建可靠且高效的软件的语言。 如今,全球有数百家公司在生产环境中使用 Rust,以提供快速、资源少、跨平台的解决方案。您熟悉和喜爱的软件,例如Firefox、 Dropbox和Cloudflare,都使用 Rust。从初创公司到大公司,从嵌入式设备到

    2024年02月07日
    浏览(60)
  • Rust语言从入门到入坑——(5)Rust 所有权

    主要介绍Rust所有权的知识,涉及到变量的作用域,内存释放机制,移动,克隆,引用等知识,很多知识是Rust语言特有机制。 所有权有以下三条规则: - Rust 中的每个值都有一个变量,称为其所有者。 - 一次只能有一个所有者。 - 当所有者不在程序运行范围时,该值将被删除

    2024年02月10日
    浏览(45)
  • Rust编程语言入门之Rust的面向对象编程特性

    Rust 受到多种编程范式的影响,包括面向对象 面向对象通常包含以下特性:命名对象、封装、继承 “设计模式四人帮”在《设计模型》中给面向对象的定义: 面向对象的程序由对象组成 对象包装了数据和操作这些数据的过程,这些过程通常被称作方法或操作 基于此定义:

    2023年04月21日
    浏览(54)
  • Web和云开发,Rust会起飞?

    Web和云开发,Rust会起飞? 一、前言 二、大厂偏爱,Rust的未来 三、Rust做Web的雄心 四、有必要换Rust做Web? 1.效率和性能 2.可靠性和可维护性 五、Rust先苦后甜 六、用Rust前的几个问题 七、开发界的强者 去年,Web开发公司Mainmatter对Web版 Rust 进行了战略押注,并发起了 EuroRust

    2024年02月12日
    浏览(36)
  • Rust 在前端都干了些啥

    前言 这里有一篇两年前的文章:Rust 是 JavaScript 基础设施的未来,应该还是有挺多人看到过的。当时在前端社区上还掀起了一阵 Rust 风,有人说怎么天天造轮子,有人说实在是学不动了,也有人抱着积极的心态去拥抱新东西。 那么现在两年已经过去了,好像 Rust 在前端领域的

    2024年02月07日
    浏览(33)
  • Rust #[actix_web::main]

    #[actix_web::main] 是一个 Rust 属性宏(Attribute Macro),用于启动 Actix-Web 框架的运行时。这个宏通常被放在程序的入口函数上,例如: 在这个例子中, #[actix_web::main] 属性宏告诉编译器将这个函数包装成一个可执行文件,并启动 Actix-Web 框架的运行时。这个函数也被称为 “应用程

    2024年02月03日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包