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这一对组合在上位机开…...
STM32MP25x嵌入式Linux平台:集成XFCE、VNC、TSN的工业边缘计算解决方案
1. 项目概述:一个面向工业边缘的“瑞士军刀”级嵌入式平台最近,我们团队基于STM32MP25x系列核心板,成功构建并发布了一套完整的Debian系统镜像。这个项目的目标非常明确:打造一个开箱即用、功能全面、且能无缝覆盖从传统工业控制到…...
从ADC采样到FFT分析:手把手教你用STM32F407的DSP库搞定频谱计算
从ADC采样到FFT分析:手把手教你用STM32F407的DSP库搞定频谱计算 在工业振动监测、音频信号处理和电源质量分析等场景中,频谱分析是理解信号特征的关键技术。STM32F407凭借其Cortex-M4内核和硬件FPU,配合CMSIS-DSP库,能够高效实现实…...
别再死记硬背参数了!Halcon形状匹配(create_shape_model)核心参数保姆级解读
Halcon形状匹配核心参数深度解析:从原理到实战调参指南 在工业视觉检测领域,形状匹配技术一直是定位和识别的核心手段。Halcon作为行业领先的机器视觉软件,其create_shape_model和find_shape_model算子提供了强大的形状匹配能力。然而&#…...
麒麟系统上跑32位老程序,别再折腾了!用这个离线打包法,5分钟搞定依赖
麒麟系统32位程序兼容方案:离线依赖打包全流程指南 在国产化操作系统迁移浪潮中,许多企业面临一个共同难题——那些关键业务依赖的32位遗留程序如何在仅支持64位的新系统上运行?本文将以麒麟系统为例,详解一套经过实战检验的离线依…...
凡亿AD22--PCB全连接与十字花焊盘连接铺铜规则
核心重点:铺铜与焊盘/过孔的连接方式,核心分为「全连接」「十字连接」「不连接」三种,实际设计中仅常用前两种;连接方式的选择,核心取决于「焊接方式」「载流需求」,过孔连接需默认采用全连接,避…...
从开题到定稿,okbiye 如何让本科毕业论文写作告别 “通宵焦虑”
okbiye-免费查重复率aigc检测/开题报告/毕业论文/智能排版/文献综述/AI PPT毕业论文 - Okbiye智能写作https://www.okbiye.com/ai/bylw 一、本科毕业论文的 “三座大山”,正在拖垮你的毕业季 对于大多数本科生而言,毕业论文写作早已不是 “写一篇文章”…...
重塑知识连接:探索Obsidian模板驱动的Zettelkasten思维系统
重塑知识连接:探索Obsidian模板驱动的Zettelkasten思维系统 【免费下载链接】Obsidian-Templates A repository containing templates and scripts for #Obsidian to support the #Zettelkasten method for note-taking. 项目地址: https://gitcode.com/gh_mirror…...
2026墙体广告供应商亲测靠谱!
行业痛点分析墙体广告领域面临着诸多核心技术挑战。传统户外大牌、短视频投放费用高昂,单次投放曝光有限,数据表明,下沉市场触达成本居高不下,中小品牌难以承担长期投放。城市广告无法渗透乡镇、农村等下沉市场,目标客…...
Linux内核死锁实战:从原理到调试与预防策略
1. 项目概述:当内核代码“卡住”时在Linux内核开发与系统运维的深水区,有一个让所有工程师都闻之色变、却又不得不面对的“幽灵”——死锁。它不像段错误那样直接崩溃,也不像内存泄漏那样缓慢侵蚀,而是以一种近乎“优雅”的静默方…...
fpga开发过程中遇到的一些小问题
vivado开发过程中的一些error1、[Chipscope 16-213] The debug port u_ila_0/probe13 has 28 unconnected channels (bits). This will cause errors during implementation.2、ERROR: [Labtools 27-3312] Data read from hw_ila [hw_ila_1] is corrupted. Unable to upload wa…...
