系列文章目錄
【跟小嘉學(xué) Rust 編程】一、Rust 編程基礎(chǔ)
【跟小嘉學(xué) Rust 編程】二、Rust 包管理工具使用
【跟小嘉學(xué) Rust 編程】三、Rust 的基本程序概念
【跟小嘉學(xué) Rust 編程】四、理解 Rust 的所有權(quán)概念
【跟小嘉學(xué) Rust 編程】五、使用結(jié)構(gòu)體關(guān)聯(lián)結(jié)構(gòu)化數(shù)據(jù)
【跟小嘉學(xué) Rust 編程】六、枚舉和模式匹配
【跟小嘉學(xué) Rust 編程】七、使用包(Packages)、單元包(Crates)和模塊(Module)來管理項(xiàng)目
【跟小嘉學(xué) Rust 編程】八、常見的集合
【跟小嘉學(xué) Rust 編程】九、錯(cuò)誤處理(Error Handling)
【跟小嘉學(xué) Rust 編程】十一、編寫自動(dòng)化測(cè)試
【跟小嘉學(xué) Rust 編程】十二、構(gòu)建一個(gè)命令行程序
【跟小嘉學(xué) Rust 編程】十三、函數(shù)式語言特性:迭代器和閉包
【跟小嘉學(xué) Rust 編程】十四、關(guān)于 Cargo 和 Crates.io
【跟小嘉學(xué) Rust 編程】十五、智能指針(Smart Point)
【跟小嘉學(xué) Rust 編程】十六、無畏并發(fā)(Fearless Concurrency)
【跟小嘉學(xué) Rust 編程】十七、面向?qū)ο笳Z言特性
【跟小嘉學(xué) Rust 編程】十八、模式匹配(Patterns and Matching)
【跟小嘉學(xué) Rust 編程】十九、高級(jí)特性
【跟小嘉學(xué) Rust 編程】二十、進(jìn)階擴(kuò)展
【跟小嘉學(xué) Rust 編程】二十一、網(wǎng)絡(luò)編程
【跟小嘉學(xué) Rust 編程】二十三、Cargo 使用指南
【跟小嘉學(xué) Rust 編程】二十四、內(nèi)聯(lián)匯編(inline assembly)
【跟小嘉學(xué) Rust 編程】二十五、Rust命令行參數(shù)解析庫(clap)
【跟小嘉學(xué) Rust 編程】二十六、Rust的序列化解決方案(Serde)
【跟小嘉學(xué) Rust 編程】二十七、Rust 異步編程(Asynchronous Programming)
【跟小嘉學(xué) Rust 編程】二十八、Rust中的日期與時(shí)間
【跟小嘉學(xué) Rust 編程】二十九、Rust 中的零拷貝序列化解決方案(rkyv)
【跟小嘉學(xué) Rust 編程】三十、Rust 使用 Slint UI
【跟小嘉學(xué) Rust 編程】三十一、Rust的日志與追蹤
【跟小嘉學(xué) Rust 編程】三十二、Rust的設(shè)計(jì)模式(Design Patterns)
【跟小嘉學(xué) Rust 編程】三十三、Rust的Web開發(fā)框架之一: Actix-Web的基礎(chǔ)
前言
本章節(jié)講解 Rust的Web開發(fā)框架的介紹和對(duì)比。Actix Web、Axum、Rocket、Warp、Tide、Poem、Pavex、Hyper等框架。
主要教材參考 《The Rust Programming Language》
主要教材參考 《Rust For Rustaceans》
主要教材參考 《The Rustonomicon》
主要教材參考 《Rust 高級(jí)編程》
主要教材參考 《Cargo 指南》
主要教材參考 《Rust 異步編程》
主要教材參考 《Rust 設(shè)計(jì)模式》
一、Rust Web開發(fā)框架了解
1.1、Hyper
Hyper 是一個(gè)受保護(hù)的、高效的http庫,目前還在開發(fā)之中,當(dāng)前版本在0.14版本;可以用作如下
- 用于 web 服務(wù)通信的客戶端;
- 用于構(gòu)建 Web 服務(wù)的服務(wù)器;
- 極快的響應(yīng)速度;
- 具有高并發(fā)性和非阻塞套接字;
- 支持 http/1 和 http/2;
1.2、Actix-web
Actix-web 是一個(gè)強(qiáng)大、實(shí)用且速度極快的 Rust web 框架。Actix Web 基于 rust Actor Model。它是一個(gè)用 Rust 編寫的高性能 Web 框架,具有一組用于構(gòu)建 Web 應(yīng)用程序的強(qiáng)大功能。
- 支持多路復(fù)用;
- 異步I/O;
- 網(wǎng)絡(luò)套接字
- 中間件支持
1.3、Rocket
Rocket 是一個(gè)簡(jiǎn)單、快速、類型安全的 Rust Web 框架。目前最新版本是0.5.0.rc.3。與 Rust 生態(tài)系統(tǒng)緊密集成,集成現(xiàn)有的庫和工具非常容易
支持模板、支持異步流開箱即用。
Rocket 哲學(xué):最少的配置啟動(dòng)和運(yùn)行。
1.4、Tide
Tide 是一個(gè)基于 Rust 構(gòu)建的最小且實(shí)用的 Web 應(yīng)用程序框架。Tide 是為快速 Web 開發(fā)而構(gòu)建的。Tide 帶有一組強(qiáng)大的內(nèi)置功能,可以輕松構(gòu)建異步 Web 應(yīng)用程序和 API。Tide 基于 rust actix Web 框架。
Tide 是功能豐富的 Web 框架。Tide 正在積極開發(fā)中,并擁有廣泛的社區(qū)資源,可讓您快速啟動(dòng)和運(yùn)行
Tide 框架具有以下功能,可幫助快速構(gòu)建應(yīng)用程序
- 異步/等待
- 支持類型安全路由
- 請(qǐng)求守衛(wèi)
- 模板支持
- 會(huì)話管理
- 網(wǎng)絡(luò)套接字支持
1.5、Warp
Warp 是一個(gè)超級(jí)簡(jiǎn)單、可組合的 Web 服務(wù)器框架,基于 Rust 構(gòu)建,用于提高速度。Warp 突出的構(gòu)建塊是 Filter,它可以組合和組合以表達(dá)對(duì)請(qǐng)求的豐富需求.
得益于其過濾系統(tǒng),warp 提供開箱即用的功能:
- 路徑路由和參數(shù)提取
- 標(biāo)頭要求和提取
- 查詢字符串反序列化
- JSON 和表單正文
- 多部分表單數(shù)據(jù)
- 靜態(tài)文件和目錄
- 網(wǎng)絡(luò)套接字
- 訪問日志記錄
- Gzip、Deflate 和 Brotli 壓縮
- 服務(wù)器發(fā)送的事件 (SSE)
由于它建立在 hyper 和 Tokio - 一個(gè)異步 Rust 運(yùn)行時(shí)之上,因此您可以自動(dòng)獲得:
- HTTP/1 和 HTTP/2 支持
- 異步功能
- 最快的 HTTP 實(shí)現(xiàn)之一
- 經(jīng)過測(cè)試和正確
1.6、Axum
Axum Web 框架旨在高效、快速和輕量級(jí)。Axum 是一個(gè)專注人體工程學(xué)和模塊化的Web應(yīng)用程序框架。
- 使用無宏 API 將請(qǐng)求路由到處理程序。
- 使用提取程序以聲明方式分析請(qǐng)求。
- 簡(jiǎn)單且可預(yù)測(cè)的錯(cuò)誤處理模型。
- 使用最少的樣板生成響應(yīng)。
- 充分利用中間件、服務(wù)和 tower-http。
- 支持 WebSocket 和其他協(xié)議
- 異步 I/O
1.7、Poem
Poem 是一個(gè)用 Rust 編寫的 Web 框架,提供了簡(jiǎn)潔的 API,并且功能豐富;它可以將自身與 Web 框架 的許多主要功能解耦,從而為開發(fā)人員提供盡可能多的靈活性。
是基于 tokio/hyper 的web服務(wù)端開發(fā)框架。
二、Actix-Web基礎(chǔ)
2.1、Actix-Web 介紹
Actix-Web 是 crate 生態(tài)系統(tǒng)的一部分。早期 Actix-Web 是建立在 Acti actor 框架之上的。現(xiàn)在 Actix-Web 在很大程度上與 actor 框架無關(guān),并且是使用不同的系統(tǒng)構(gòu)建的,盡管 Actix 仍然被保留,但是隨著 future、async、await 生態(tài)系統(tǒng)的成熟,它作為通用工具的用處正在減弱。
現(xiàn)在只有 websocket 端點(diǎn)才需要使用 actix。
2.2、hello world
2.2.1、創(chuàng)建工程
cargo new hello-world
2.2.2、添加依賴
cd hello-world
cargo add actix-web
或者修改 cargo.toml 文件
actix-web = "4.4.0"
2.2.3、編輯 src/main.rs 文件
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello world!")
}
#[post("/echo")]
async fn echo(req_body: String) -> impl Responder {
HttpResponse::Ok().body(req_body)
}
async fn manual_hello() -> impl Responder {
HttpResponse::Ok().body("Hey there!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hello)
.service(echo)
.route("/hey", web::get().to(manual_hello))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2.2.4、啟動(dòng)測(cè)試
1、啟動(dòng)
cargo run
2、測(cè)試:瀏覽器訪問
http://localhost:8080/hey
http://localhost:8080
2.2.5、代碼解讀
- #[actix_web::main] :標(biāo)注 main 是一個(gè) async的異步函數(shù)。
- 使用 HttpServer 可以聲明 HttpServer 對(duì)象,進(jìn)行IP地址和端口的綁定;
- 使用 App new 可以創(chuàng)建 app對(duì)象,可以定義 endpoint (路由)、注冊(cè)http 服務(wù)等
- 使用 #[get(“/”)]、#[post(“/echo”)] 宏可以標(biāo)注路由端點(diǎn)以及請(qǐng)求方法。
2.3、App對(duì)象
2.3.1、app對(duì)象介紹
Actix-Web 提供了使用 Rust 構(gòu)建 Web服務(wù)器和應(yīng)用程序的各種原語,提供了路由、中間件、請(qǐng)求預(yù)處理、響應(yīng)后處理等功能。
所有的 httpServer 都是圍繞 App 對(duì)象構(gòu)建的,它用于為資源和中間件注冊(cè)路由,他還存儲(chǔ)在同一范圍的所有處理程序之間共享的應(yīng)用程序狀態(tài)。
應(yīng)用的作用域是所有路由的命名空間,也就是說,特定應(yīng)用作用域的所有路由都有相同的url路徑前綴。應(yīng)用程序前綴總是包含一個(gè)前導(dǎo)“/”斜杠。如果提供的前綴不包含斜杠,則自動(dòng)插入。前綴應(yīng)該由值路徑段組成。
對(duì)于作用域?yàn)?app的應(yīng)用,任何帶有/app、/app/或/app/test路徑的請(qǐng)求都可以匹配;但是,路徑/應(yīng)用程序?qū)⒉黄ヅ洹?/p>
2.3.2、共享的不可變的狀態(tài)
應(yīng)用狀態(tài)被同一個(gè)作用域內(nèi)的所有路由和資源共享,狀態(tài)可以通過 web::Data<T>
來獲取訪問,其中 T 是狀態(tài)的類型,中間件也可以訪問狀態(tài) State。
我們來看下面這段代碼
use actix_web::{get, web, App, HttpServer};
// This struct represents state
struct AppState {
app_name: String,
}
#[get("/")]
async fn index(data: web::Data<AppState>) -> String {
let app_name = &data.app_name; // <- get app_name
format!("Hello {app_name}!") // <- response with app_name
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.app_data(web::Data::new(AppState {
app_name: String::from("Actix Web"),
}))
.service(index)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
上述代碼,我們?cè)赼pp上初始化了 app_name對(duì)象,我們get請(qǐng)求中可以直接獲取到 對(duì)象。
2.3.2、共享的可變狀態(tài)
HttpServer 可以接受應(yīng)用程序工廠而不是應(yīng)用程序?qū)嵗?,HttpServer 為每個(gè)線程構(gòu)造一個(gè)應(yīng)用程序?qū)嵗?。因此,必須多次?gòu)造應(yīng)用程序數(shù)據(jù)。如果你想在不同的線程之間共享數(shù)據(jù),應(yīng)該使用一個(gè)可共享的對(duì)象(Send、Sync)。
在內(nèi)部,Web::Data 使用了 Arc。為了避免創(chuàng)建兩個(gè) arc,我們應(yīng)該在使用 App::app_data() 注冊(cè)之前創(chuàng)建 Data。
use actix_web::{web, App, HttpServer};
use std::sync::Mutex;
struct AppStateWithCounter {
counter: Mutex<i32>, // <- Mutex is necessary to mutate safely across threads
}
async fn index(data: web::Data<AppStateWithCounter>) -> String {
let mut counter = data.counter.lock().unwrap(); // <- get counter's MutexGuard
*counter += 1; // <- access counter inside MutexGuard
format!("Request number: {counter}") // <- response with count
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// Note: web::Data created _outside_ HttpServer::new closure
let counter = web::Data::new(AppStateWithCounter {
counter: Mutex::new(0),
});
HttpServer::new(move || {
// move counter into the closure
App::new()
.app_data(counter.clone()) // <- register the created data
.route("/", web::get().to(index))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
- 在傳遞給 httpServer::new 的閉包中初始化的狀態(tài)對(duì)于工作線程來說是本地的,如果被修改,可能會(huì)變得不同步。
- 為了實(shí)現(xiàn)全局共享狀態(tài),必須在傳遞給 httpServer:: new 并移動(dòng)/克隆進(jìn)去的閉包之外創(chuàng)建
2.3.3、可以使用 scope 來組合 app
使用 web::scope() 方法可以為資源設(shè)置前綴,這個(gè)作用域標(biāo)識(shí)一個(gè)資源前綴,它被附加到路由資源配置添加到所有資源上。
2.3.4、應(yīng)用守衛(wèi)和虛擬主機(jī)
可以把 Guard 看作是一個(gè)簡(jiǎn)單的函數(shù),它接受請(qǐng)求對(duì)象(request)引用并返回 true 或 false。形式上, Guard 是 任何實(shí)現(xiàn)了 Guard trait 的對(duì)象。
我們可以為應(yīng)用守衛(wèi)設(shè)置虛擬主機(jī)
use actix_web::{web, App, HttpServer, guard, HttpResponse};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(
web::scope("/")
.guard(guard::Host("127.0.0.1"))
.route("", web::to(|| async { HttpResponse::Ok().body("www") })),
)
.service(
web::scope("/")
.guard(guard::Host("localhost"))
.route("", web::to(|| async { HttpResponse::Ok().body("user") })),
)
.route("/", web::to(HttpResponse::Ok))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
我們也可以基于 過濾器獲取請(qǐng)求頭信息。由于本機(jī)情況,我們沒有配置本地dns解析,就簡(jiǎn)單使用 localhost 和 127.0.01 執(zhí)行結(jié)果
2.3.5、配置
為了簡(jiǎn)潔和可重用,App 和 web::Scope 均提供了 configure 方法,此函數(shù)用于配置的部分移動(dòng)到不同的模塊設(shè)置庫中。例如:資源的某些配置可以移動(dòng)到其它模塊。
use actix_web::{web, App, HttpResponse, HttpServer};
// this function could be located in a different module
fn scoped_config(cfg: &mut web::ServiceConfig) {
cfg.service(
web::resource("/test")
.route(web::get().to(|| async { HttpResponse::Ok().body("test") }))
.route(web::head().to(HttpResponse::MethodNotAllowed)),
);
}
// this function could be located in a different module
fn config(cfg: &mut web::ServiceConfig) {
cfg.service(
web::resource("/app")
.route(web::get().to(|| async { HttpResponse::Ok().body("app") }))
.route(web::head().to(HttpResponse::MethodNotAllowed)),
);
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.configure(config)
.service(web::scope("/api").configure(scoped_config))
.route(
"/",
web::get().to(|| async { HttpResponse::Ok().body("/") }),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
上述代碼效果
/ -> "/"
/app -> "app"
/api/test -> "test"
每個(gè) ServiceConfig 都可以有自己的 data、routes、services。
2.4、HttpServer
2.4.1、HttpServer 介紹
HttpServer 類型負(fù)責(zé)提供 HTTP 請(qǐng)求。HttpServer 接受應(yīng)用程序工廠作為參數(shù),并且應(yīng)用程序工廠必須具有 Send + Sync 邊界。
要啟動(dòng)web 服務(wù),我們必須先綁定到一個(gè)網(wǎng)絡(luò)套接字,使用 HttpServer::bind 與套接字地址元組或字符串一起使用,例如 (“127.0.0.1”, 8080) 或 0.0.0.0:8080。
綁定成功之后,使用 run 方法回返回一個(gè) Server 實(shí)例。服務(wù)器必須等待或生成以開始處理請(qǐng)求,并且將運(yùn)行直到它接收到關(guān)閉信號(hào)(默認(rèn)情況下,可以使用 ctrl+c)。
2.4.2、多線程(Multi-Threading)
HttpServer 自動(dòng)開啟一個(gè) HTTP 工作線程,默認(rèn)情況下這個(gè)數(shù)量等于系統(tǒng)中物理 CPU的數(shù)量。
我們可以使用 HttpServer::workers 方法來進(jìn)行修改。
use actix_web::{web, App, HttpResponse, HttpServer};
#[actix_web::main]
async fn main() {
HttpServer::new(|| App::new().route("/", web::get().to(HttpResponse::Ok))).workers(4);
// <- Start 4 workers
}
一旦工作線程被創(chuàng)建,他們每個(gè)接收一個(gè)單獨(dú)的應(yīng)用程序?qū)嵗齺硖幚碚?qǐng)求。應(yīng)用程序狀態(tài)不會(huì)在線程之間共享,處理程序可以自由地操作他們的狀態(tài)副本,而沒有并發(fā)性問題。
應(yīng)用狀態(tài)不需要 Send 或 Sync,但是應(yīng)用工廠必須是 Send + Async。
要在工作線程之間共享狀態(tài),可以使用 Arc 或 Data。一旦引入共享和同步,就需要特別小心,在許多情況下,由于鎖定共享狀態(tài)以供修改,無意中引入了性能成本。
在某些情況,可以使用更有效的鎖策略來減輕這些成本,例如使用讀寫鎖而不是互斥鎖來實(shí)現(xiàn)非排他性鎖,但是性能最好的實(shí)現(xiàn)往往是不需要鎖。
由于每個(gè)工作線程線程順序處理請(qǐng)求,阻塞當(dāng)前線程的處理程序?qū)?dǎo)致當(dāng)前工作線程停止處理新請(qǐng)求。
由于這個(gè)原因,任何長(zhǎng)時(shí)間或非cpu限制的操作(I/O、數(shù)據(jù)庫操作)等都應(yīng)該表示為future 或異步函數(shù),異步處理程序由工作線程并發(fā)執(zhí)行,因此 不會(huì)阻塞執(zhí)行。
同樣的限制也適用于提取器,當(dāng)處理程序函數(shù)接收到一個(gè)實(shí)現(xiàn) FromRequest的參數(shù)并且該實(shí)現(xiàn)阻塞當(dāng)前線程,工作線程將在運(yùn)行處理程序時(shí)阻塞,由于這個(gè)原因,在實(shí)現(xiàn)提取器時(shí)必須特別注意,并且在需要時(shí)也應(yīng)該異步實(shí)現(xiàn)。
2.4.3、TLS/HTTPS
Actix Web 支持兩種 TLS實(shí)現(xiàn): rustls 和 openssl。
2.4.3.1、openssl
[dependencies]
actix-web = {version = "4.4.0", features = ["openssl"]}
openssl = "0.10.57"
use actix_web::{get, App, HttpRequest, HttpServer, Responder};
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
#[get("/")]
async fn index(_req: HttpRequest) -> impl Responder {
"Welcome!"
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
// load TLS keys
// to create a self-signed temporary cert for testing:
// `openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -days 365 -subj '/CN=localhost'`
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder
.set_private_key_file("key.pem", SslFiletype::PEM)
.unwrap();
builder.set_certificate_chain_file("cert.pem").unwrap();
HttpServer::new(|| App::new().service(index))
.bind_openssl("127.0.0.1:8080", builder)?
.run()
.await
}
注意,我們需要使用openssl 創(chuàng)建對(duì)象的文件。這里不做過多的講解。
2.4.4、Keep-Alive
Actix Web 支持保持連接打開以后等待后續(xù)請(qǐng)求。 保持連接是服務(wù)器定義的行為,所以要設(shè)置服務(wù)有三種方法
- 1、使用 keep_alive(Duration::from_secs(time)) 來設(shè)置開啟time 秒保持時(shí)間
- 2、使用 OS 保持:keep_alive(KeepAlive::Os);
- 3、使用 None 或 KeepAlive::Disabled,來關(guān)閉 keep-alive;
如果選擇了第一種,那么響應(yīng)沒有顯示地禁止它,例如,將連接設(shè)置為 Close 或 Upgrade,則對(duì)HTTP/1.1請(qǐng)求啟用keep-alive,強(qiáng)制關(guān)閉連接可以通過 HttpResponseBuilder 上的 force_close 方法完成。
Keep-Alive 在HTTP/1.1之后默認(rèn)是開啟的。
2.4.5、優(yōu)雅關(guān)機(jī)(Graceful Shutdown)
HttpServer 支持安全關(guān)閉,在接收到停止信號(hào),工作程序有一定的時(shí)間來完成服務(wù)器請(qǐng)求。超時(shí)后,仍然存活的工作線程將被強(qiáng)制丟棄,默認(rèn)情況下,關(guān)機(jī)超時(shí)時(shí)間設(shè)置為30秒,您可以使用 HttpServer:: shutdown_timeout() 方法來修改。
HttpServer 處理多個(gè)操作系統(tǒng)信號(hào),Ctrl+C 在所有操作系統(tǒng)上都可用。其它信號(hào)在 unix 系統(tǒng)上可用。
- SIGINT - Force shutdown workers
- SIGTERM - Graceful shutdown workers
- SIGQUIT - Force shutdown workers
您也可以使用 HttpServer::disable_signals() 來禁用信號(hào)處理。
2.5、提取
2.5.1、類型安全的信息提取
Actix Web 提供了一種用于類型安全請(qǐng)求信息訪問的工具叫做提取器(例如 實(shí)現(xiàn)了 FromRequest),內(nèi)置了很多提取器。
提取器可以作為處理程序的函數(shù)參數(shù)來使用,Actix Web支持每個(gè)處理器函數(shù)最多12個(gè)提取器。參數(shù)位置不固定。
例如
async fn index(path: web::Path<(String, String)>, json: web::Json<MyInfo>) -> impl Responder {
let path = path.into_inner();
format!("{} {} {} {}", path.0, path.1, json.id, json.username)
}
2.5.2、路徑參數(shù)(Path Parameters)
Path 提供了 路徑參數(shù)(Path Parameters)的方法,路徑中可提取的部分稱為動(dòng)態(tài)段,使用花括號(hào)標(biāo)記,您可以從路徑中反序列化任何可變段。
例如
use actix_web::{get, web, App, HttpServer, Result};
/// extract path info from "/users/{user_id}/{friend}" url
/// {user_id} - deserializes to a u32
/// {friend} - deserializes to a String
#[get("/users/{user_id}/{friend}")] // <- define path parameters
async fn index(path: web::Path<(u32, String)>) -> Result<String> {
let (user_id, friend) = path.into_inner();
Ok(format!("Welcome {}, user_id {}!", friend, user_id))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(index))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
通過動(dòng)態(tài)段名稱和字段名稱匹配之外,還可以序列化成對(duì)象。例如我們可以 Serde
use actix_web::{get, web, Result};
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
user_id: u32,
friend: String,
}
/// extract path info using serde
#[get("/users/{user_id}/{friend}")] // <- define path parameters
async fn index(info: web::Path<Info>) -> Result<String> {
Ok(format!(
"Welcome {}, user_id {}!",
info.friend, info.user_id
))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
use actix_web::{App, HttpServer};
HttpServer::new(|| App::new().service(index))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
還有一種非類型安全的的替代方法,我們可以使用 HttpRequest 的 match_info 方法。
#[get("/users/{user_id}/{friend}")] // <- define path parameters
async fn index(req: HttpRequest) -> Result<String> {
let name: String = req.match_info().get("friend").unwrap().parse().unwrap();
let userid: i32 = req.match_info().query("user_id").parse().unwrap();
Ok(format!("Welcome {}, user_id {}!", name, userid))
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
use actix_web::{App, HttpServer};
HttpServer::new(|| App::new().service(index))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
使用場(chǎng)景:路徑參數(shù)常用于標(biāo)識(shí)資源或指定資源的唯一標(biāo)識(shí)符,例如獲取用戶信息、獲取特定文章。
2.5.3、查詢參數(shù)(Query Parameters)
查詢參數(shù)是通過 URL 的查詢字符串部分來傳遞的,以?
開頭多個(gè)參數(shù)之間用&
分隔。使用場(chǎng)景參數(shù)常用于傳遞篩選、排序、分頁等額外的請(qǐng)求參數(shù),例如搜索用戶、排序商品列表等。
例子:
use actix_web::{get, web, App, HttpServer};
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
username: String,
}
// this handler gets called if the query deserializes into `Info` successfully
// otherwise a 400 Bad Request error response is returned
#[get("/")]
async fn index(info: web::Query<Info>) -> String {
format!("Welcome {}!", info.username)
}
該例子需要使用 serde_urlencoded 。
2.5.4、JSON參數(shù)
使用 JSON<T>
允許反序列化一個(gè)請(qǐng)求體到結(jié)構(gòu)體,要抽取的T 必須 實(shí)現(xiàn)反序列化。
例如
use actix_web::{post, web, App, HttpServer, Result};
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
username: String,
}
/// deserialize `Info` from request's body
#[post("/submit")]
async fn submit(info: web::Json<Info>) -> Result<String> {
Ok(format!("Welcome {}!", info.username))
}
一些提取器提供了一種配置提取過程的方法,要配置提取器,將其配置對(duì)象傳遞給資源的 app_data() 方法。在JSON 提取器的情況下,返回JsonConfig,配置JSON有效負(fù)載的最大大小以及自定義錯(cuò)誤處理函數(shù)。
use actix_web::{error, web, App, HttpResponse, HttpServer, Responder};
use serde::Deserialize;
#[derive(Deserialize)]
struct Info {
username: String,
}
/// deserialize `Info` from request's body, max payload size is 4kb
async fn index(info: web::Json<Info>) -> impl Responder {
format!("Welcome {}!", info.username)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
let json_config = web::JsonConfig::default()
.limit(4096)
.error_handler(|err, _req| {
// create custom error response
error::InternalError::from_response(err, HttpResponse::Conflict().finish())
.into()
});
App::new().service(
web::resource("/")
// change json extractor configuration
.app_data(json_config)
.route(web::post().to(index)),
)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2.5.5、URL編碼的表單數(shù)據(jù)
use actix_web::{post, web, App, HttpServer, Result};
use serde::Deserialize;
#[derive(Deserialize)]
struct FormData {
username: String,
}
/// extract form data using serde
/// this handler gets called only if the content type is *x-www-form-urlencoded*
/// and the content of the request could be deserialized to a `FormData` struct
#[post("/")]
async fn index(form: web::Form<FormData>) -> Result<String> {
Ok(format!("Welcome {}!", form.username))
}
2.5.6、其它提取器
Actix Web 還提供了其它的提取器
- Data:用于方法應(yīng)用程序狀態(tài)的提取器;
- HttpRequest:能夠訪問請(qǐng)求對(duì)象的抽取器;
- String:可以將有效的負(fù)載 payload 轉(zhuǎn)換為字符串;
- Bytes:可以將有效的負(fù)載 payload 轉(zhuǎn)換為Bytes;
- Payload:低級(jí)有效載荷提取器,主要用于構(gòu)建其他提取器;
2.5.7、應(yīng)用狀態(tài)提取
2.5.7.1、訪問狀態(tài)
應(yīng)用狀態(tài)可以通過 web:Data 提取器從處理程序中訪問,但是,state 可以作為只讀引用訪問,如果需要對(duì)狀態(tài)進(jìn)行可變?cè)L問,則必須實(shí)現(xiàn)它。
use actix_web::{web, App, HttpServer, Responder};
use std::cell::Cell;
#[derive(Clone)]
struct AppState {
count: Cell<usize>,
}
async fn show_count(data: web::Data<AppState>) -> impl Responder {
format!("count: {}", data.count.get())
}
async fn add_one(data: web::Data<AppState>) -> impl Responder {
let count = data.count.get();
data.count.set(count + 1);
format!("count: {}", data.count.get())
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let data = AppState {
count: Cell::new(0),
};
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(data.clone()))
.route("/", web::to(show_count))
.route("/add", web::to(add_one))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2.5.7.2、使用原子或ARC
如果要跨線程計(jì)算,我們需要使用共享的Arc和原子。
use actix_web::{get, web, App, HttpServer, Responder};
use std::{
cell::Cell,
sync::atomic::{AtomicUsize, Ordering},
sync::Arc,
};
#[derive(Clone)]
struct AppState {
local_count: Cell<usize>,
global_count: Arc<AtomicUsize>,
}
#[get("/")]
async fn show_count(data: web::Data<AppState>) -> impl Responder {
format!(
"global_count: {}\nlocal_count: {}",
data.global_count.load(Ordering::Relaxed),
data.local_count.get()
)
}
#[get("/add")]
async fn add_one(data: web::Data<AppState>) -> impl Responder {
data.global_count.fetch_add(1, Ordering::Relaxed);
let local_count = data.local_count.get();
data.local_count.set(local_count + 1);
format!(
"global_count: {}\nlocal_count: {}",
data.global_count.load(Ordering::Relaxed),
data.local_count.get()
)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let data = AppState {
local_count: Cell::new(0),
global_count: Arc::new(AtomicUsize::new(0)),
};
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(data.clone()))
.service(show_count)
.service(add_one)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2.6、處理器(Handlers)
2.6.1、請(qǐng)求處理器
請(qǐng)求處理程序是一個(gè)異步函數(shù),它可以接受從請(qǐng)求(實(shí)現(xiàn)了 FromRequest)中提取零個(gè)或多個(gè)參數(shù),并返回一個(gè)可以轉(zhuǎn)換為 HttpResponse 的類型。
請(qǐng)求處理分兩個(gè)階段進(jìn)行
- 1、處理程序?qū)ο螅祷貙?shí)現(xiàn)了 Responder 特征的任何對(duì)象;
- 2、在返回對(duì)象上調(diào)用 response_to 將自身轉(zhuǎn)換為 HttpResponse 或 Error;
默認(rèn)情況下,Actix Web 提供了一些標(biāo)準(zhǔn)類型的響應(yīng)器實(shí)現(xiàn),例如 &'static str、String 等
async fn index_01(_req: HttpRequest) -> &'static str {
"Hello world!"
}
async fn index_02(_req: HttpRequest) -> String {
"Hello world!".to_owned()
}
您也可以方法簽名,返回 impl Responder
async fn index_03(_req: HttpRequest) -> impl Responder {
web::Bytes::from_static(b"Hello world!")
}
async fn index_04(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
...
}
2.6.2、使用自定義類型的響應(yīng)
要從處理器函數(shù)直接返回自定義類型,該類型需要實(shí)現(xiàn) Responder trait。
例子
use actix_web::{
body::BoxBody, http::header::ContentType, HttpRequest, HttpResponse, Responder,
};
use serde::Serialize;
#[derive(Serialize)]
struct MyObj {
name: &'static str,
}
// Responder
impl Responder for MyObj {
type Body = BoxBody;
fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
let body = serde_json::to_string(&self).unwrap();
// Create response and set content type
HttpResponse::Ok()
.content_type(ContentType::json())
.body(body)
}
}
async fn index() -> impl Responder {
MyObj { name: "user" }
}
2.6.3、流響應(yīng)
響應(yīng)體可以異步生成,在這種情況下,響應(yīng)體必須實(shí)現(xiàn) Stream<Item=Result<Bytes,Error>>
use actix_web::{get, web, App, Error, HttpResponse, HttpServer};
use futures::{future::ok, stream::once};
#[get("/stream")]
async fn stream() -> HttpResponse {
let body = once(ok::<_, Error>(web::Bytes::from_static(b"test")));
HttpResponse::Ok()
.content_type("application/json")
.streaming(body)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(stream))
.bind(("127.0.0.1", 8080))?
.run()
.await
}
2.6.4、不同的返回類型(Either)
有些時(shí)候,你需要返回不同類型的響應(yīng),例如錯(cuò)誤校驗(yàn)返回錯(cuò)誤,成功就返回 Response,或別的響應(yīng)結(jié)果。在這種情況下,可以使用 Either 類型。文章來源:http://www.zghlxwxcb.cn/news/detail-766989.html
use actix_web::{Either, Error, HttpResponse};
type RegisterResult = Either<HttpResponse, Result<&'static str, Error>>;
async fn index() -> RegisterResult {
if is_a_variant() {
// choose Left variant
Either::Left(HttpResponse::BadRequest().body("Bad data"))
} else {
// choose Right variant
Either::Right(Ok("Hello!"))
}
}
總結(jié)
本節(jié)課講解了各Web開發(fā)框架的介紹,以及講解了 Actix-Web 基礎(chǔ)用法,后續(xù)將會(huì)講解如何結(jié)合數(shù)據(jù)庫進(jìn)行一個(gè)rbac權(quán)限系統(tǒng)的開發(fā)。文章來源地址http://www.zghlxwxcb.cn/news/detail-766989.html
到了這里,關(guān)于【跟小嘉學(xué) Rust 編程】三十三、Rust的Web開發(fā)框架之一: Actix-Web的基礎(chǔ)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!