# 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(面向对象编程)的补充和完善。…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...
