当前位置: 首页 > news >正文

SpringCloud Gateway获取请求响应body大小

前提

本文获取请求、响应body大小方法的前提 : 网关只做转发逻辑,不修改请求、相应的body内容。
SpringCloud Gateway内部的机制类似下图,HttpServer(也就是NettyServer)接收外部的请求,在Gateway内部请求将会通过HttpClient(Netty实现的客户端)发送给后端应用。
本文的body获取方式,基于HttpClient端实现,通过获取HttpClient发送、接收后端的请求、响应body实现。如果SpringCloudGateway内部逻辑修改了body,那么本文方式获取的body大小将会存在歧义误差。
如果想要在HttpServer层获取到报文大小,可以尝试自定义实现Netty的ChannelDuplexHandler,尝试获取到报文大小。
在这里插入图片描述

SpringCloud Gateway底层基于异步模型Netty实现,调用时相关的body内容不直接加载到内存。如果使用简单的SpringCloud Gateway Filter读取报文,读取body大小,会大幅影响网关性能。因此需要考虑一种方法,在不影响网关性能的前提下,获取请求、响应body大小。

方式一、重写SpringCloudGateway Filter类

重写 NettyRoutingFilter 获取 Request Body

重写Gateway自带的org.springframework.cloud.gateway.filter.NettyRoutingFilter
修改类的filter内的代码,在底层获取请求body的大小,并在exchange保存。

Flux<HttpClientResponse> responseFlux = getHttpClient(route, exchange).headers(headers -> {...}).request(method).uri(url).send((req, nettyOutbound) -> {...return nettyOutbound.send(request.getBody().map(body -> {// 修改此处代码,获取请求body的大小,并将获取到的结果存入exchange内。int size = body.readableByteCount();exchange.getAttributes().put("gw-request-body-size", size);return getByteBuf(body);}));}).responseConnection((res, connection) -> {...

重写 NettyWriteResponseFilter 获取 Response Body

重写Gateway自带的org.springframework.cloud.gateway.filter.NettyWriteResponseFilter
修改类filter内的代码,在底层获取响应body的大小,并在exchange保存。

return chain.filter(exchange).doOnError(throwable -> cleanup(exchange)).then(Mono.defer(() -> {...// TODO: needed?final Flux<DataBuffer> body = connection.inbound().receive().retain().map(byteBuf -> {// 获取响应报文的长度,并将结果写入exchange内。int respSize = byteBuf.readableBytes();exchange.getAttributes().put("gw-response-body-size", respSize);return wrap(byteBuf, response);});...

自定义Filter打印报文大小

通过上述的2个方法,request、response body的大小已经写入exchange内,只需要实现一个自定义的Filter,就可以获取到报文的大小。假设自定义的Filter命名为BodySizeFilter,它的Order需要在NettyWriteResponseFilter之前。
在这里插入图片描述

在filter方法内,从exchange获取request、response body大小。

@Slf4j
@Component
public class BodySizeFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {return chain.filter(exchange).then(Mono.defer(() -> {Integer exchangeReq = exchange.getAttribute("gw-request-body-size");Integer exchangeResp = exchange.getAttribute("gw-response-body-size");log.info("req from exchange: {}", exchangeReq);log.info("resp from exchange: {}", exchangeResp);respSize.set(null);reqSize.set(null);return Mono.empty();}));}@Overridepublic int getOrder() {return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;}
}

方式二、自定义Netty Handler

另一种方式是基于Netty的Hander,非重写SpringCloud Gateway类。本文构建的SpringCloudGateway版本为2.2.9.RELEASE

实现自定义的Netty ChannelDuplexHandler

重写2个方法 write、channelRead。

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.UUID;@Slf4j
public class HttpClientLoggingHandler extends ChannelDuplexHandler {private static final AttributeKey<Long> RESP_SIZE = AttributeKey.valueOf("resp-size");private static final AttributeKey<Long> REQ_SIZE = AttributeKey.valueOf("req-size");@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {if (msg instanceof ByteBuf) {final ByteBuf buf = (ByteBuf) msg;// 读取报文大小,一笔请求可能存在多个 msg,也就是一个请求报文,可能分多次经过write方法。int length = buf.readableBytes();long size;// 将结果以attribute形式保存在channel内,一笔完整的调用对应一个完整的context上下文。Attribute<Long> sizeAttr = ctx.channel().attr(REQ_SIZE);if (sizeAttr.get() == null) {size = 0L;} else {size = sizeAttr.get();}// 每次累加当前请求的报文大小。size += length;ctx.channel().attr(REQ_SIZE).set(size);}super.write(ctx, msg, promise);}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {if (msg instanceof ByteBuf) {final ByteBuf buf = (ByteBuf) msg;// 获取响应body的大小,一笔响应可能存在多个 msg,也就是一个响应报文,可能分多次经过channelRead方法。int length = buf.readableBytes();long size;Attribute<Long> sizeAttr = ctx.channel().attr(RESP_SIZE);if (sizeAttr.get() == null) {size = 0L;} else {size = ctx.channel().attr(RESP_SIZE).get();}size += length;// 将结果以attribute形式保存在channel内,一笔完整的调用对应一个完整的context上下文。ctx.channel().attr(RESP_SIZE).set(size);}super.channelRead(ctx, msg);}
}

将自定义Handler配置到网关内。

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.config.HttpClientCustomizer;
import org.springframework.context.annotation.Configuration;
import reactor.netty.channel.BootstrapHandlers;
import reactor.netty.http.client.HttpClient;@Slf4j
@Configuration
public class GwHttpClientCustomizer implements HttpClientCustomizer {@Overridepublic HttpClient customize(HttpClient client) {// 本文基于2.2.9.RELEASE的SpringCloud Gateway实现。return client.tcpConfiguration(tcpClient ->tcpClient.bootstrap(b ->BootstrapHandlers.updateConfiguration(b, "client-log", (connectionObserver, channel) -> {channel.pipeline().addFirst("client-log", new HttpClientLoggingHandler());})));}
}

通过上述自定义的方法,一笔完整的调用中请求、响应body的大小,已经被计算保存在netty channel内,只需要自定义SpringCloud Gateway Filter获取到结果。

import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.netty.Connection;import java.util.function.Consumer;import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CLIENT_RESPONSE_CONN_ATTR;/*** @author luobo on 2023/08/01 3:51 PM*/
@Slf4j
@Component
public class BodySizeFilter implements GlobalFilter, Ordered {private static final AttributeKey<Long> REQ_SIZE = AttributeKey.valueOf("req-size");private static final AttributeKey<Long> RESP_SIZE = AttributeKey.valueOf("resp-size");@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {return chain.filter(exchange).then(Mono.defer(() -> {// SpringCloud Gateway内将每个调用的Connection保存在exchange内// connection 可以获取到 channelConnection connection = exchange.getAttribute(CLIENT_RESPONSE_CONN_ATTR);Attribute<Long> respSize = connection.channel().attr(RESP_SIZE);Attribute<Long> reqSize = connection.channel().attr(REQ_SIZE);long resp;if (respSize.get() == null) {resp = 0L;} else {resp = respSize.get();}long req;if (reqSize.get() == null) {req = 0L;} else {req = reqSize.get();}log.info("------------------------> resp size: {}", resp);log.info("------------------------> req size: {}", req);// 每次调用结束需要清空保存的值(因为连接会复用)respSize.set(null);reqSize.set(null);return Mono.empty();}));}@Overridepublic int getOrder() {return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;}
}

通过此方法获取的body大小会比真实的body大 ,因为它包含了请求和响应头的信息。


总结

本人更加推荐使用方式一。

相关文章:

SpringCloud Gateway获取请求响应body大小

前提 本文获取请求、响应body大小方法的前提 : 网关只做转发逻辑&#xff0c;不修改请求、相应的body内容。 SpringCloud Gateway内部的机制类似下图&#xff0c;HttpServer&#xff08;也就是NettyServer&#xff09;接收外部的请求&#xff0c;在Gateway内部请求将会通过Htt…...

二叉树的层序遍历

给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09; 。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[9,20],[15,7]]示例 2&#xff1a; 输入…...

Spring Boot 集成 Thymeleaf 模板引擎

Spring Boot 集成 Thymeleaf 模板引擎 1. Thymeleaf 介绍 Thymeleaf 是适用于 Web 和独立环境的现代服务器端 Java 模板引擎。 Thymeleaf 的主要目标是为开发工作流程带来优雅的自然模板&#xff0c;既可以在浏览器中正确显示的 HTML&#xff0c;也可以用作静态原型&#xff…...

如何快速找到合适的工作?

前面好几篇文章都在写面试的感悟&#xff0c;带大家了解了目前的一些市场行情&#xff0c;以及面试过程中招聘方与求职者看待面试的不同视角。 今天这篇文章写一些对大家求职面试可能更有用的几条建议&#xff1a; 第一&#xff0c;值与量的权衡 在早些年&#xff0c;大多数…...

Elasticsearch入门用例

快速开始 使用版本&#xff1a;V7.12 资料来自官方文档 本指南幫助初學者學習如何&#xff1a; 將數據添加到 Elasticsearch 搜索和排序數據 在搜索過程中從非結構化內容中提取字段 测试运行&#xff1a; http://localhost:9200 响应&#xff1a; {"name": &qu…...

python制作超炫流星雨表白,python好看的流星雨代码

大家好&#xff0c;本文将围绕python制作超炫流星雨表白展开说明&#xff0c;python好看的流星雨代码是一个很多人都想弄明白的事情&#xff0c;想搞清楚html流星雨代码可复制需要先了解以下几个事情。 本文讲述了Python代码示例&#xff1a;实现流星雨特效&#xff01;具有很好…...

iOS数字转为图片

根据数字&#xff0c;转成对应的图片 - (void)viewDidLoad {[super viewDidLoad];[self testNum2String:10086]; }/// 根据数字&#xff0c;显示对应的图片 数字用特定的图片显示 - (void)testNum2String:(NSInteger)num {UIView *numContentView [[UIView alloc] initWithFr…...

mac cli文件管理器

背景 最近研究了一下在控制台查看文件的插件ranger, 官方的解释是&#xff1a;一个cli下的文件管理器。觉得效果也很酷炫&#xff0c;所以在此展示一下。 安装 brew install ranger配置生成 建议第一次使用的时候使用 ranger --copy-configall将会在~/.config/ranger目录输…...

不同语言操作符的优先级

看到标题&#xff0c;可能会心生疑惑: 这么基础且重要的操作&#xff0c;不同语言不应该是一致的吗&#xff1f; 并不一定&#xff0c;比如对于右移运算和加法运算&#xff0c;Go就与其他多数语言表现得不一致&#xff1a; Go: package mainimport "fmt"func main() …...

YOLOv5源码解读1.7-网络架构common.py

往期回顾:YOLOv5源码解读1.0-目录_汉卿HanQ的博客-CSDN博客 学习了yolo.py网络模型后&#xff0c;今天学习common.py&#xff0c;common.py存放这YOLOv5网络搭建的通用模块&#xff0c;如果修改某一块&#xff0c;就要修改这个文件中对应的模块 目录 1.导入python包 2.加载自…...

关于前端框架vue2升级为vue3的相关说明

一些框架需要升级 当前&#xff08;202306&#xff09; Vue 的最新稳定版本是 v3.3.4。Vue 框架升级为最新的3.0版本&#xff0c;涉及的相关依赖变更有&#xff1a; 前提条件&#xff1a;已安装 16.0 或更高版本的Node.js&#xff08;摘&#xff09; 必须的变更&#xff1a;核…...

gdb调试时查看汇编代码

在gdb中查看汇编代码&#xff0c;可以使用display命令或x命令。 以下是一个示例程序&#xff0c;我们以它为例来演示如何在gdb中查看汇编代码。 #include <stdio.h> int main() { int a 10; int b 20; int c a b; printf("c %d\n", c); return 0;…...

小研究 - JVM GC 对 IMS HSS 延迟分析(二)

用户归属服务器&#xff08;IMS HSS&#xff09;是下一代通信网&#xff08;NGN&#xff09;核心网络 IP 多媒体子系统&#xff08;IMS&#xff09;中的主要用户数据库。IMS HSS 中存储用户的配置文件&#xff0c;可执行用户的身份验证和授权&#xff0c;并提供对呼叫控制服务器…...

eNSP 路由器启动时一直显示 # 号的解决办法

文章目录 1 问题截图2 解决办法2.1 办法一&#xff1a;排除防火墙原因导致 3 验证是否成功 1 问题截图 路由器命令行一直显示 # 号&#xff0c;如下图 2 解决办法 2.1 办法一&#xff1a;排除防火墙原因导致 排查是否因为系统防火墙原因导致。放行与 eNSP 和 virtualbox 相…...

Kotlin~Facade

概念 又称门面模式&#xff0c;为复杂系统提供简单交互接口。 角色介绍 Facade&#xff1a;外观类&#xff0c;供客户端调用&#xff0c;将请求委派给响应的子系统。SubSystem&#xff1a;子系统&#xff0c;独立的子设备或子类 UML 代码实现 class Light(val name: Strin…...

服务配置文件/var/lib/systemd与/etc/systemd/

这两个目录都是用于存储 systemd 服务的配置文件。但它们的作用和用途略有不同。 /etc/systemd/system/: 这个目录存放的是系统管理员自己创建或修改的 systemd 服务配置文件。在这里的配置文件优先级更高&#xff0c;会覆盖默认的 systemd 配置文件。通常&#xff0c;我们可以…...

华为、阿里巴巴、字节跳动 100+ Python 面试问题总结(一)

系列文章目录 个人简介&#xff1a;机电专业在读研究生&#xff0c;CSDN内容合伙人&#xff0c;博主个人首页 Python面试专栏&#xff1a;《Python面试》此专栏面向准备面试的2024届毕业生。欢迎阅读&#xff0c;一起进步&#xff01;&#x1f31f;&#x1f31f;&#x1f31f; …...

【牛客网】二叉搜索树与双向链表

二叉搜索树与双向链表 题目描述算法分析编程代码 链接: 二叉搜索树与双向链表 题目描述 算法分析 编程代码 /* struct TreeNode {int val;struct TreeNode *left;struct TreeNode *right;TreeNode(int x) :val(x), left(NULL), right(NULL) {} };*/ class Solution { public:…...

Oracle免费在线编程:Oracle APEX

前提&#xff1a; 注意&#xff1a;你要有个梯子才能更稳定的访问。 不需要安装Oracle&#xff0c;但是需要注册。&#xff08;还算方便的&#xff09; 注册&登录过程 进入Oracle APEX官网&#xff0c;我们选择免费的APEX工作区即可&#xff0c;点击“免费注册”。在注册…...

C#+WPF上位机开发(模块化+反应式)

在上位机开发领域中&#xff0c;C#与C两种语言是应用最多的两种开发语言&#xff0c;在C语言中&#xff0c;与之搭配的前端框架通常以QT最为常用&#xff0c;而C#语言中&#xff0c;与之搭配的前端框架是Winform和WPF两种框架。今天我们主要讨论一下C#和WPF这一对组合在上位机开…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

FastAPI 教程:从入门到实践

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;支持 Python 3.6。它基于标准 Python 类型提示&#xff0c;易于学习且功能强大。以下是一个完整的 FastAPI 入门教程&#xff0c;涵盖从环境搭建到创建并运行一个简单的…...

页面渲染流程与性能优化

页面渲染流程与性能优化详解&#xff08;完整版&#xff09; 一、现代浏览器渲染流程&#xff08;详细说明&#xff09; 1. 构建DOM树 浏览器接收到HTML文档后&#xff0c;会逐步解析并构建DOM&#xff08;Document Object Model&#xff09;树。具体过程如下&#xff1a; (…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

【单片机期末】单片机系统设计

主要内容&#xff1a;系统状态机&#xff0c;系统时基&#xff0c;系统需求分析&#xff0c;系统构建&#xff0c;系统状态流图 一、题目要求 二、绘制系统状态流图 题目&#xff1a;根据上述描述绘制系统状态流图&#xff0c;注明状态转移条件及方向。 三、利用定时器产生时…...

Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?

Redis 的发布订阅&#xff08;Pub/Sub&#xff09;模式与专业的 MQ&#xff08;Message Queue&#xff09;如 Kafka、RabbitMQ 进行比较&#xff0c;核心的权衡点在于&#xff1a;简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

【生成模型】视频生成论文调研

工作清单 上游应用方向&#xff1a;控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...