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

news/2024/12/29 12:44:42/

前言

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

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.ppmy.cn/news/1340537.html

相关文章

2024美赛数学建模F题思路分析 - 减少非法野生动物贸易

1 赛题 问题F&#xff1a;减少非法野生动物贸易 非法的野生动物贸易会对我们的环境产生负面影响&#xff0c;并威胁到全球的生物多样性。据估计&#xff0c;它每年涉及高达265亿美元&#xff0c;被认为是全球第四大非法交易。[1]你将开发一个由数据驱动的5年项目&#xff0c;…

双非本科准备秋招(14.1)—— 力扣刷题

今天做两个有点难度的题。 1、295. 数据流的中位数 手写堆实现&#xff1a; 加入元素&#xff1a; 如何维护一个中位数&#xff1f;我们考虑一下堆的特点&#xff0c;大顶堆堆顶是一个最大值&#xff0c;小顶堆堆顶是一个最小值&#xff0c;那么&#xff0c;如果我们可以把数…

jenkins 下载插件sentry-cli失败 证书过期

现状 npm set ENTRYCLI_CDNURLhttps://cdn.npm.taobao.org/dist/sentry-cli npm set sentrycli_cdnurlhttps://cdn.npm.taobao.org/dist/sentry-cli 原因是npm原域名停止解析&#xff0c;在访问上面sentry-cli的cdn资源的时候 证书过期无法下载。 解决&#xff1a; 替换证书过期…

代码随想录算法训练营29期Day37|LeetCode 738,968

文档讲解&#xff1a;单调递增的数字 监控二叉树 贪心算法总结 738.单调递增的数字 题目链接&#xff1a;https://leetcode.cn/problems/monotone-increasing-digits/description/ 思路&#xff1a; 题目要求小于等于N的最大单调递增的整数&#xff0c;那么拿一个两位的数字…

element-ui icon 组件源码分享

今日简单分享 element-ui 源码中的 icon 组件&#xff0c;主要从以下两个方面来分享&#xff1a; 一、源码中 icon 设计思想是什么呢&#xff1f;主要从页面结构、数据、 icon 样式三个方面来分享。 1.1 源码中 icon 组件的页面结构&#xff0c;可以在 package 目录下找到 ico…

【Node系列】REPL详解

文章目录 一、REPL介绍二、REPL案例三、REPL命令四、node介绍五、相关链接 一、REPL介绍 Node.js REPL&#xff08;Read-Eval-Print Loop&#xff09;是一个交互式环境&#xff0c;允许用户在命令行中直接输入JavaScript代码并立即看到结果。REPL是Node.js的一个重要组成部分&…

2024美赛A题思路/代码:资源可用性和性别比例

美赛直播b站&#xff0c;提前关注&#xff1a;川川菜鸟 美赛辅导预定&#xff1a;美赛服务 去年美赛A题作品&#xff1a;2023美赛A题 题目 背景 尽管一些动物物种不属于通常的雄性或雌性&#xff0c;大多数物种在出生时要么显著地为雄性&#xff0c;要么为雌性。虽然许多物…

Linux信号详解~

目录 前言 一、初识信号 二、信号的概念 三、信号的发送与捕捉 3.1 信号的发送 3.1.1 kill 命令 3.1.2 kill 函数 3.1.3 raise函数 3.1.4 abort函数 3.2 信号的捕捉 3.2.1 signal函数 3.2.2 sigaction函数 3.2.3 图示 四、信号的产生 4.1 硬件异常产生信号 4.2 …