[Rust开发]actix_web::middleware 中心件

打印 上一主题 下一主题

主题 838|帖子 838|积分 2514

actix_web::middleware 在 Actix Web 框架中扮演偏重要的角色,它答应开发者在处理 HTTP 哀求和响应的过程中插入自定义的逻辑。中心件可以在哀求到达处理函数之前或响应返回给客户端之前实行,从而实现日志记录、身份验证、数据验证、错误处理等功能。
为什么要有中心件?

代码在处理逻辑的时候,通常只处理正常的业务逻辑,而在处理过程中,大概会遇到一些特殊的环境,好比:404错误,这种错误会让客户端的哀求无法进入到代码功能中,这时候就需要中心件来处理这些特殊的环境。
另外,还可以起到Java内里的过滤器功能,在Java内里,过滤器可以在哀求到达处理函数之前或响应返回给客户端之前实行,从而实现对哀求和响应的预处理和后处理。
在Rust开发中,特殊是在使用Actix Web框架时,中心件(middleware)是一种非常有用的计划模式,它答应开发者在处理HTTP哀求和响应的过程中插入自定义的逻辑。以下是使用中心件的几个主要原因:


  • 代码模块化和可重用性:中心件可以将通用的功能(如日志记录、身份验证、数据验证等)封装起来,使得这些功能可以被多个处理函数共享和重用。这样可以减少代码重复,提高代码的可读性和可维护性。
  • 机动性和可扩展性:通过使用中心件,开发者可以机动地组合和配置不同的功能,以满足应用步伐的特定需求。例如,可以根据不同的路由或用户角色应用不同的中心件。
  • 哀求和响应的预处理和后处理:中心件可以在哀求到达处理函数之前或响应返回给客户端之前实行,从而实现对哀求和响应的预处理和后处理。例如,可以在哀求到达之前记录日志、验证用户身份,大概在响应返回之前压缩数据、添加额外的头部信息等。
  • 提高开发效率:使用中心件可以减少开发者编写重复代码的工作量,使得开发者可以更专注于业务逻辑的实现。同时,中心件的模块化计划也使得代码的测试和调试更加容易。
  • 支持异步编程模子:Actix Web是一个基于异步IO的Web框架,中心件可以很好地适应这种编程模子,答应开发者在不阻塞主线程的环境下实行耗时的操作。
以下是一些常见的中心件及其作用:



  • 日志记录:记录每个哀求的详细信息,如哀求方法、路径、时间戳等,有助于调试和监控。
  • 身份验证:验证用户的身份,确保只有经过授权的用户才能访问特定的资源或实行特定的操作。
  • 数据验证:在哀求到达处理函数之前,验证哀求数据的格式和内容是否符合预期,防止无效或恶意数据进入系统。
  • 错误处理:统一处理应用步伐中的错误,提供友好的错误信息给客户端,同时记录错误日志以便后续分析。
  • 性能监控:丈量每个哀求的处理时间,帮助开发者辨认性能瓶颈并进行优化。
  • 跨域资源共享(CORS):处理跨域哀求,答应或拒绝来自不同域的哀求,确保安全的跨域数据交互。
  • 使用中心件可以使代码更加模块化和可重用,开发者可以根据需要组合和配置不同的中心件,以满足应用步伐的特定需求。在 Actix Web 中,中心件通常通过 App::wrap 方法进行注册和应用。
自定义中心件的过程:

使用闭包实现简朴的中心件

例如我要定义一个预先获取哀求中header内里的token,假如没有这个token则直接就返回错误。代码可以这样写:
  1. use actix_web::dev::Service;
  2. use actix_web::error;
  3. use actix_web::{middleware::{self, Logger}, web, App, HttpServer};
  4. #[actix_web::main]
  5. async fn main() -> std::io::Result<()> {
  6.     HttpServer::new(move || {
  7.         App::new()
  8.             .wrap(middleware::Logger::default())
  9.             .wrap_fn(|req, srv| {
  10.                 let header = req.headers().to_owned();
  11.                 let fut = srv.call(req);
  12.                 async move {
  13.                     let res = fut.await;
  14.                     match header.get("token"){
  15.                         Some(token) => {
  16.                             println!("token:{}", token.to_str().unwrap());
  17.                         },
  18.                         None => {
  19.                             println!("token is None");
  20.                             return Err(error::ErrorUnauthorized("Unauthorized"));
  21.                         },
  22.                     }
  23.                     res
  24.                 }
  25.             })
  26.             .route("/", web::get().to(|| async { "Hello, World!" }))
  27.     })
  28.     .bind("127.0.0.1:8080")?
  29.     .run()
  30.     .await
  31. }
复制代码
这里的核心,是使用wrap_fn + 一个闭包来实现中心件。
在示例代码中,wrap_fn 被用来创建一个中心件,这个中心件查抄哀求头中是否包含 token。假如没有 token,它会返回一个未经授权的错误。假如有 token,它会调用下一个服务并返回其结果。
warp_fn的源码如下(看不懂也不要紧,不消懂,我写这里是为了你以后能看懂时候返来查资料用的)
  1. pub fn wrap_fn<F, Fut>(f: F) -> impl Transform<ServiceRequest, Response = ServiceResponse<Fut::Item>, Error = Fut::Error, InitError = ()>
  2. where
  3.     F: Fn(ServiceRequest, &mut dyn Service<ServiceRequest, Response = ServiceResponse<Fut::Item>, Error = Fut::Error, Future = Fut>) -> Fut,
  4.     Fut: Future<Output = Result<ServiceResponse<Fut::Item>, Fut::Error>> + 'static,
复制代码
内里参数解释如下:


  • 输入参数:

    • F: 这是一个函数范例,它担当两个参数:ServiceRequest 和一个可变引用 &mut dyn Service<...>。这个函数将返回一个 Fut 范例的 Future。
    • Fut: 这是一个 Future 范例,它的输出是 Result<ServiceResponseFut::Item, Fut::Error>。Fut::Item 是响应体的范例,Fut::Error 是错误范例。

  • 返回值:

    • impl Transform<ServiceRequest, Response = ServiceResponseFut::Item, Error = Fut::Error, InitError = ()>: 这是一个实现了 Transform trait 的范例,它可以转换 ServiceRequest 到 ServiceResponseFut::Item,并且可以处理 Fut::Error 范例的错误。

实行结果如下:

假如是一些发起的逻辑,我们用wrap_fn + 闭包就可以了,但是假如是一些复杂的逻辑,就需要自己实现Transform trait了。
重新实现Transform trait 来实现自定义的中心件


  • 中心件初始化:在这个阶段,中心件工厂函数被调用,它接收链中的下一个服务作为参数。这答应中心件在现实处理哀求之前进行任何须要的设置或配置。


  • 中心件工厂类是 Transform trait的。

    • S - 后续服务的范例:S 代表链中下一个服务的范例,即当前中心件将哀求转达给哪个服务。
    • B - 响应体的范例:B 代表响应体(response)的范例,指定了从服务返回的响应内容的格式。

  1. //中间的工厂类
  2. pub struct Auth;
  3. impl<S, B> Transform<S, ServiceRequest> for Auth
  4. where
  5.     S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
  6.     S::Future: 'static,
  7.     B: 'static,
  8. {
  9.     type Response = ServiceResponse<B>;
  10.     type Error = Error;
  11.     type InitError = ();
  12.     type Transform = AuthMiddleware<S>;
  13.     type Future = Ready<Result<Self::Transform, Self::InitError>>;
  14.     fn new_transform(&self, service: S) -> Self::Future {
  15.         ready(Ok(AuthMiddleware { service }))
  16.     }
  17. }
复制代码

  • 编写中心件的具体实现,核心是中心件的 call 方法调用:一旦中心件初始化完成,每当有新的哀求到来时,中心件的 call 方法就会被调用,并接收这个普通哀求作为参数。此时,中心件可以对哀求进行处理
  1. // 中间件的具体实现,里面需要接受工厂类里面过来的service
  2. pub struct AuthMiddleware<S> {
  3.     service: S,
  4. }
  5. //具体实现
  6. //核心是两个方法:
  7. // call 具体实现
  8. // poll_ready
  9. impl<S, B> Service<ServiceRequest> for AuthMiddleware<S>
  10. where
  11.     S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
  12.     S::Future: 'static,
  13.     B: 'static,
  14. {
  15.     type Response = ServiceResponse<B>;
  16.     type Error = Error;
  17.     type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
  18.     // 实现 poll_ready 方法,用于检查服务是否准备好处理请求
  19.     //这里用的是forward_ready!宏
  20.     forward_ready!(service);
  21.     // 实现 call 方法,用于处理实际的请求
  22.     fn call(&self, req: ServiceRequest) -> Self::Future {
  23.         // 进行鉴权操作,判断是否有权限
  24.         if has_permission(&req) {
  25.             // 有权限,继续执行后续中间件
  26.             let fut = self.service.call(req);
  27.             Box::pin(async move {
  28.                 let res = fut.await?;
  29.                 Ok(res)
  30.             })
  31.         } else {
  32.             // 没有权限,立即返回响应
  33.             Box::pin(async move {
  34.                 // 鉴权失败,返回未授权的响应,停止后续中间件的调用
  35.                 Err(error::ErrorUnauthorized("Unauthorized"))
  36.             })
  37.         }
  38.     }
  39. }
  40. fn has_permission(req: &ServiceRequest) -> bool {
  41.     // 实现你的鉴权逻辑,根据需求判断是否有权限
  42.     // 返回 true 表示有权限,返回 false 表示没有权限
  43.     // unimplemented!()
  44.     let value = HeaderValue::from_str("").unwrap();
  45.     match req.path().to_ascii_lowercase().as_str(){
  46.         "/login" => true,
  47.         _ => {
  48.             let token = req.headers().get("token").unwrap_or(&value);
  49.             if token.len() <=0{
  50.                 false
  51.             }else{
  52.                 println!("验证一下token,看看是否合法");
  53.                 true
  54.             }
  55.         }
  56.     }
  57. }
复制代码
核心方法说明:



  • poll_ready: 这个方法用于查抄服务是否准备利益理哀求。 它返回一个Poll范例的结果,表示服务是否停当。 假如服务已经停当,返回Poll::Ready(Ok(()))。 假如服务尚未停当,返回Poll:ending,表示需要等待一段时间后再次查抄。 在middleware中,poll_ready方法通常用于确保在处理哀求之前,所有依赖的资源或服务都已经准备停当。
  • call: 这个方法用于处理现实的哀求。 它接收一个ServiceRequest范例的参数,并返回一个ServiceResponse范例的结果。 在middleware中,call方法通常用于对哀求进行预处理或后处理,例如添加日志记录、验证哀求、修改响应等。 call方法的返回值是一个Future,表示异步处理的结果。
所以也可以自己实现poll_ready
  1. fn poll_ready(&self, ctx: &mut core::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
  2.         if self.service.poll_ready(ctx).is_pending() {
  3.             // 如果服务尚未准备好,返回Pending
  4.             return std::task::Poll::Pending;
  5.         }
  6.         // 如果服务已准备好,返回Ready(Ok(()))
  7.         std::task::Poll::Ready(Ok(()))
  8. }
复制代码
使用中心件

  1. #[actix_web::main]
  2. async fn main() -> std::io::Result<()> {
  3.     //中间件的顺序是从下到上的,最后注册的中间件会最先执行
  4.     HttpServer::new(move || {
  5.         App::new()
  6.             .wrap(middleware::Logger::default())
  7.             .wrap(Auth::Auth)
  8.             // 注册其他路由和处理函数
  9.             .route("/", web::get().to(|| async { "Hello, World!" }))
  10.             .route("/login", web::get().to(|| async { "Hello, login" }))
  11.     })
  12.     .bind("127.0.0.1:8080")?
  13.     .run()
  14.     .await
  15. }
复制代码
实行结果如下:


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

络腮胡菲菲

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表