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大小方法的前提 : 网关只做转发逻辑,不修改请求、相应的body内容。 SpringCloud Gateway内部的机制类似下图,HttpServer(也就是NettyServer)接收外部的请求,在Gateway内部请求将会通过Htt…...

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

Spring Boot 集成 Thymeleaf 模板引擎
Spring Boot 集成 Thymeleaf 模板引擎 1. Thymeleaf 介绍 Thymeleaf 是适用于 Web 和独立环境的现代服务器端 Java 模板引擎。 Thymeleaf 的主要目标是为开发工作流程带来优雅的自然模板,既可以在浏览器中正确显示的 HTML,也可以用作静态原型ÿ…...
如何快速找到合适的工作?
前面好几篇文章都在写面试的感悟,带大家了解了目前的一些市场行情,以及面试过程中招聘方与求职者看待面试的不同视角。 今天这篇文章写一些对大家求职面试可能更有用的几条建议: 第一,值与量的权衡 在早些年,大多数…...
Elasticsearch入门用例
快速开始 使用版本:V7.12 资料来自官方文档 本指南幫助初學者學習如何: 將數據添加到 Elasticsearch 搜索和排序數據 在搜索過程中從非結構化內容中提取字段 测试运行: http://localhost:9200 响应: {"name": &qu…...

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

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

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

不同语言操作符的优先级
看到标题,可能会心生疑惑: 这么基础且重要的操作,不同语言不应该是一致的吗? 并不一定,比如对于右移运算和加法运算,Go就与其他多数语言表现得不一致: Go: package mainimport "fmt"func main() …...
YOLOv5源码解读1.7-网络架构common.py
往期回顾:YOLOv5源码解读1.0-目录_汉卿HanQ的博客-CSDN博客 学习了yolo.py网络模型后,今天学习common.py,common.py存放这YOLOv5网络搭建的通用模块,如果修改某一块,就要修改这个文件中对应的模块 目录 1.导入python包 2.加载自…...

关于前端框架vue2升级为vue3的相关说明
一些框架需要升级 当前(202306) Vue 的最新稳定版本是 v3.3.4。Vue 框架升级为最新的3.0版本,涉及的相关依赖变更有: 前提条件:已安装 16.0 或更高版本的Node.js(摘) 必须的变更:核…...
gdb调试时查看汇编代码
在gdb中查看汇编代码,可以使用display命令或x命令。 以下是一个示例程序,我们以它为例来演示如何在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 延迟分析(二)
用户归属服务器(IMS HSS)是下一代通信网(NGN)核心网络 IP 多媒体子系统(IMS)中的主要用户数据库。IMS HSS 中存储用户的配置文件,可执行用户的身份验证和授权,并提供对呼叫控制服务器…...

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

Kotlin~Facade
概念 又称门面模式,为复杂系统提供简单交互接口。 角色介绍 Facade:外观类,供客户端调用,将请求委派给响应的子系统。SubSystem:子系统,独立的子设备或子类 UML 代码实现 class Light(val name: Strin…...
服务配置文件/var/lib/systemd与/etc/systemd/
这两个目录都是用于存储 systemd 服务的配置文件。但它们的作用和用途略有不同。 /etc/systemd/system/: 这个目录存放的是系统管理员自己创建或修改的 systemd 服务配置文件。在这里的配置文件优先级更高,会覆盖默认的 systemd 配置文件。通常,我们可以…...

华为、阿里巴巴、字节跳动 100+ Python 面试问题总结(一)
系列文章目录 个人简介:机电专业在读研究生,CSDN内容合伙人,博主个人首页 Python面试专栏:《Python面试》此专栏面向准备面试的2024届毕业生。欢迎阅读,一起进步!🌟🌟🌟 …...

【牛客网】二叉搜索树与双向链表
二叉搜索树与双向链表 题目描述算法分析编程代码 链接: 二叉搜索树与双向链表 题目描述 算法分析 编程代码 /* 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
前提: 注意:你要有个梯子才能更稳定的访问。 不需要安装Oracle,但是需要注册。(还算方便的) 注册&登录过程 进入Oracle APEX官网,我们选择免费的APEX工作区即可,点击“免费注册”。在注册…...

C#+WPF上位机开发(模块化+反应式)
在上位机开发领域中,C#与C两种语言是应用最多的两种开发语言,在C语言中,与之搭配的前端框架通常以QT最为常用,而C#语言中,与之搭配的前端框架是Winform和WPF两种框架。今天我们主要讨论一下C#和WPF这一对组合在上位机开…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...

React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
Java 二维码
Java 二维码 **技术:**谷歌 ZXing 实现 首先添加依赖 <!-- 二维码依赖 --><dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.1</version></dependency><de…...