太長(zhǎng),反著大家也不看,點(diǎn)個(gè)贊意思下就行 actix_web::middleware 在 Actix Web 框架中扮演著重要的角色,它允許開發(fā)者在處理 HTTP 請(qǐng)求和響應(yīng)的過程中插入自定義的邏輯。中間件可以在請(qǐng)求到達(dá)處理函數(shù)之前或響應(yīng)返回給客戶端之前執(zhí)行,從而實(shí)現(xiàn)日志記錄、身份驗(yàn)證、數(shù)據(jù)驗(yàn)證、錯(cuò)誤處理等功能。 為什么要有中間件?代碼在處理邏輯的時(shí)候,通常只處理正常的業(yè)務(wù)邏輯,而在處理過程中,可能會(huì)遇到一些特殊的情況,比如:404錯(cuò)誤,這種錯(cuò)誤會(huì)讓客戶端的請(qǐng)求無法進(jìn)入到代碼功能中,這時(shí)候就需要中間件來處理這些特殊的情況。 另外,還可以起到Java里面的過濾器功能,在Java里面,過濾器可以在請(qǐng)求到達(dá)處理函數(shù)之前或響應(yīng)返回給客戶端之前執(zhí)行,從而實(shí)現(xiàn)對(duì)請(qǐng)求和響應(yīng)的預(yù)處理和后處理。 在Rust開發(fā)中,特別是在使用Actix Web框架時(shí),中間件(middleware)是一種非常有用的設(shè)計(jì)模式,它允許開發(fā)者在處理HTTP請(qǐng)求和響應(yīng)的過程中插入自定義的邏輯。以下是使用中間件的幾個(gè)主要原因: 代碼模塊化和可重用性:中間件可以將通用的功能(如日志記錄、身份驗(yàn)證、數(shù)據(jù)驗(yàn)證等)封裝起來,使得這些功能可以被多個(gè)處理函數(shù)共享和重用。這樣可以減少代碼重復(fù),提高代碼的可讀性和可維護(hù)性。 靈活性和可擴(kuò)展性:通過使用中間件,開發(fā)者可以靈活地組合和配置不同的功能,以滿足應(yīng)用程序的特定需求。例如,可以根據(jù)不同的路由或用戶角色應(yīng)用不同的中間件。 請(qǐng)求和響應(yīng)的預(yù)處理和后處理:中間件可以在請(qǐng)求到達(dá)處理函數(shù)之前或響應(yīng)返回給客戶端之前執(zhí)行,從而實(shí)現(xiàn)對(duì)請(qǐng)求和響應(yīng)的預(yù)處理和后處理。例如,可以在請(qǐng)求到達(dá)之前記錄日志、驗(yàn)證用戶身份,或者在響應(yīng)返回之前壓縮數(shù)據(jù)、添加額外的頭部信息等。 提高開發(fā)效率:使用中間件可以減少開發(fā)者編寫重復(fù)代碼的工作量,使得開發(fā)者可以更專注于業(yè)務(wù)邏輯的實(shí)現(xiàn)。同時(shí),中間件的模塊化設(shè)計(jì)也使得代碼的測(cè)試和調(diào)試更加容易。 支持異步編程模型:Actix Web是一個(gè)基于異步IO的Web框架,中間件可以很好地適應(yīng)這種編程模型,允許開發(fā)者在不阻塞主線程的情況下執(zhí)行耗時(shí)的操作。
以下是一些常見的中間件及其作用:日志記錄:記錄每個(gè)請(qǐng)求的詳細(xì)信息,如請(qǐng)求方法、路徑、時(shí)間戳等,有助于調(diào)試和監(jiān)控。 身份驗(yàn)證:驗(yàn)證用戶的身份,確保只有經(jīng)過授權(quán)的用戶才能訪問特定的資源或執(zhí)行特定的操作。 數(shù)據(jù)驗(yàn)證:在請(qǐng)求到達(dá)處理函數(shù)之前,驗(yàn)證請(qǐng)求數(shù)據(jù)的格式和內(nèi)容是否符合預(yù)期,防止無效或惡意數(shù)據(jù)進(jìn)入系統(tǒng)。 錯(cuò)誤處理:統(tǒng)一處理應(yīng)用程序中的錯(cuò)誤,提供友好的錯(cuò)誤信息給客戶端,同時(shí)記錄錯(cuò)誤日志以便后續(xù)分析。 性能監(jiān)控:測(cè)量每個(gè)請(qǐng)求的處理時(shí)間,幫助開發(fā)者識(shí)別性能瓶頸并進(jìn)行優(yōu)化。 跨域資源共享(CORS):處理跨域請(qǐng)求,允許或拒絕來自不同域的請(qǐng)求,確保安全的跨域數(shù)據(jù)交互。 使用中間件可以使代碼更加模塊化和可重用,開發(fā)者可以根據(jù)需要組合和配置不同的中間件,以滿足應(yīng)用程序的特定需求。在 Actix Web 中,中間件通常通過 App::wrap 方法進(jìn)行注冊(cè)和應(yīng)用。
自定義中間件的過程:利用閉包實(shí)現(xiàn)簡(jiǎn)單的中間件例如我要定義一個(gè)預(yù)先獲取請(qǐng)求中header里面的token,如果沒有這個(gè)token則直接就返回錯(cuò)誤。代碼可以這樣寫: use actix_web::dev::Service; use actix_web::error; use actix_web::{middleware::{self, Logger}, web, App, HttpServer}; #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(move || { App::new() .wrap(middleware::Logger::default()) .wrap_fn(|req, srv| { let header = req.headers().to_owned(); let fut = srv.call(req); async move { let res = fut.await; match header.get("token"){ Some(token) => { println!("token:{}", token.to_str().unwrap()); }, None => { println!("token is None"); return Err(error::ErrorUnauthorized("Unauthorized")); }, } res } }) .route("/", web::get().to(|| async { "Hello, World!" })) }) .bind("127.0.0.1:8080")? .run() .await }
這里的核心,是使用wrap_fn + 一個(gè)閉包來實(shí)現(xiàn)中間件。 在示例代碼中,wrap_fn 被用來創(chuàng)建一個(gè)中間件,這個(gè)中間件檢查請(qǐng)求頭中是否包含 token。如果沒有 token,它會(huì)返回一個(gè)未經(jīng)授權(quán)的錯(cuò)誤。如果有 token,它會(huì)調(diào)用下一個(gè)服務(wù)并返回其結(jié)果。 warp_fn的源碼如下(看不懂也沒關(guān)系,不用懂,我寫這里是為了你以后能看懂時(shí)候回來查資料用的) pub fn wrap_fn<F, Fut>(f: F) -> impl Transform<ServiceRequest, Response = ServiceResponse<Fut::Item>, Error = Fut::Error, InitError = ()> where F: Fn(ServiceRequest, &mut dyn Service<ServiceRequest, Response = ServiceResponse<Fut::Item>, Error = Fut::Error, Future = Fut>) -> Fut, Fut: Future<Output = Result<ServiceResponse<Fut::Item>, Fut::Error>> + 'static,
里面參數(shù)解釋如下: 輸入?yún)?shù): F: 這是一個(gè)函數(shù)類型,它接受兩個(gè)參數(shù):ServiceRequest 和一個(gè)可變引用 &mut dyn Service<...>。這個(gè)函數(shù)將返回一個(gè) Fut 類型的 Future。 Fut: 這是一個(gè) Future 類型,它的輸出是 Result<ServiceResponseFut::Item, Fut::Error>。Fut::Item 是響應(yīng)體的類型,F(xiàn)ut::Error 是錯(cuò)誤類型。
返回值: impl Transform<ServiceRequest, Response = ServiceResponseFut::Item, Error = Fut::Error, InitError = ()>: 這是一個(gè)實(shí)現(xiàn)了 Transform trait 的類型,它可以轉(zhuǎn)換 ServiceRequest 到 ServiceResponseFut::Item,并且可以處理 Fut::Error 類型的錯(cuò)誤。
執(zhí)行結(jié)果如下: 如果是一些建議的邏輯,我們用wrap_fn + 閉包就可以了,但是如果是一些復(fù)雜的邏輯,就需要自己實(shí)現(xiàn)Transform trait了。 重新實(shí)現(xiàn)Transform trait 來實(shí)現(xiàn)自定義的中間件中間件初始化:在這個(gè)階段,中間件工廠函數(shù)被調(diào)用,它接收鏈中的下一個(gè)服務(wù)作為參數(shù)。這允許中間件在實(shí)際處理請(qǐng)求之前進(jìn)行任何必要的設(shè)置或配置。
//中間的工廠類 pub struct Auth;
impl<S, B> Transform<S, ServiceRequest> for Auth where S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>, S::Future: 'static, B: 'static, { type Response = ServiceResponse<B>; type Error = Error; type InitError = (); type Transform = AuthMiddleware<S>; type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future { ready(Ok(AuthMiddleware { service })) } }
編寫中間件的具體實(shí)現(xiàn),核心是中間件的 call 方法調(diào)用:一旦中間件初始化完成,每當(dāng)有新的請(qǐng)求到來時(shí),中間件的 call 方法就會(huì)被調(diào)用,并接收這個(gè)普通請(qǐng)求作為參數(shù)。此時(shí),中間件可以對(duì)請(qǐng)求進(jìn)行處理
// 中間件的具體實(shí)現(xiàn),里面需要接受工廠類里面過來的service pub struct AuthMiddleware<S> { service: S, }
//具體實(shí)現(xiàn) //核心是兩個(gè)方法: // call 具體實(shí)現(xiàn) // poll_ready impl<S, B> Service<ServiceRequest> for AuthMiddleware<S> where S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>, S::Future: 'static, B: 'static, { type Response = ServiceResponse<B>; type Error = Error; type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
// 實(shí)現(xiàn) poll_ready 方法,用于檢查服務(wù)是否準(zhǔn)備好處理請(qǐng)求 //這里用的是forward_ready!宏 forward_ready!(service);
// 實(shí)現(xiàn) call 方法,用于處理實(shí)際的請(qǐng)求 fn call(&self, req: ServiceRequest) -> Self::Future { // 進(jìn)行鑒權(quán)操作,判斷是否有權(quán)限 if has_permission(&req) { // 有權(quán)限,繼續(xù)執(zhí)行后續(xù)中間件 let fut = self.service.call(req); Box::pin(async move { let res = fut.await?; Ok(res) }) } else { // 沒有權(quán)限,立即返回響應(yīng) Box::pin(async move { // 鑒權(quán)失敗,返回未授權(quán)的響應(yīng),停止后續(xù)中間件的調(diào)用 Err(error::ErrorUnauthorized("Unauthorized")) }) } } }
fn has_permission(req: &ServiceRequest) -> bool { // 實(shí)現(xiàn)你的鑒權(quán)邏輯,根據(jù)需求判斷是否有權(quán)限 // 返回 true 表示有權(quán)限,返回 false 表示沒有權(quán)限 // unimplemented!() let value = HeaderValue::from_str("").unwrap(); match req.path().to_ascii_lowercase().as_str(){ "/login" => true, _ => { let token = req.headers().get("token").unwrap_or(&value); if token.len() <=0{ false }else{ println!("驗(yàn)證一下token,看看是否合法"); true } } } }
核心方法說明:poll_ready: 這個(gè)方法用于檢查服務(wù)是否準(zhǔn)備好處理請(qǐng)求。它返回一個(gè)Poll類型的結(jié)果,表示服務(wù)是否就緒。如果服務(wù)已經(jīng)就緒,返回Poll::Ready(Ok(()))。如果服務(wù)尚未就緒,返回Poll::Pending,表示需要等待一段時(shí)間后再次檢查。在middleware中,poll_ready方法通常用于確保在處理請(qǐng)求之前,所有依賴的資源或服務(wù)都已經(jīng)準(zhǔn)備就緒。 call: 這個(gè)方法用于處理實(shí)際的請(qǐng)求。它接收一個(gè)ServiceRequest類型的參數(shù),并返回一個(gè)ServiceResponse類型的結(jié)果。在middleware中,call方法通常用于對(duì)請(qǐng)求進(jìn)行預(yù)處理或后處理,例如添加日志記錄、驗(yàn)證請(qǐng)求、修改響應(yīng)等。call方法的返回值是一個(gè)Future,表示異步處理的結(jié)果。
所以也可以自己實(shí)現(xiàn)poll_ready fn poll_ready(&self, ctx: &mut core::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> { if self.service.poll_ready(ctx).is_pending() { // 如果服務(wù)尚未準(zhǔn)備好,返回Pending return std::task::Poll::Pending; }
// 如果服務(wù)已準(zhǔn)備好,返回Ready(Ok(())) std::task::Poll::Ready(Ok(())) }
使用中間件#[actix_web::main] async fn main() -> std::io::Result<()> { //中間件的順序是從下到上的,最后注冊(cè)的中間件會(huì)最先執(zhí)行 HttpServer::new(move || { App::new() .wrap(middleware::Logger::default()) .wrap(Auth::Auth) // 注冊(cè)其他路由和處理函數(shù) .route("/", web::get().to(|| async { "Hello, World!" })) .route("/login", web::get().to(|| async { "Hello, login" })) }) .bind("127.0.0.1:8080")? .run() .await }
執(zhí)行結(jié)果如下:
|