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

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则直接就返回错误。代码可以这样写:

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 + 一个闭包来实现中间件。

在示例代码中,wrap_fn 被用来创建一个中间件,这个中间件检查请求头中是否包含 token。如果没有 token,它会返回一个未经授权的错误。如果有 token,它会调用下一个服务并返回其结果。

warp_fn的源码如下(看不懂也没关系,不用懂,我写这里是为了你以后能看懂时候回来查资料用的)

pub fn wrap_fn<F, Fut>(f: F) -> impl Transform<ServiceRequest, Response = ServiceResponse<Fut::Item>, Error = Fut::Error, InitError = ()>
whereF: 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,

里面参数解释如下:

  • 输入参数:

    • 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 来实现自定义的中间件

  1. 中间件初始化:在这个阶段,中间件工厂函数被调用,它接收链中的下一个服务作为参数。这允许中间件在实际处理请求之前进行任何必要的设置或配置。
  • 中间件工厂类是 Transform trait的。
    • S - 后续服务的类型:S 代表链中下一个服务的类型,即当前中间件将请求传递给哪个服务。
    • B - 响应体的类型:B 代表响应体(response)的类型,指定了从服务返回的响应内容的格式。
//中间的工厂类
pub struct Auth;impl<S, B> Transform<S, ServiceRequest> for Auth
whereS: 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 }))}
}
  1. 编写中间件的具体实现,核心是中间件的 call 方法调用:一旦中间件初始化完成,每当有新的请求到来时,中间件的 call 方法就会被调用,并接收这个普通请求作为参数。此时,中间件可以对请求进行处理

// 中间件的具体实现,里面需要接受工厂类里面过来的service
pub struct AuthMiddleware<S> {service: S,
}//具体实现
//核心是两个方法:
// call 具体实现
// poll_ready
impl<S, B> Service<ServiceRequest> for AuthMiddleware<S>
whereS: 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>>;// 实现 poll_ready 方法,用于检查服务是否准备好处理请求 //这里用的是forward_ready!宏forward_ready!(service);// 实现 call 方法,用于处理实际的请求fn call(&self, req: ServiceRequest) -> Self::Future {// 进行鉴权操作,判断是否有权限if has_permission(&req) {// 有权限,继续执行后续中间件let fut = self.service.call(req);Box::pin(async move {let res = fut.await?;Ok(res)})} else {// 没有权限,立即返回响应Box::pin(async move {// 鉴权失败,返回未授权的响应,停止后续中间件的调用Err(error::ErrorUnauthorized("Unauthorized"))})}}
}fn has_permission(req: &ServiceRequest) -> bool {// 实现你的鉴权逻辑,根据需求判断是否有权限// 返回 true 表示有权限,返回 false 表示没有权限// 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!("验证一下token,看看是否合法");true}}}
}

核心方法说明:

  • poll_ready: 这个方法用于检查服务是否准备好处理请求。 它返回一个Poll类型的结果,表示服务是否就绪。 如果服务已经就绪,返回Poll::Ready(Ok(()))。 如果服务尚未就绪,返回Poll::Pending,表示需要等待一段时间后再次检查。 在middleware中,poll_ready方法通常用于确保在处理请求之前,所有依赖的资源或服务都已经准备就绪。

  • call: 这个方法用于处理实际的请求。 它接收一个ServiceRequest类型的参数,并返回一个ServiceResponse类型的结果。 在middleware中,call方法通常用于对请求进行预处理或后处理,例如添加日志记录、验证请求、修改响应等。 call方法的返回值是一个Future,表示异步处理的结果。

所以也可以自己实现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() {// 如果服务尚未准备好,返回Pendingreturn std::task::Poll::Pending;}// 如果服务已准备好,返回Ready(Ok(()))std::task::Poll::Ready(Ok(()))
}

使用中间件

#[actix_web::main]
async fn main() -> std::io::Result<()> {//中间件的顺序是从下到上的,最后注册的中间件会最先执行HttpServer::new(move || {App::new().wrap(middleware::Logger::default()).wrap(Auth::Auth)// 注册其他路由和处理函数.route("/", web::get().to(|| async { "Hello, World!" })).route("/login", web::get().to(|| async { "Hello, login" }))}).bind("127.0.0.1:8080")?.run().await
}

执行结果如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/63854.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

opencv——图片矫正

图像矫正 图像矫正的原理是透视变换&#xff0c;下面来介绍一下透视变换的概念。 听名字有点熟&#xff0c;我们在图像旋转里接触过仿射变换&#xff0c;知道仿射变换是把一个二维坐标系转换到另一个二维坐标系的过程&#xff0c;转换过程坐标点的相对位置和属性不发生变换&a…

记录:ubuntu24.04源码安装nginx

一. 下载Nginx源码 两个地址二选一即可 Nginx官网Nginx官网 Github eg&#xff1a;nginx-1.27.3.tar.gz 下载到 ubuntu24.04 的 Downloads &#xff0c;解压 cd Downloads tar -zxvf nginx-1.27.3.tar.gz二. 编译安装 Note: 编译最好用 root 权限&#xff0c; 使用下面命令…

CNCF云原生生态版图

CNCF云原生生态版图 概述什么是云原生生态版图如何使用生态版图 项目和产品&#xff08;Projects and products&#xff09;会员&#xff08;Members&#xff09;认证合作伙伴与提供商&#xff08;Certified partners and providers&#xff09;无服务&#xff08;Serverless&a…

wsl2子系统ubuntu发行版位置迁移步骤

默认的wsl2发行版是安装在windos的c盘&#xff0c;占用空间较大&#xff0c;有迁移需求&#xff0c;也可以迁移到其他电脑&#xff1b; 查看现有发行版信息 运行以下命令查看现有的 WSL 发行版及其状态&#xff1a; wsl --list --verbose# 输出示例NAME STATE …

SpringBoot基于Redis+WebSocket 实现账号单设备登录.

引言 在现代应用中&#xff0c;一个账号在多个设备上的同时登录可能带来安全隐患。为了解决这个问题&#xff0c;许多应用实现了单设备登录&#xff0c;确保同一个用户只能在一个设备上登录。当用户在新的设备上登录时&#xff0c;旧设备会被强制下线。 本文将介绍如何使用 Spr…

MVC配置文件及位置

配置文件位置 默认位置 WEB-INF目录下&#xff0c;文件名&#xff1a;<servlet-name>-servlet.xml <?xml version"1.0" encoding"UTF-8"?> <web-app xmlns"http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi"http://www.w3.…

【若依项目-RuoYi】掌握若依前端的基本流程

搞毕设项目&#xff0c;使用前后端分离技术&#xff0c;后端springBoot&#xff0c;前端vue3element plus。自己已经写好前端与后端代码&#xff0c;但想换一个前端界面所以使用到了若依&#xff0c;前前后后遇到许多坑&#xff0c;记录一下&#xff0c;方便之后能够快速回忆。…

图像边缘检测示例(综合利用阈值分割、数学形态学和边缘检测算子)

一、问题 读入一副灰度图像&#xff08;如果是彩色图像&#xff0c;可以先将其转化为灰度图像&#xff09;&#xff0c;然后提取比较理想的灰度图像边缘。这里以moon.tif为例。 二、算法 大家一开始容易想到直接利用MATLAB的内置函数edge并采用不同边缘提取算子进行边缘提取&a…

R语言的数据结构-向量

【图书推荐】《R语言医学数据分析实践》-CSDN博客 《R语言医学数据分析实践 李丹 宋立桓 蔡伟祺 清华大学出版社9787302673484》【摘要 书评 试读】- 京东图书 (jd.com) R语言编程_夏天又到了的博客-CSDN博客 在R语言中&#xff0c;数据结构是非常关键的部分&#xff0c;它提…

集成方案 | Docusign + 泛微,实现全流程电子化签署!

本文将详细介绍 Docusign 与泛微的集成步骤及其效果&#xff0c;并通过实际应用场景来展示 Docusign 的强大集成能力&#xff0c;以证明 Docusign 集成功能的高效性和实用性。 在现代企业运营中&#xff0c;效率和合规性是至关重要的。泛微作为企业级办公自动化和流程管理的解决…

Docker Compose应用实战

文章目录 1、使用Docker Compose必要性及定义2、Docker Compose应用参考资料3、Docker Compose应用最佳实践步骤1_概念2_步骤 4、Docker Compose安装5、Docker Compose应用案例1_网站文件准备2_Dockerfile文件准备3_Compose文件准备4_使用docker-compose up启动容器5_访问6_常见…

51c大模型~合集88

我自己的原文哦~ https://blog.51cto.com/whaosoft/12805165 #Number Cookbook 数字比你想得更复杂——一文带你了解大模型数字处理能力的方方面面 目前大语言模型&#xff08;Large Language Models, LLMs&#xff09;的推理能力备受关注。从思维链&#xff08;Chain of…

STP(生成树协议)

STP的基本概念 概述 STP是一个用于局域网中消除环路的协议。运行该协议的设备通过彼此交互信息而发现网络中的环路&#xff0c;并对某些接口进行阻塞以消除环路。STP在网络中运行后会持续监控网络的状态&#xff0c;当网络出现拓扑变更时&#xff0c;STP能够感知并且进行自动…

GLM-4-Plus初体验

引言&#xff1a;为什么高效的内容创作如此重要&#xff1f; 在当前竞争激烈的市场环境中&#xff0c;内容创作已成为品牌成功的重要支柱。无论是撰写营销文案、博客文章、社交媒体帖子&#xff0c;还是制作广告&#xff0c;优质的内容不仅能够帮助品牌吸引目标受众的注意力&a…

Jetpack Compose赋能:以速破局,高效打造非凡应用

Android Compose 是谷歌推出的一种现代化 UI 框架&#xff0c;基于 Kotlin 编程语言&#xff0c;旨在简化和加速 Android 应用开发。它以声明式编程为核心&#xff0c;与传统的 View 系统相比&#xff0c;Compose 提供了更直观、更简洁的开发体验。以下是对 Android Compose 的…

MinerU:PDF文档提取工具

目录 docker一键启动本地配置下载模型权重文件demo.py使用命令行启动GPU使用情况 wget https://github.com/opendatalab/MinerU/raw/master/Dockerfile docker build -t mineru:latest .docker一键启动 有点问题&#xff0c;晚点更新 本地配置 就是在Python环境中配置依赖和…

UE4_控件蓝图_制作3D生命血条

一&#xff1a;效果图如下&#xff1a; 二、实现步骤&#xff1a; 1、新建敌人 右键蓝图类 选择角色&#xff0c; 重命名为BP_Enemytest。 双击打开&#xff0c;配置敌人网格体 修改位置及朝向 效果如下&#xff1a; 选择合适的动画蓝图类&#xff1a; 人物就有了动作&#x…

【深度学习】深刻理解ViT

ViT&#xff08;Vision Transformer&#xff09;是谷歌研究团队于2020年提出的一种新型图像识别模型&#xff0c;首次将Transformer架构成功应用于计算机视觉任务中。Transformer最初应用于自然语言处理&#xff08;如BERT和GPT&#xff09;&#xff0c;而ViT展示了其在视觉任务…

用于日语词汇学习的微信小程序+ssm

日语词汇学习小程序是高校人才培养计划的重要组成部分&#xff0c;是实现人才培养目标、培养学生科研能力与创新思维、检验学生综合素质与实践能力的重要手段与综合性实践教学环节。本学生所在学院多采用半手工管理日语词汇学习小程序的方式&#xff0c;所以有必要开发日语词汇…

ichunqiu-2024年春秋杯网络安全联赛夏季赛-brother

1.打开题目&#xff0c;看到题目我就想到了再后面加一个ls&#xff0c;结果回显了ls&#xff0c;然后又想到会不会是模板注入&#xff0c;尝试{{7*7}}&#xff0c;然后页面返回了49&#xff0c;说明存在模板注入 如下&#xff0c;判定为模板注入 看一下系统环境配置 然后看可…