rust actix-web定义中间件(middleware)记录接口耗时(接口耗时中间件和鉴权中间件)

文章目录

  • Actix-web定义中间件(middleware)记录接口耗时
    • 中间件简介
    • 中间件添加的两种方式(接口耗时中间件)
      • 使用wrap_fn + 闭包实现
      • 使用warp + struct实现
      • 中间件调用顺序
      • actix自带的接口耗时中间件
    • 鉴权中间件

Actix-web定义中间件(middleware)记录接口耗时

actix-web的官网关于中间件的介绍如下

  • https://actix.rs/docs/middleware/

这里使用的是最新版的actix-web,旧版本的可能接口不太一样

# actix-web
actix-rt = "2.6.0"
actix-web = "4.0.0"

中间件简介

我们添加的中间件能干什么?我们用一段代码来观察一下

下面是官方提供的中间件的定义方式之一,我们可以看到闭包里面有两个参数reqsrv

  • 其中req(actix_web::dev::ServiceRequest)就是请求了,你可以通过它来获取请求的各种属性,比如请求的路径 req.path()
  • srv 是一个 &mut dyn actix_web::dev::Service 类型的参数,代表服务处理程序。通过调用 srv.call(req),我们将请求传递给实际的服务处理程序进行处理。这里其实使用的是装饰器模式,这使得我们能在具体service方法调用前后做一些操作。官网上写到可以添加这些操作
    • Pre-process the Request:在请求时做前置处理
    • Post-process a Response:在响应时进行后置处理
    • Modify application state:修改state。state是我actix-web在整个调用链路中的上下文,可以用来存储我们自己想要保存的数据
    • Access external services (redis, logging, sessions):可以访问外部的服务,例如redis等等
use actix_web::{dev::Service as _, web, App};
use futures_util::future::FutureExt;#[actix_web::main]
async fn main() {let app = App::new().wrap_fn(|req, srv| {// Pre-process the Requestprintln!("Hi from start. You requested: {}", req.path());srv.call(req).map(|res| {// Post-process a Responseprintln!("Hi from response");res})}).route("/index.html",web::get().to(|| async { "Hello, middleware!" }),);
}

中间件添加的两种方式(接口耗时中间件)

上述添加中间件的方式其实是通过wrap_fn来实现的,我们可以通过传入闭包的方式完成我们中间件的逻辑

但是一般我们中间件处理的逻辑可能很多,在闭包中修改会显得很冗余,我们还可以通过方法warp传入一个实现了 Service trait and Transform traitstruct,这样就会调用我们实现好的方法

在实现前我们需要先添加一些依赖

# actix-web
actix-rt = "2.6.0"
actix-web = "4.0.0"
# 提供对异步编程的支持和工具
futures-util = "0.3"

使用wrap_fn + 闭包实现

use actix_web::{dev::Service as _, web, App};
use futures_util::future::FutureExt;
use std::time::{Duration, Instant};#[actix_web::main]
async fn main() {let app = App::new().wrap_fn(|req, srv| {let start_time = Instant::now();let path = req.path().to_owned();let fut = srv.call(req);async move {let res = fut.await;let elapsed_time = start_time.elapsed();println!("Request to {} took {:?}",path,elapsed_time);res}}).route("/index.html",web::get().to(|| async { "Hello, middleware!" }),);
}

这样就能打印接口耗时的日志了

Request to /index.html took 381.325909ms

使用warp + struct实现

使用struct需要实现两个traitTransformService

// 中间件 => 打印接口耗时use std::{future::{ready, Ready}, time::Instant};use actix_web::{dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},Error,
};
use futures_util::future::LocalBoxFuture;// There are two steps in middleware processing.
// 1. Middleware initialization, middleware factory gets called with
//    next service in chain as parameter.
// 2. Middleware's call method gets called with normal request.
pub struct Timed;// Middleware factory is `Transform` trait
// `S` - type of the next service
// `B` - type of response's body
impl<S, B> Transform<S, ServiceRequest> for Timed
whereS: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,S::Future: 'static,B: 'static,
{type Response = ServiceResponse<B>;type Error = Error;type InitError = ();type Transform = TimedMiddleware<S>;type Future = Ready<Result<Self::Transform, Self::InitError>>;fn new_transform(&self, service: S) -> Self::Future {ready(Ok(TimedMiddleware { service }))}
}pub struct TimedMiddleware<S> {service: S,
}impl<S, B> Service<ServiceRequest> for TimedMiddleware<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>>;forward_ready!(service);fn call(&self, req: ServiceRequest) -> Self::Future {let start_time = Instant::now();let path = req.path().to_owned();let method = req.method().to_string();let remote_addr = req.connection_info().peer_addr().unwrap_or("unknown").to_string();let version = format!("{:?}", req.version()); // 使用 format! 宏转换版本号为字符串let headers = req.headers().clone();println!("{}", "1. Pre-process the Request");let fut = self.service.call(req);Box::pin(async move {let res = fut.await?;let elapsed = start_time.elapsed();let status = res.status();let content_length = res.headers().get(actix_web::http::header::CONTENT_LENGTH).and_then(|v| v.to_str().ok()).unwrap_or("-");let user_agent = headers.get(actix_web::http::header::USER_AGENT).and_then(|v| v.to_str().ok()).unwrap_or("-");println!("{}", "2. Post-process a Response")println!("{} {} {} {} {} {} {}  time took [{:.6}] ms",remote_addr,method,path,version,status.as_u16(),content_length,user_agent,elapsed.as_millis());Ok(res)})}
}

在主程序中添加

use actix_web::{dev::Service as _, web, App};
use futures_util::future::FutureExt;
use std::time::{Duration, Instant};#[actix_web::main]
async fn main() {let app = App::new().wrap_fn(|req, srv| {println!("{}", "2. Pre-process the Request")let start_time = Instant::now();let path = req.path().to_owned();let fut = srv.call(req);async move {let res = fut.await;let elapsed_time = start_time.elapsed();println!("{}", "3. Post-process a Response")println!("Request to {} took {:?}",path,elapsed_time);res}}).wrap(Timed).route("/index.html",web::get().to(|| async { "Hello, middleware!" }),);
}

打印情况

1. Pre-process the Request
2. Pre-process the Request
3. Post-process a Response
Request to /index.html took 70.694224ms
4. Post-process a Response
127.0.0.1 GET /index.html HTTP/1.1 200 - Apifox/1.0.0 (https://www.apifox.cn)  time took [70] ms

中间件调用顺序

如果我们有多个中间件,调用顺序可以从官方上看到这样一句话

Warning: if you use wrap() or wrap_fn() multiple times, the last occurrence will be executed first.

也就是后面添加的中间件会先执行

笔者根据上面添加的中间件,对于前置和后置处理我们可以总结出调用顺序

  • 前置比后置处理先调用
  • 前置处理是后添加的先执行
  • 后置处理按照中间件添加的属性进行执行

actix自带的接口耗时中间件

其实在actix中自带了接口耗时的记录,我们只需要指定日志,并启用就可以看到了

# 日志相关
log = "0.4.0"
env_logger = "0.10.0"
use actix_web::{dev::Service as _, web, App, middleware};
use futures_util::future::FutureExt;
use std::time::{Duration, Instant};
use log::info;#[actix_web::main]
async fn main() {// 初始化日志init_logger();let app = App::new()// 日志中间件.wrap(middleware::Logger::default()).wrap_fn(|req, srv| {println!("{}", "2. Pre-process the Request")let start_time = Instant::now();let path = req.path().to_owned();let fut = srv.call(req);async move {let res = fut.await;let elapsed_time = start_time.elapsed();println!("{}", "3. Post-process a Response")println!("Request to {} took {:?}",path,elapsed_time);res}}).wrap(Timed).route("/index.html",web::get().to(|| async { "Hello, middleware!" }),);
}fn init_logger() {use env_logger::fmt::Color;use env_logger::Env;use log::LevelFilter;let env = Env::default().filter_or("MY_LOG_LEVEL", "debug");// 设置日志打印格式env_logger::Builder::from_env(env).format(|buf, record| {let level_color = match record.level() {log::Level::Error => Color::Red,log::Level::Warn => Color::Yellow,log::Level::Info => Color::Green,log::Level::Debug | log::Level::Trace => Color::Cyan,};let mut level_style = buf.style();level_style.set_color(level_color).set_bold(true);let mut style = buf.style();style.set_color(Color::White).set_dimmed(true);writeln!(buf,"{} {} [{}] {}",Local::now().format("%Y-%m-%d %H:%M:%S"),level_style.value(record.level()),style.value(record.module_path().unwrap_or("<unnamed>")),record.args())}).filter(None, LevelFilter::Debug).init();info!("env_logger initialized.");
}

日志打印

2023-08-24 16:06:14 INFO [teacher_service] env_logger initialized.
2023-08-24 16:06:14 INFO [actix_server::builder] starting 2 workers
2023-08-24 16:06:14 INFO [actix_server::server] Actix runtime found; starting in Actix runtime
1. Pre-process the Request
2. Pre-process the Request
2023-08-24 16:06:50 INFO [teacher_service::my_middleware::auth] 2. Hi from start. You requested: /teacher
2023-08-24 16:06:50 INFO [teacher_service] 1. Hi from start. You requested: /teacher
2023-08-24 16:06:50 INFO [teacher_service] 2. Hi from response
2023-08-24 16:06:50 INFO [teacher_service::my_middleware::auth] 1. Hi from response
3. Post-process a Response
Request to /teacher took 355.839222ms
4. Post-process a Response
2023-08-24 16:06:50 INFO [teacher_service::my_middleware::timedMiddleware] 127.0.0.1 GET /teacher HTTP/1.1 200 - Apifox/1.0.0 (https://www.apifox.cn)  time took [355] ms
2023-08-24 16:06:50 INFO [actix_web::middleware::logger] 127.0.0.1 "GET /teacher HTTP/1.1" 200 191 "-" "Apifox/1.0.0 (https://www.apifox.cn)" 0.355607

最后一行就是actix日志记录请求的调用情况,最后一个参数就是调用时间,单位是秒

鉴权中间件

我们用相同的思路,写一个鉴权的中间件,这里具体的校验规则读者可以实现一下

use std::future::{ready, Ready};use actix_web::{dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},error,http::header::HeaderValue,middleware::ErrorHandlerResponse,Error, HttpResponse,
};
use futures_util::{future::{self, LocalBoxFuture},FutureExt,
};
use log::info;// There are two steps in middleware processing.
// 1. Middleware initialization, middleware factory gets called with
//    next service in chain as parameter.
// 2. Middleware's call method gets called with normal request.
pub struct Auth;// Middleware factory is `Transform` trait
// `S` - type of the next service
// `B` - type of response's body
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 }))}
}pub struct AuthMiddleware<S> {service: S,
}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>>;forward_ready!(service);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();let token = req.headers().get("token").unwrap_or(&value);token.len() > 0 || req.path().to_string() == "/login"
}

接下来我们在启动的App上加上中间件,这里我们要注意⚠️,如果我们有很多中间件,我们肯定是想要我们的鉴权中间件先执行的,这样如果鉴权没有过,就不执行后面中间件的逻辑

根据官方的提示:后添加的中间件会先执行。我们应该把鉴权中间件放到最后面的位置

use actix_web::{dev::Service as _, web, App, middleware};
use futures_util::future::FutureExt;
use std::time::{Duration, Instant};
use log::info;#[actix_web::main]
async fn main() {// 初始化日志init_logger();let app = App::new()// 日志中间件.wrap(middleware::Logger::default()).wrap_fn(|req, srv| {println!("{}", "2. Pre-process the Request")let start_time = Instant::now();let path = req.path().to_owned();let fut = srv.call(req);async move {let res = fut.await;let elapsed_time = start_time.elapsed();println!("{}", "3. Post-process a Response")println!("Request to {} took {:?}",path,elapsed_time);res}}).wrap(Timed)// 添加自己中间件的路径.wrap(my_middleware::auth::Auth).route("/index.html",web::get().to(|| async { "Hello, middleware!" }),);
}fn init_logger() {use env_logger::fmt::Color;use env_logger::Env;use log::LevelFilter;let env = Env::default().filter_or("MY_LOG_LEVEL", "debug");// 设置日志打印格式env_logger::Builder::from_env(env).format(|buf, record| {let level_color = match record.level() {log::Level::Error => Color::Red,log::Level::Warn => Color::Yellow,log::Level::Info => Color::Green,log::Level::Debug | log::Level::Trace => Color::Cyan,};let mut level_style = buf.style();level_style.set_color(level_color).set_bold(true);let mut style = buf.style();style.set_color(Color::White).set_dimmed(true);writeln!(buf,"{} {} [{}] {}",Local::now().format("%Y-%m-%d %H:%M:%S"),level_style.value(record.level()),style.value(record.module_path().unwrap_or("<unnamed>")),record.args())}).filter(None, LevelFilter::Debug).init();info!("env_logger initialized.");
}

这样当我们鉴权失败后,后续的中间件就不会执行了(下面笔者写了一个接口)

image-20230824180822950

鉴权成功

image-20230824180842643

日志也符合预期

image-20230824180921566

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

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

相关文章

cortex-A7核IIC实验--STM32MP157AAA

实验目的&#xff1a;采集温湿度传感器值 一&#xff0c;IIC概念 1.同步串行半双工总线&#xff0c;主要用于链接整体电路&#xff0c;硬件结构简单&#xff0c;接口连接方便&#xff0c;成本较低 2.两线制&#xff0c;只有两根双向信号线&#xff0c;数据线SDA,时钟线SCL …

基于AWS的3D模型搜索服务实现

3D模型广泛应用于计算机游戏、电影、工程、零售业、广告等许多领域。市场上有很多制作3D模型的工具&#xff0c;但几乎没有工具可以直观地搜索3D模型数据库以找到类似的3D模型 因为开发好的 3D 模型搜索工具非常具有挑战性。 它需要复杂的计算和 AI/ML 框架来创建模型描述符并提…

【React源码实现】元素渲染的实现原理

前言 本文将结合React的设计思想来实现元素的渲染&#xff0c;即通过JSX语法的方式是如何创建为真实dom渲染到页面上&#xff0c;本文基本不涉及React的源码&#xff0c;但与React的实现思路是一致的&#xff0c;所以非常适合小白学习&#xff0c;建议跟着步骤敲代码&#xff…

Docker consul的容器服务注册与发现

前言一、服务注册与发现二、consul 介绍三、consul 部署3.1 consul服务器3.1.1 建立 Consul 服务3.1.2 查看集群信息3.1.3 通过 http api 获取集群信息 3.2 registrator服务器3.2.1 安装 Gliderlabs/Registrator3.2.2 测试服务发现功能是否正常3.2.3 验证 http 和 nginx 服务是…

CRM系统如何定制?定制哪些功能?

虽然市场上有许多成熟的CRM系统供企业选择&#xff0c;但很多时候&#xff0c;现有的标准化CRM系统无法满足企业的特殊需求。这时就需要进行CRM系统定制。那么&#xff0c;什么时候需要CRM系统定制&#xff0c;CRM系统定制怎么弄&#xff1f;下面我们就说一说。 什么时候需要C…

缓存的设计方式

问题情况&#xff1a; 当有大量的请求到内部系统时&#xff0c;若每一个请求都需要我们操作数据库&#xff0c;例如查询操作&#xff0c;那么对于那种数据基本不怎么变动的数据来说&#xff0c;每一次都去数据库里面查询&#xff0c;是很消耗我们的性能 尤其是对于在海量数据…

【Winform学习笔记(九)】Winform窗体程序延迟函数

Winform窗体程序延迟函数 前言正文1、具体代码2、使用示例 前言 Winform 窗体程序开发时&#xff0c;有时需要程序延迟或休眠几秒&#xff0c;如果直接使用 Thread.Sleep() 方法&#xff0c;会造成程序的假死&#xff0c;UI 界面停止响应&#xff1b; 本文中主要介绍一种方法&…

VB.NET调用VB6封装在OCX控件中的函数

将功能函数封装于OCX之中在VB6平台上可以简单化&#xff0c;在默认模板中直接考贝贴入那些函数即可。在博文【将《VB6编程IEEE浮点算法实践》中的Function封装成OCX】将《VB6编程IEEE浮点算法实践》中的Function封装成OCX_Mongnewer的博客-CSDN博客中对VB6的OCX封装做了具体实践…

keepalived+lvs(DR)(四十六)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、作用 二、调度器配置 三、web节点配置 一、作用 使用keepalived解决lvs的单点故障 高可用集群 二、调度器配置 安装keepalived yum install -y k…

OVRL-V2: A simple state-of-art baseline for IMAGENAV and OBJECTNAV 论文阅读

论文信息 题目&#xff1a;OVRL-V2: A simple state-of-art baseline for IMAGENAV and OBJECTNAV 作者:Karmesh Yadav&#xff0c; Arjun Majumdar&#xff0c; Ram Ramrakhya 来源&#xff1a;arxiv 时间&#xff1a;2023 代码地址&#xff1a; https://github.com/ykarmesh…

whisper 语音识别项目部署

1.安装anaconda软件 在如下网盘免费获取软件&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1zOZCQOeiDhx6ebHh5zNasA 提取码&#xff1a;hfnd 2.使用conda命令创建python3.8环境 conda create -n whisper python3.83.进入whisper虚拟环境 conda activate whisper4.…

vue uniapp 同意验证码滑块验证

前言 &#xff08;vue-puzzle-vcode&#xff09; 发送验证码以及登录的时候会做验证&#xff0c;防止机刷等 效果图 一、安装依赖 npm install vue-puzzle-vcode --save二、使用步骤 1.html使用 <Vcode :show"isShow" success"onSuccess"/>2.j…

数据降维 | MATLAB实现T-SNE降维特征可视化

数据降维 | MATLAB实现T-SNE降维特征可视化 目录 数据降维 | MATLAB实现T-SNE降维特征可视化降维效果基本描述程序设计参考资料 降维效果 基本描述 T-SNE降维特征可视化&#xff0c;MATLAB程序。 T-分布随机邻域嵌入&#xff0c;主要用途是对高维数据进行降维并进行可视化&…

Anolis 8.6 下 Redis 7.2.0 集群搭建和配置

Redis 7.2.0 搭建和集群配置 一.Redis 下载与单机部署1.Redis 下载2.虚拟机配置3.Redis 单机源码安装和测试4.Java 单机连接测试1.Pom 依赖2.配置文件3.启动类4.配置类5.单元测试6.测试结果 二.Redis 集群部署1.主从1.从节点配置2.Java 测试 2.哨兵1.哨兵节点配置2.复制一个哨兵…

SpringCloud学习笔记(六)_Ribbon服务调用

Ribbon介绍 Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具 Ribbon是Netflix发布的开源项目&#xff0c;主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时、重试等。简单的说&#xff0c;就是…

java八股文面试[Spring]——如何实现一个IOC容器

什么是IOC容器 IOC不是一种技术&#xff0c;只是一种思想&#xff0c;一个重要的面向对象编程的法则&#xff0c;它能指导我们如何设计出松耦合&#xff0c;更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象&#xff0c;从而导致类与类之间高耦合&#xff0c;难于…

生成式人工智能的潜在有害影响与未来之路(三)

产品责任法的潜在适用 背景和风险 产品责任是整个二十世纪发展起来的一个法律领域&#xff0c;旨在应对大规模生产的产品可能对社会造成的伤害。这一法律领域侧重于三个主要危害&#xff1a;设计缺陷的产品、制造缺陷的产品和营销缺陷的产品。产品责任法的特点有两个要素&…

LinkedList的顶级理解

目录 1.LinkedList的介绍 LinkedList的结构 2.LinkedList的模拟实现 2.1创建双链表 2.2头插法 2.3尾插法 2.4任意位置插入 2.5查找关键字 2.6链表长度 2.7遍历链表 2.8删除第一次出现关键字为key的节点 2.9删除所有值为key的节点 2.10清空链表 2.11完整代码 3.…

聚观早报 | 云鲸扫拖机器人J4体验;芯科科技第三代无线开发平台

【聚观365】8月24日消息 云鲸扫拖机器人J4体验 芯科科技推出第三代无线开发平台 英伟达与VMWare宣布扩大合作 万物新生&#xff08;爱回收&#xff09;2023年二季度财报 充电桩需求增长带动汽车后服务市场 云鲸扫拖机器人J4体验 家庭卫生清洁是每个人都无法回避的事情&am…

Unity 类Scene窗口相机控制

类Scene窗口相机控制 &#x1f354;效果 &#x1f354;效果 传送门&#x1f448;