6黃頁網(wǎng)站建設(shè)網(wǎng)絡(luò)推廣公司主要做什么
本教程筆記來自 楊旭老師的 rust web 全棧教程,鏈接如下:
https://www.bilibili.com/video/BV1RP4y1G7KF?p=1&vd_source=8595fbbf160cc11a0cc07cadacf22951
學(xué)習(xí) Rust Web 需要學(xué)習(xí) rust 的前置知識可以學(xué)習(xí)楊旭老師的另一門教程
https://www.bilibili.com/video/BV1hp4y1k7SV/?spm_id_from=333.999.0.0&vd_source=8595fbbf160cc11a0cc07cadacf22951
今天來入門基于 rust 的 web 框架 Actix:
Actix簡單使用
Actix - Rust 的 Actor 異步并發(fā)框架
Actix 基于 Tokio 和 Future,開箱具有異步非阻塞事件驅(qū)動并發(fā)能力,其實(shí)現(xiàn)低層級 Actor 模型來提供無鎖并發(fā)模型,而且同時(shí)提供同步 Actor,具有快速、可靠,易可擴(kuò)展。
Actix 之上是高性能 Actix-web 框架,很容易上手。使用 Actix-web 開發(fā)的應(yīng)用程序?qū)⒃诒緳C(jī)可執(zhí)行文件中包含 HTTP 服務(wù)器。你可以把它放在另一個(gè)像 nginx 這樣的 HTTP 服務(wù)器上。但即使完全不存在另一個(gè) HTTP 服務(wù)器 (像 nginx) 的情況下,Actix-web 也足以提供 HTTP 1 和 HTTP 2 支持以及 SSL/TLS。這對于構(gòu)建微服務(wù)分發(fā)非常有用。
我們需要先創(chuàng)建一個(gè)項(xiàng)目,然后引入需要的依賴,然后使用 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 下創(chuàng)建一個(gè) bin 目錄和一個(gè) server1.rs 編寫我們的框架:
對于 server1.rs 我們需要初始化一個(gè) app 作為我們的 web 項(xiàng)目,然后為它配置一個(gè)路由的函數(shù),之后再指定的端口運(yùn)行我們的 app 項(xiàng)目。因?yàn)樗钱惒降?#xff0c;所以我們要加上 await 和 async 進(jìn)行修飾并且使用 actix_rt::main 這個(gè)包
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
}
之后我們編寫我們的路由函數(shù),它傳入一個(gè)配置項(xiàng),你可以在其中配置對應(yīng)路由的處理方法,比如我們處理 /health 路徑的 get 方法,我們就可以用如下的方式進(jìn)行編寫,在 to 之后提供一個(gè)函數(shù)作為我們的處理函數(shù)。
處理函數(shù)是需要實(shí)現(xiàn) Responder 這個(gè) Trait 的,所以我們的返回值需要使用 HttpResponse 相關(guān)的函數(shù)進(jìn)行返回,其中 Ok() 表示 200 這個(gè)狀態(tài)碼,之后又使用 json 函數(shù)返回了一段 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!")
}
現(xiàn)在我們的創(chuàng)建搭建完畢了,我們在命令行啟動我們的項(xiàng)目,然后訪問 120.0.0.1:3000 ,可以看到,Actix Web Service is running! 這句話,那么我們的項(xiàng)目就可以正常使用了
構(gòu)建完整的 rust API
現(xiàn)在我們已經(jīng)可以運(yùn)行我們的 Actix 框架了,之后我們來嘗試構(gòu)建一個(gè)完整的具有增刪改查功能的 api,我們再新建一個(gè) teacher-service.rs 把這個(gè)項(xiàng)目設(shè)置為默認(rèn)項(xiàng)目,并且加載我們需要的包:
[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"
數(shù)據(jù)庫的部分將會在下一部分講解,我們先把我們的數(shù)據(jù)放在內(nèi)存中,我們先建立一個(gè) models.rs 它用于定義我們的數(shù)據(jù)結(jié)構(gòu), 通過剛剛引入的 serde 包,我們可以讓 json 數(shù)據(jù)轉(zhuǎn)化為我們的數(shù)據(jù)結(jié)構(gòu)
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,}}
}
之后我們編寫一個(gè) state.rs 封裝我們?nèi)止蚕淼臄?shù)據(jù)結(jié)構(gòu),它包括一個(gè)響應(yīng),一個(gè)訪問次數(shù)和一個(gè)返回的結(jié)構(gòu)體,這個(gè)內(nèi)容將作為全局內(nèi)容在我們的程序中共享,因?yàn)樯婕暗蕉鄠€(gè)程序會調(diào)用 visit_count 和 courses 數(shù)據(jù),所以我們把他們放在 Mutex 中來保證互斥調(diào)用:
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));
}
然后新建一個(gè) handlers.rs 方法來定于我們的對于路由的處理函數(shù),這里我們可以調(diào)用全局注冊的 app_state ,這個(gè)內(nèi)容會在下一部分講到。我們?nèi)〕龉蚕頂?shù)據(jù)里的 訪問次數(shù)和響應(yīng)內(nèi)容,之后返回一個(gè) json 數(shù)據(jù)。
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)
}
最后我們配置我們的主函數(shù) teacher-service.rs ,在 3000 端口啟動我們的項(xiàng)目,我們將一個(gè)初始化的 shared_data 配置到項(xiàng)目中,之后在項(xiàng)目的整個(gè)的流程中都可以使用它
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 啟動我們的項(xiàng)目,當(dāng)你調(diào)用 127.0.0.1:3000/health 的時(shí)候,你可以看到輸出了
I'm OK. 1 times
,每調(diào)用一次,times + 1
處理POST 請求
我們現(xiàn)在已經(jīng)可以處理 get 請求并且返回一組預(yù)定的數(shù)據(jù)了,現(xiàn)在我們來嘗試調(diào)用 POST 請求來新增我們的數(shù)據(jù):
我們首先注冊一個(gè)新的路由,它在一個(gè) /courses
的空間中,表示它的所有 api 都必須使用 localhost:3000/courses 開頭,我們先添加一個(gè) localhost:3000/courses 的路由,它是 post 方法,用于新增一條數(shù)據(jù)
pub fn course_routes(cfg: &mut web::ServiceConfig) {cfg.service(web::scope("/courses").route("/", web::post().to(new_course)));
}
之后我們在 handlers.rs 編寫它的處理函數(shù):我們要做的是把我們收到的數(shù)據(jù)寫入到 app_state 中,我們先計(jì)算出有多少個(gè)數(shù)據(jù)來計(jì)算出新增數(shù)據(jù)的 id 號作為唯一標(biāo)識,然后將傳入數(shù)據(jù)存入我們的全局?jǐn)?shù)據(jù)中
要注意,我們需要先獲取所有權(quán),然后將數(shù)據(jù)克隆一份來計(jì)算長度,否則數(shù)據(jù)在使用完畢以后就被回收了:
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")
}
我們編寫一個(gè)測試來測試我們的接口:
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);}
}
動態(tài)路由
有時(shí)候我們希望我們的路徑中帶有我們需要的查詢數(shù)據(jù),例如,我們希望通過 /course/1
來查詢對應(yīng) id 為1 的老師的課程,通過 /course/1/12
來查詢對應(yīng) id 為1 的老師 id 為 12的課程,那么我們需要構(gòu)建一個(gè)動態(tài)路由:
首先我們這樣編寫一個(gè)路由,其中的 user_id 和 course_id 可以作為參數(shù)提取到,而我們的路徑可以匹配到這些路由
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 里編寫處理方法,通過傳入?yún)?shù) params 可以拿到我們的路徑,我們需要構(gòu)建我們的查詢來返回對應(yīng)的值:
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())}
}
我們也可以為我們編寫的這兩個(gè)方法添加測試:
#[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);}
如果通過測試,我們將擁有一個(gè)完整的具有新增和查詢功能的 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
}
現(xiàn)在你可以通過 POSTMAN 等工具來測試新增和查詢數(shù)據(jù)的 api 了,之后我們將會講解通過數(shù)據(jù)庫來持久化我們的數(shù)據(jù),而不是用全局注入的數(shù)據(jù)結(jié)構(gòu)存儲數(shù)據(jù)。
說明
本教程只是作者的學(xué)習(xí)筆記,幫助理解和記憶 RUST 開發(fā),課程全部來源是 B站 楊旭老師的教程:
https://www.bilibili.com/video/BV1RP4y1G7KF?p=1&vd_source=8595fbbf160cc11a0cc07cadacf22951
教學(xué) demo 可以查看大佬的 git:
https://github.com/FrancisYLfan/rust_web_server_from_yang