# 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
今天来入门基于 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 了,之后我们将会讲解通过数据库来持久化我们的数据,而不是用全局注入的数据结构存储数据。
说明
本教程只是作者的学习笔记,帮助理解和记忆 RUST 开发,课程全部来源是 B站 杨旭老师的教程:
https://www.bilibili.com/video/BV1RP4y1G7KF?p=1&vd_source=8595fbbf160cc11a0cc07cadacf22951
教学 demo 可以查看大佬的 git:
https://github.com/FrancisYLfan/rust_web_server_from_yang
相关文章:
# Rust Web入门(二):Actix
本教程笔记来自 杨旭老师的 rust web 全栈教程,链接如下: https://www.bilibili.com/video/BV1RP4y1G7KF?p1&vd_source8595fbbf160cc11a0cc07cadacf22951 学习 Rust Web 需要学习 rust 的前置知识可以学习杨旭老师的另一门教程 https://www.bili…...
jvm之String
基本特性 字符串,使用一对""引起来表示声明为final的,不可被继承实现了Serializable接口:表示字符串是支持序列化的实现了Comparable接口:表示String 可以比较大小在jdk8及以前内部定义了final char[] value用于存储字…...
WebRTC系列-工具系列之ByteBuffer,BitBuffer及相关类
文章目录 1. 类介绍1.1 ByteBuffer及子类1.2 BitBuffer类1.3 基础内存操作类BufferT2. 源码分析(stun response消息解析)2.1 消息头解析2.2 消息中Attribute解析3. 结语在之前的文章 WebRTC系列-Qos系列之RTP/RTCP协议分析及后续的文章中详细的介绍了RTP/RTCP协议的相关内容,…...
Spring中bean的生命周期(通俗易懂)
具体流程 bean的生命周期分4个阶段: 1.实例化 2.属性赋值 3.初始化 4.销毁 实例化就是在内存中new()出一个对象,属性赋值就是给那些被Autowired修饰的属性注入对象,销毁是在Spring容器关闭时触发,初始化的步骤比较…...
雷达编程实战之恒虚警率(CFAR)检测
在雷达系统中,目标检测是一项非常重要的任务。检测本身非常简单,它将信号与阈值进行比较,超过阈值的信号则认为是目标信号,所以目标检测的真正工作是寻找适当的阈值。由于目标误检的严重后果,因此雷达系统希望有一个检…...
Github隐藏功能:显示自己的README,Github 个人首页的 README,这样玩儿
内容概览 前言创建仓库修改 README 的内容总结前言 大家最近有没有发现这个现象,有些名人的 Github 首页变得更丰富了?尤其是那个夺目的 README 板块!!! 请看,这是 iOS 喵神 的 Github 首页: …...
@JsonSerialize—优雅地封装返回值
1.场景项目开发中给前端提供查询接口时,经常遇到需要将从数据库中取出来的字段值做一层重新封装。比如数据库中存的状态值是数字,返回给前端的时候,前端并不知道这个数值代表什么意思。此时,有两种方式:(1&…...
【Python网络编程】利用Python进行TCP、UDP套接字编程
之前实现了Java版本的TCP和UDP套接字编程的例子,于是决定结合Python的学习做一个Python版本的套接字编程实验。 流程如下: 1.一台客户机从其标准输入(键盘)读入一行字符,并通过其套接字将该行发送到服务器。 2.服务…...
fuzz测试之libfuzzer使用小结
fuzz测试之libfuzzer使用小结背景基本原理使用方法主调DEMO参考资料背景 项目中,为测试算法的鲁棒性,经常会用到fuzz测试进行压力测试。fuzz测试是一种模糊测试方法,本质是通过灌入各种变异的随机数据,去遍历不同函数分支…...
电子标签拣货系统——外接供电版
Power_DC24v 型号:Power_DC24v24V电源适配器级联线:长30cm直径:15mmCK_Wire_V1 型号:CK_Wire_V1连接电源适配器级联线:长30cm公线:长宽厚 14*11*9mm母线:长宽厚 13*5.5*3mmCK_Wire_V2 型号&…...
为什么启动一个线程不用run()方法,而是用start()方法
在使用java多线程时,有三种方式创建线程 复习传送门 当使用继承Thread来实现多线程时, 我们会把线程执行的代码写在run() 方法中, 使用Thread的start()方法来启动一个线程。 代码如下: public class ThreadDemo extends Thread{O…...
Java File相关操作
文章目录File文件操作IO流处理流缓冲流转换流对象流File文件操作 利用File类来操作。 文件操作中常用到相对目录和绝对路径 package org.File; import java.io.File; public class demo01 { public static void main(String[] args) { try{ File file new File("…...
LabVIEW利用矢量量化直方图开发人脸识别
LabVIEW利用矢量量化直方图开发人脸识别通常,人脸识别系统会检查场景的静止图像或视频图像,然后使用存储的人脸数据库识别或验证场景中的一个或多个人。我程序专注于静止图像人脸识别,使用来自众所周知的人脸数据库的人脸图像,用于…...
RK3568工业开发板工控板说明
说明HW356X-GKA是采用中高端的通用型 SOC,一款基于Rockchip公司RK3568处理器的工控主板。主板标配处理器为Cortex-A55四核,最高主频2GHz的RK3568处理器,内置4GB DDR4内存(最大8GB),32GB eMMC存储。集成4核 arm架构 A55 处理器和Ma…...
JavaScript Web API 来构建你不了解的网站
随着技术的日新月异,为开发人员提供了令人难以置信的新工具和API。 但据了解,在100 多个 API中,只有5%被开发人员积极使用。 随着技术的日新月异,为开发人员提供了令人难以置信的新工具和API。但据了解,在100 多个 A…...
KeePass敏感信息明文传输漏洞复现 (CVE-2023-24055)
一、漏洞描述 漏洞简述 KeePass 是一款免费的开源密码管理器,可帮助您以安全的方式管理您的密码。您可以将所有密码存储在一个数据库中,该数据库由一把万能钥匙锁定。因此,您只需记住一个主密钥即可解锁整个数据库。数据库文件使用目前已知…...
Android 11 中的权限更新
官网:https://developer.android.com/about/versions/11/privacy/permissions#audit-by-feature Android 11 使用户能够为位置、麦克风和摄像头指定更精细的权限。此外,系统会重置针对 Android 11 或更高版本的未使用应用程序的权限,如果应用…...
october-cms
环境准备 靶机链接:百度网盘 请输入提取码 提取码:3e4s 虚拟机网络链接模式:桥接模式 攻击机系统:kali linux 2021.1 信息收集 1.探测目标靶机ip。 2.探测靶机开放端口和服务情况。 漏洞探测 1.访问网页 2.用dirsearch扫描…...
抖音怎样报白?报白需要审核哪些资料呢
抖音怎样报白?报白需要审核哪些资料呢 抖音报白需要什么资料,翡翠原石产品如何开通报白#报白#小店报白#小店运营#抖音#抖音小店运营 文/专栏作家百收 随着抖音在国内流行起来,抖音上每天会有大量的视频更新,越来越多的年轻人也加…...
Spring中的AOP
Spring中的AOP 文章目录Spring中的AOPAOP概述相关术语总结作用AOP概述 AOP(Aspect Programming) 是一种设计思想,是面向切面编程思想 跟OOP(面向对象编程)有什么关系呢? AOP面向切面编程然后是OOP(面向对象编程)的补充和完善。…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...
