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

news2024/11/24 14:38:42

文章目录

  • 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 Request
            println!("Hi from start. You requested: {}", req.path());
            srv.call(req).map(|res| {
                // Post-process a Response
                println!("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
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 = 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>
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>>;

    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
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 }))
    }
}

pub struct AuthMiddleware<S> {
    service: S,
}

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>>;

    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.coloradmin.cn/o/923176.html

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

相关文章

一文全懂!带你了解芯片“流片”!

一、流片是什么&#xff1f; 流片(tape-out)是指通过一系列工艺步骤在流水线上制造芯片&#xff0c;是集成电路设计的最后环节&#xff0c;也就是送交制造。 流片即为"试生产"&#xff0c;简单来说就是设计完电路以后&#xff0c;先生产几片几十片&#xff0c;供测试…

Packet_Tracer的使用

一、实验目的&#xff1a; 通过该实验了解Packet Tracer的使用方法&#xff0c;能够用Packet Tracer建立和模拟网络模型。 二、主要任务&#xff1a; 1.熟悉PT的界面&#xff0c;了解按键用途。 2.尝试自己建立一个小型网络&#xff0c;并测试连通性。 3.学习P…

STM32--USART串口

文章目录 通信接口串口通信硬件电路电平标准参数时序 USART主要特性框图 数据帧发送器 波特率发生器SWART串口发送与接收工程串口收发数据包 通信接口 通信接口是指连接中央处理器&#xff08;CPU&#xff09;和标准通信子系统之间的接口&#xff0c;用于实现数据和控制信息在不…

【JVM 内存结构 | 程序计数器】

内存结构 前言简介程序计数器定义作用特点示例应用场景 主页传送门&#xff1a;&#x1f4c0; 传送 前言 Java 虚拟机的内存空间由 堆、栈、方法区、程序计数器和本地方法栈五部分组成。 简介 JVM&#xff08;Java Virtual Machine&#xff09;内存结构包括以下几个部分&#…

关于CC2652的看门狗和系统时钟的我呢

看门狗 可以在CCS的syscfg的ui中配置&#xff0c;如下图 如果想看相关例程&#xff0c;可以电极最顶部watchdog旁边的问号 相关问题&#xff1a; 例程中没有添加hw_wdt的头文件&#xff0c;需要#include <ti/devices/cc13x2_cc26x2/inc/hw_wdt.h>&#xff0c;否则在获…

全面介绍ERP采购审批管理

在现代企业中&#xff0c;采购管理对于保障企业正常运营和维护供应链的稳定性至关重要。然而&#xff0c;传统的手动采购审批流程常常存在效率低下、易出错和缺乏可追溯性等问题。为了解决这些问题&#xff0c;越来越多的企业选择采用ERP采购审批管理方法&#xff0c;以实现更高…

CentOS7 TAR安装 EMQX(MQTT)

1、软件下载 官网 --> 右上角[免费试用] --> EMQX 下载 --> EMQX 开源版 --> 选择版本 系统 --> [免费下载] 选择 tar.gz amd64 --> [立即下载] 选择对应下载方式 上传到 /usr/local/ 目录下。 2、安装 #进入操作目录 cd /usr/local#创建安装目录 mk…

javascript常用的东西

JavaScript 是一门强大的编程语言&#xff0c;用于为网页添加交互性和动态性。也可以锻炼人们的逻辑思维&#xff0c;是一个非常好的东西。 一、变量和数据类型&#xff1a; 变量&#xff1a; 变量是用于存储数据值的容器。在 JavaScript 中&#xff0c;你可以使用 var、let…

ELK之LogStash介绍及安装配置

一、logstash简介 集中、转换和存储数据 Logstash 是免费且开放的服务器端数据处理管道&#xff0c;能够从多个来源采集数据&#xff0c;转换数据&#xff0c;然后将数据发送到您最喜欢的“存储库”中。 Logstash 能够动态地采集、转换和传输数据&#xff0c;不受格式或复杂度的…

2023.8 - java - Java 方法

什么是方法呢&#xff1f; Java方法是语句的集合&#xff0c;它们在一起执行一个功能。 方法是解决一类问题的步骤的有序组合方法包含于类或对象中方法在程序中被创建&#xff0c;在其他地方被引用 方法的命名规则 1.方法的名字的第一个单词应以小写字母作为开头&#xff0…

铁威马教程丨铁威马NAS如何使用安全顾问工具

在使用NAS的过程中&#xff0c;我们时常可能忽略了一些小细节&#xff0c;久而久之可能造成一定的风险&#xff0c;影响着我们NAS的健康。而使用铁威马NAS的安全顾问工具&#xff0c;可以快速地帮我们扫描系统设置是否安全&#xff0c;让我们更放心更安心地使用NAS。 安全顾问…

【Antd】Cascader 级联组件添加顶部栏(解决低版 antd 无 dropdownRender 属性方案)

文章目录 背景实现 背景 表单级联菜单需要添加表头&#xff0c;用于表示各级含义。 如果你的antd版本大于等于4.4.0&#xff0c;则直接可以使用dropdownRender 属性自定义&#xff0c;本篇文章可以直接跳过。 参数说明类型默认值版本dropdownRender自定义下拉框内容(menus: R…

LabVIEW利用线性压缩弹簧开发重量测量系统

LabVIEW利用线性压缩弹簧开发重量测量系统 柔性传感器的曲率半径相对于指定重量的变化特性可用作力测量方法。在许多应用中&#xff0c;例如&#xff1a;汽车和工业控制&#xff0c;计算机外围设备&#xff0c;操纵杆和测量设备&#xff0c;带有压缩弹簧和柔性传感器的重量测量…

【第三阶段】kotlin语言的takeUnless内置函数

1.takeUnless和takeif功能是相反的 username.takeif{true/false} true:返回username本身 false:返回null username.takeUnless{true/false} false:返回username本身 true:返回nullpackage Stage3 class Manager{private var info:String?nullfun getInfo()infofun setI…

ROS系统API接口

文章目录 一、 节点1. 节点初始化 ros API官网链接 一、 节点 1. 节点初始化 API名称&#xff1a;ros::init() ROS程序调用的第一个函数&#xff0c;用于对ROS程序的初始化。API常用形式&#xff1a; ros::init()函数最常见的使用方式为 ros::init(argc, argv, "my_n…

C++位域

Bit field 是什么&#xff1f; “ 位域 “ 或 “ 位段 “(Bit field)为一种数据结构&#xff0c;可以把数据以位的形式紧凑的储存&#xff0c;并允许程序员对此结构的位进行操作。这种数据结构的一个好处是它可以使数据单元节省储存空间&#xff0c;当程序需要成千上万个数据单…

Mybatis分页查询及特殊字符的处理

一. Mybatis分页查询 分页是我们在开发中绕不过去的一个坎&#xff01;当你的数据量大了的时候&#xff0c;一次性将所有数据查出来不现实&#xff0c;所以我们一般都是分页查询的&#xff0c;减轻服务端的压力&#xff0c;提升了速度和效率&#xff01;也减轻了前端渲染的压力…

如何使用海艺人工智能生成创意汉字

1、用某种字体生成文字。 jf storehttps://store.justfont.com/fonts 2、打开seaart。ai网站。https://www.seaart.ai/home 3、效果如下。 4、右键保存图片。

vue实现自定义树形组件

欢迎点击关注-前端面试进阶指南&#xff1a;前端登顶之巅-最全面的前端知识点梳理总结 *分享一个使用比较久的&#x1fa9c; 效果展示&#xff1a; 近期的一个功能需求&#xff0c;实现一个树形结构&#xff1a;可点击&#xff0c;可拖拽&#xff0c;右侧数据可以拖拽到对应的…

金融学复习博迪(第10-12章)

第4部分 风险管理与资产组合理论 第10章 风险管理的原理 10.1什么是风险 风险与风险规避 在证券市场上&#xff0c;风险是指由于投资环境的不确定性和变动性&#xff0c;而导致的投资者收益的不确定性和变动性。不确定性是风险的必要条件而非充分条件。任何一种存在风险的情…