Spring Cloud Gateway 修改请求体、响应体

前言

例行每半年一次的工作轮换,接手了同事的网关服务
年底了工作不是很忙,看了下前人的代码,虽然都能读懂,但感觉应该可以再优雅一点
于是把网关的相关知识又翻阅了一下
官方资料

PS:这里如果按新方案调整的话,在结构上会看起来更清晰、可读性上会得到一定的提高
但学习研究是一回事,我肯定不会去直接修改前人的代码,我们还是要对运行稳定的项目持一点敬畏心,搞得不好,手一抖就是一个BUG

原方案 - 请求体修改

  1. 自定义 Filter 实现 GlobalFilter, Ordered 接口
  2. 重写 filter 方法,具体操作如下

// 这里的 exchange 是 过滤器入参 ServerWebExchange
ServerRequest sr = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
// 在 flatMap 方法里做请求体的修改
Mono modifiedBody = sr.bodyToMono(String.class).flatMap

原方案 - 响应体修改

  1. 自定义 Filter 实现 GlobalFilter, Ordered 接口
  2. 自定义 ResponseDecorator 继承 ServerHttpResponseDecorator 类,顶层是 ServerHttpResponse 接口
  3. 重写 ServerHttpResponseDecorator 的 writeWith 接口,以此实现 responseBody 的修改,加密加签
	@Override@SuppressWarnings(value = "unchecked")public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {if (body instanceof Flux) {Flux<DataBuffer> fluxBody = (Flux<DataBuffer>) body;return super.writeWith(fluxBody.buffer().map(dataBuffers -> {DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();DataBuffer join = dataBufferFactory.join(dataBuffers);byte[] content = new byte[join.readableByteCount()];join.read(content);// 释放掉内存DataBufferUtils.release(join);String originalResponseBody = new String(content, StandardCharsets.UTF_8);// 修改响应体String updatedResponseBody = modifyBody(originalResponseBody);getDelegate().getHeaders().setContentLength(updatedResponseBody.getBytes().length);return bufferFactory().wrap(updatedResponseBody.getBytes());}));}return super.writeWith(body);}
  1. 在 filter 方法中,添加 response 装饰器
ResponseDecorator decorator = new ResponseDecorator(exchange.getResponse());
Mono<Void> filter = chain.filter(exchange.mutate().response(decorator).build());
return filter;

新方案 - 修改客户端请求体

创建请求过滤器 RequestModifyFilter

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.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import javax.annotation.Resource;
import java.util.Objects;/*** 修改客户端请求体* @author weiheng* @date 2024-02-01**/
@Component
@Slf4j
public class RequestModifyFilter implements GlobalFilter, Ordered {@Resourceprivate ModifyRequestBodyGatewayFilterFactory modifyRequestBodyFilter;@Resourceprivate RequestRewriteFunction requestRewriteFunction;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();MediaType mediaType = request.getHeaders().getContentType();String method = request.getMethodValue();if (Objects.equals(HttpMethod.POST.name(), method) && MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {// 如果是POST请求,并且请求体是 application/json 格式return modifyRequestBodyFilter.apply(new ModifyRequestBodyGatewayFilterFactory.Config().setRewriteFunction(byte[].class, byte[].class, requestRewriteFunction)).filter(exchange, chain);} else {return filter(exchange, chain);}}@Overridepublic int getOrder() {return FilterPriorityConstant.REQUEST_BODY_DECRYPT_AND_SIGNATURE_VERIFICATION;}
}

RequestRewriteFunction

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;/*** 修改请求体* @author weiheng* @date 2024-01-31**/
@Slf4j
@Component
public class RequestRewriteFunction implements RewriteFunction<byte[], byte[]> {/*** Applies this function to the given arguments.** @param serverWebExchange the first function argument* @param bytes             the second function argument* @return the function result*/@Overridepublic Publisher<byte[]> apply(ServerWebExchange serverWebExchange, byte[] bytes) {ServerHttpRequest request = serverWebExchange.getRequest();HttpHeaders headers = request.getHeaders();String requestId = headers.getFirst(HttpConstant.HEADER_REQUEST_ID);JSONObject requestBody = JSON.parseObject(bytes);log.info(">>>>> requestId[{}], 原始请求体[{}]", requestId, requestBody);try {// TODO 写自己的请求拦截业务,比如请求体的 【解密、验签】// 测试,看业务服务的 controller类请求体中是否添加了该属性 - 自测OKrequestBody.put("traceId", RandomUtil.randomInt());log.info(">>>>> requestId[{}], 修改后请求体[{}]", requestId, requestBody);return Mono.just(requestBody.toString().getBytes());} catch (Exception e) {log.error(">>>>> requestId[{}], 修改请求体出错", requestId, e);throw e;}}}

新方案 - 修改服务端响应体

创建过滤器 ResponseModifyFilter

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.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import javax.annotation.Resource;
import java.util.Objects;/*** 修改服务端响应体* @author weiheng* @date 2024-02-01**/
@Component
@Slf4j
public class ResponseModifyFilter implements GlobalFilter, Ordered {@Resourceprivate ModifyResponseBodyGatewayFilterFactory modifyResponseBodyFilter;@Resourceprivate ResponseRewriteFunction responseRewriteFunction;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();MediaType mediaType = request.getHeaders().getContentType();String method = request.getMethodValue();if (Objects.equals(HttpMethod.POST.name(), method) && MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {// 如果是POST请求,并且请求体是 application/json 格式return modifyResponseBodyFilter.apply(new ModifyResponseBodyGatewayFilterFactory.Config().setRewriteFunction(byte[].class, byte[].class, responseRewriteFunction)).filter(exchange, chain);} else {return filter(exchange, chain);}}@Overridepublic int getOrder() {return FilterPriorityConstant.RESPONSE_BODY_ENCRYPT_AND_ADD_SIGNATURE;}
}

ResponseRewriteFunction

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;/*** 修改响应体* @author weiheng* @date 2024-01-31**/
@Slf4j
@Component
public class ResponseRewriteFunction implements RewriteFunction<byte[], byte[]> {/*** Applies this function to the given arguments.** @param serverWebExchange the first function argument* @param bytes             the second function argument* @return the function result*/@Overridepublic Publisher<byte[]> apply(ServerWebExchange serverWebExchange, byte[] bytes) {ServerHttpRequest request = serverWebExchange.getRequest();HttpHeaders headers = request.getHeaders();String requestId = headers.getFirst(HttpConstant.HEADER_REQUEST_ID);JSONObject responseBody = JSON.parseObject(bytes);log.info(">>>>> requestId[{}], 原始响应体[{}]", requestId, responseBody);try {// TODO 在这里写自己的业务逻辑,比如响应体的 加密、加签// 测试,看客户端拿到的响应体中,是否有该属性 - 亲测OKresponseBody.put("traceResponse", RandomUtil.randomInt(1000, 9999));log.info(">>>>> requestId[{}], 修改后的响应体[{}]", requestId, responseBody);return Mono.just(responseBody.toString().getBytes());} catch (Exception e) {log.error(">>>>> requestId[{}], 修改响应体出错", requestId, e);throw e;}}}

把所有过滤的优先级统一管理

自定义 FilterPriorityConstant 常量类

PS:项目中有好几个过滤器,原先都是在各自的类中定义的优先级,这里统一放到常量里进行管理
这里并未展示真实项目中的所有过滤器,仅作示例

import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;/*** 过滤器优先级定义* @author weiheng* @date 2024-01-31**/
public class FilterPriorityConstant {/*** Useful constant for the highest precedence value.* @see java.lang.Integer#MIN_VALUE*/public static int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;/*** Useful constant for the lowest precedence value.* @see java.lang.Integer#MAX_VALUE*/public static int LOWEST_PRECEDENCE = Integer.MAX_VALUE;/** 商户权限校验 - 根据header的商户ID做接口权限校验 */public static int MERCHANT_AUTH_VALIDATE = 2;/** 请求体解密验签 */public static int REQUEST_BODY_DECRYPT_AND_SIGNATURE_VERIFICATION = 3;/** 响应体加密加签 value: -2 */public static int RESPONSE_BODY_ENCRYPT_AND_ADD_SIGNATURE = NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
}

以上请求体和响应体的修改,亲测OK

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/662343.shtml

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

相关文章

Windows - 防火墙 - 如何开启单个端口以供Web应用访问(以82端口为例) - 开启端口后还是访问失败了?

Windows - 防火墙 - 如何开启单个端口以供Web应用访问(以82端口为例) - 开启端口后还是访问失败了&#xff1f; 前言 在网上搜“防火墙开启某个端口”供其他机器访问&#xff0c;都是只讲到了“如何允许某个端口被访问”&#xff0c;而没有后续了。 我之前就遇到过这个问题&…

数据据库八之 视图、触发器、事务

【零】准备数据 【1】创建表 &#xff08;1&#xff09;部门表 d_id是部门的编号d_name是部门的名字 # 确保表不存在 drop table if exists department; # 创建表 create table department( d_id int auto_increment primary key, d_name varchar(6) )auto_increment 501 …

STM32-GPIO输入——按键检测

1 软件设计 为了使程序更有条例&#xff0c;方便移植在“工程模板”之上新建“bsp_key.c”及“bsp_key.h”文件&#xff0c;这些文件也可根据您的喜好命名&#xff0c;这 些文件不属于STM32HAL库的内容&#xff0c;是由我们自己根据应用需要编写的 1.1 编程要点 1&#xff…

2024-01-06-AI 大模型全栈工程师 - 机器学习基础

摘要 2024-01-06 阴 杭州 晴 本节简介: a. 数学模型&算法名词相关概念; b. 学会数学建模相关知识&#xff1b; c. 学会自我思考&#xff0c;提升认知&#xff0c;不要只会模仿&#xff1b; 课程内容 1. Fine-Tuning 有什么作用&#xff1f; a. 什么是模型训练&#xff…

Linux(一)

目录结构 【在 Linux 世界里&#xff0c;一切皆文件】 linux 的文件系统是采用级层式的树状目录结构&#xff1b; 序号名称介绍备注1/&#xff1a;根目录一般根目录下只存放目录&#xff0c;在 linux 下有且只有一个根目录&#xff0c;所有的东西都是从这里开始&#xff1b; 当…

机器学习1-种类及应用

机器学习主要包括以下几种主要的种类&#xff1a; 1. 监督学习&#xff08;Supervised Learning&#xff09; 在监督学习中&#xff0c;模型通过使用已标记的训练数据&#xff08;包括输入和对应的输出&#xff09;来学习预测目标变量。常见的任务包括回归和分类。应用&#xf…

Docker进阶篇-Docker微服务实战

一、通过IDEA新建一个普通微服务模块 1、建Moduel <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation…

Open CASCADE学习|曲面上一点的曲率及切平面

曲率&#xff08;Curvature&#xff09;是一个几何学的概念&#xff0c;用于描述一个物体的形状在某一点上的弯曲程度。在我们日常生活中&#xff0c;曲率与我们的生活息息相关&#xff0c;如道路的弯道、建筑物的拱形结构、自然界的山脉等等。了解曲率的概念和计算方法&#x…

开源软件,推动技术创新

文章目录 一、开源软件介绍二、推动技术创的影响力三、常见的开源软件四、应用案例五、存在安全风险 一、开源软件介绍 开源软件&#xff0c;也称为自由软件或公众可用的软件&#xff0c;是一种源代码公开的软件。与传统的商业软件不同&#xff0c;开源软件由社区驱动&#xf…

Unity中开发程序打包发布

添加ESC脚本 使用Unity打包发布的过程中&#xff0c;考虑到打开的程序会处于全屏界面&#xff0c;而此时我们又会有退出全屏的需求&#xff0c;因此需要添加ESC脚本&#xff0c;当我们单击ESC脚本的过程中&#xff0c;退出全屏模式。 在Assets/Scenes下&#xff0c;创建esc.cs…

Python之PySpark简单应用

文章目录 一、介绍1.准备工作2. 创建SparkSession对象&#xff1a;3. 读取数据&#xff1a;4. 数据处理与分析&#xff1a;5. 停止SparkSession&#xff1a; 二、示例1.读取解析csv数据2.解析计算序列数据map\flatmap 三、问题总结1.代码问题2.配置问题 一、介绍 PySpark是Apa…

Linux离线安装Telnet

前言&#xff1a;由于服务器部署在内网环境&#xff0c;不能yum安装 1.先从网站下载好我们所需要到的三个rpm包http://www.rpmfind.net/linux/rpm2html/search.php?queryxinetd&submitSearch...&system&arch image.png 三个依赖包分别是&#xff1a; -rw-r--r-- 1…

Invicti Professional v24.1.0.43434

新的安全检查 添加了对 dotCMS 的检查添加了对 Ultimate Member WordPress 插件的检查添加了新的 mXSS 模式添加了新签名来检测 JWK 改进 改进了针对 Weak Ciphers Enabled 漏洞的建议改进了对 swagger.json 漏洞的检测添加了对 AWS WAFv2 规则的支持改进了更多错误和警告消…

探索Gin框架:Golang使用Gin完成文件上传

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站https://www.captainbed.cn/kitie。 前言 在之前的文章中&#xff0c;我们讲解了Gin框架的快速入门使用&#xff0c;今天我们来聊聊如何使用…

golang的sqlite驱动不使用cgo实现 更换gorm默认的SQLite驱动

golang的sqlite驱动不使用cgo实现 更换gorm默认的SQLite驱动 最近在开发一个边缘物联网程序时使用Golang开发&#xff0c;用到GORM来操作SQLite数据库&#xff0c;GORM默认使用gorm.io/driver/sqlite这个库作为SQLite驱动&#xff0c;该库用CGO实现&#xff0c;在使用过程中遇…

OpenAI Gym 中级教程——多智能体系统

Python OpenAI Gym 中级教程&#xff1a;多智能体系统 在强化学习中&#xff0c;多智能体系统涉及到多个智能体相互作用的情况。在本篇博客中&#xff0c;我们将介绍如何在 OpenAI Gym 中构建和训练多智能体系统&#xff0c;并使用 Multi-Agent Deep Deterministic Policy Gra…

Unity_Shader

Unity_Shader 目录 Unity_Shader 带着问题开始: Shader Graph 开始吧!

【NLP冲吖~】一、朴素贝叶斯(Naive Bayes)

0、朴素贝叶斯法 朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法。对于给定的训练数据集&#xff0c;首先基于特征条件独立假设学习输入输出的联合概率分布&#xff0c;然后基于此模型&#xff0c;对给定的输入 x x x&#xff0c;利用贝叶斯定理求出后验概率最大的…

【Spring Boot 3】应用启动执行特定逻辑

【Spring Boot 3】应用启动执行特定逻辑 背景介绍开发环境开发步骤及源码工程目录结构总结背景 软件开发是一门实践性科学,对大多数人来说,学习一种新技术不是一开始就去深究其原理,而是先从做出一个可工作的DEMO入手。但在我个人学习和工作经历中,每次学习新技术总是要花…

element-ui button 仿写 demo

基于上篇 button 源码分享写了一个简单 demo&#xff0c;在写 demo 的过程中&#xff0c;又发现了一个小细节&#xff0c;分享一下&#xff1a; 1、组件部分&#xff1a; <template><buttonclass"yss-button"click"handleClick":class"[ty…