当前位置: 首页 > 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这一对组合在上位机开…...

【LeetCode 算法】Card Flipping Game 翻转卡片游戏-阅读题

文章目录 Card Flipping Game 翻转卡片游戏问题描述&#xff1a;EN 分析代码 Tag Card Flipping Game 翻转卡片游戏 问题描述&#xff1a; 在桌子上有 N 张卡片&#xff0c;每张卡片的正面和背面都写着一个正数&#xff08;正面与背面上的数有可能不一样&#xff09;。 我们…...

【leetcode】138.复制带随机指针的链表

方法一&#xff1a;暴力求解 1️⃣遍历原链表&#xff0c;复制节点尾插 2️⃣更新random&#xff0c;原链表中的random对应第几个节点则复制链表中的random就对应第几个 &#x1f4d6;Note 不能通过节点中的val判断random的指向&#xff0c;因为链表中可能存在两个val相等的节点…...

svn工具使用

svn 介绍 解决之道&#xff1a; SCM&#xff1a;软件配置管理 所谓的软件配置管理实际就是对软件源代码进行控制与管理 CVS&#xff1a;元老级产品 VSS&#xff1a;入门级产品 ClearCase&#xff1a;IBM 公司提供技术支持 SVN&#xff1a;主流产品 什么是SVN&#xff…...

SpringBoot项目使用MyBatisX+Apifox IDEA 插件快速开发

今天跟大家介绍两个快速开发项目的插件。能大大提高开发效率。希望能帮助到大家。 1、MyBatisX 插件 MyBatis-Plus为我们提供了强大的mapper和service模板&#xff0c;能够大大的提高开发效率。但是在真正开发过程中&#xff0c;MyBatis-Plus并不能为我们解决所有问题&#xf…...

Redis数据结构

Redis 支持的数据结构的列表 1、String&#xff1a;字符串&#xff0c;是 Redis 最基本的数据类型&#xff0c;可以存储字符串、整数和浮点数。 2、Hash&#xff1a;哈希表&#xff0c;由多个键值对组成&#xff0c;可以储存多个字段和值。 3、List&#xff1a;列表&#xff0c…...

解密Redis:应对面试中的缓存相关问题

文章目录 1. 缓存穿透问题及解决方案2. 缓存击穿问题及解决方案3. 缓存雪崩问题及解决方案4. Redis的数据持久化5. Redis的过期删除策略和数据淘汰策略6. Redis分布式锁和主从同步7. Redis集群方案8. Redis的数据一致性保障和高可用性方案 导语&#xff1a; 在面试过程中&#…...

读取application-dev.properties的中文乱码【bug】

读取application-dev.properties的中文编码【bug】 2023-7-30 22:37:46 版权 禁止其他平台发布时删除以下此话 本文首次发布于CSDN平台 作者是CSDN日星月云 博客主页是https://blog.csdn.net/qq_51625007 禁止其他平台发布时删除以上此话 bug 读取application-dev.propert…...

Linux(centos7)如何实现配置iscsi存储多路径 及DM-Multipath的配置文件概述

安装多路径软件&#xff08;系统默认安装&#xff09; #第一&#xff1a;安装多路径软件yum -y install device-mapper device-mapper-multipath#第二&#xff1a;在CentOS7中启用多路径模块&#xff0c;mpathconf命令及相关模块加载&#xff08;可以使用mpathconf -h查看用法&…...

DK7 vs JDK8 vs JDK11特性和功能的对比

JDK7 vs JDK8 vs JDK11特性和功能的对比 Java Development Kit (JDK) 是 Java 程序员所使用的开发工具包&#xff0c;它提供了编译、调试和运行 Java 程序所需的一切。JDK 在不同的版本中引入了许多新的特性和功能&#xff0c;下面我们来比较 JDK7、JDK8 和 JDK11 之间的一些重…...

你觉得企业为什么需要数据分析?

数据对于企业的作用就好比&#xff0c;一位士兵作战需要手枪一样&#xff0c;仅仅只是作为一项工具&#xff0c;帮助我们提高取得胜利的几率或者概率。数据只是数据&#xff0c;不代表业务&#xff0c;所起的作用也是有限的&#xff0c;网上那些夸大数据作用的&#xff0c;没有…...