# 优雅的实现spring-cloud-gateway修改请求和响应体

## 引言
最近公司的微服务越来越多,管理起来比较麻烦,决定引入微服务网关。咨询了百度老师发现Spring Cloud gateway功能已经比较完善,性能也还很不错。决定就是它了。
建工程,加依赖,写配置,一切看起来都那么的简单加顺利。
只是某天某人突然提了一个需求,为了方便调试让我增加1个可以查看每个post+json+restApi请求的请求头,请求体,响应头,响应体的功能。
我想这还不简单嘛,直接加个全局过滤器就搞定了。
赶紧加班加点写了一个过滤器实现GlobalFilter和Ordered接口,在接口中接收获取请求和响应的信息通过日志打印出来就OK了吧。

理想是美好的,现实是残酷的。才发现遇到了不好处理的问题。
## 遇到的问题

* 1.ServerHttpRequest请求对象的请求体只能获取一次,一旦获取了就不能继续往下传递。
Flux<DataBuffer> getBody();

* 2.请求体方法返回的是基于webFlux响应式的函数结构,特别难处理。

* 3.ServerHttpResponse响应体根本就没有获取响应体的方法。

* 4.系统提供的修改请求体和响应体的类只实现了GatewayFilter过滤器,无法对全局进行监控而且还只能通过Java DSL进行配置.不可能每增加一个服务都修改代码。我需要的是全局过滤器。
ModifyRequestBodyGatewayFilterFactory  系统提供的修改请求体的类
ModifyResponseBodyGatewayFilterFactory 系统提供的修改响应体的类
## 解决方案

查询了网上很多的实现方法始终都不能完美的处理。不是丢包就是根本获取不到数据。

基本思想:代理实现ServerHttpRequest和ServerHttpResponse类,自己先处理请求和响应体,然后重新生成1个新的数据返回出去。网上大多数都是这种方案。

想起组件本身提供的有ModifyRequestBodyGatewayFilterFactory和ModifyResponseBodyGatewayFilterFactory来对请求和响应体进行修改。

不过研究之后发现。这两个类生成的是GatewayFilter路由过滤器,而我们需要GlobalFilter(全局过滤器)

要么把这个类拷贝出来重新自己实现1个基于全局过滤器的版本。不过这样代码改动比较大,而且重复严重,哪怕是跟组件本身的代码重复这也是不允许的。不提倡重复造轮子。

想到 GatewayFilter和GlobalFilter过滤器除了实现的接口不同,它要实现的方法签名都是相同的。既然官方已经实现了GatewayFilter版本的,我应该可以代理直接使用。

设计思路:提供一个类专门用来处理请求和响应。可以实现接口RewriteFunction,为了统一处理,输入输出参数都用byte[].代码如下:

```

import java.util.stream.Collectors;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;
/**
* 泛型参数1:源请求体类,源响应体
* 泛型参数2:新请求体类,新响应体
* @author cqyhm
*
*/
@Slf4j
public class BodyRewrite implements RewriteFunction<byte[], byte[]> {

/**
* 在执行全局请求或响应的过滤器的时候会执行该方法,并把请求体或响应体传递进来。
* @param exchange 网关处理上下文
* @param body 源请求或响应体
* @return 返回处理过的请求体或响应体
*/
@Override
public Publisher<byte[]> apply(ServerWebExchange exchange, byte[] body) {
//如果路由没有完成应该是请求过滤器执行
if(!ServerWebExchangeUtils.isAlreadyRouted(exchange)) {
exchange.getAttributes().put("request_key", new String(body));  //保存请求体到全局上下文中
exchange.getAttributes().put("startTime", System.currentTimeMillis()); //保存启动时间到上下中
//TODO 可以在这里对请求体进行修改
} else { //已经路由应该是响应过滤器执行
//TODO 可以在这里对响应体进行修改
response(exchange, body);
}
return Mono.just(body);
}
/**
*  打印输出响应的参数,请求体,响应体,请求头部,响应头部,请求地址,请求方法等。
*  @param exchange 网关处理上下文
*  @param body 源请求或响应体
*  @return 返回处理过的请求体或响应体
*/
public byte[] response(ServerWebExchange exchange,  byte[] responseBody) {
try {
ServerHttpRequest request=exchange.getRequest();
ServerHttpResponse response=exchange.getResponse();
String requestbody=exchange.getAttribute("request_key");
Long startTime=exchange.getAttributeOrDefault("startTime", 0L);
Long time=System.currentTimeMillis()-startTime;
boolean flag=MediaType.APPLICATION_JSON.isCompatibleWith(response.getHeaders().getContentType());
//responseBody=objectMapper.writeValueAsString(MessageBox.ok());
log.info("\n[{}]请求地址:\n\t{} {}\n[{}]请求头部:\n{}\n[{}]路径参数:\n{}\n[{}]请求参数:\n{}"
+ "\n[{}]响应头部:\n{}\n[{}]响应内容:\n\t{}\n[{}]执行时间[{}]毫秒",
request.getId(), request.getMethod(), request.getPath(),
request.getId(), headers(request.getHeaders()),
request.getId(), request(request),
request.getId(), requestbody,
request.getId(), headers(response.getHeaders()),
request.getId(), flag ? new String(responseBody) : "非JSON字符串不显示",
request.getId(),    time);
//TODO 可以对响应体进行修改
return responseBody;
} catch (Exception e) {
throw new RuntimeException("响应转换错误");
} finally {
exchange.getAttributes().remove("request_key");
exchange.getAttributes().remove("startTime");
}
}

public String headers(HttpHeaders headers) {
return headers.entrySet().stream()
.map(entry -> "\t" + entry.getKey() + ": [" + String.join(";", entry.getValue()) + "]")
.collect(Collectors.joining("\n"));
}
/**
* 处理其它get请求参数
*/
public String request(ServerHttpRequest request) {
String params=request.getQueryParams().entrySet().stream()
.map(entry -> "\t" + entry.getKey() + ": [" + String.join(";", entry.getValue()) + "]")
.collect(Collectors.joining("\n"));
return params;
}
}
```
业务处理逻辑写好之后,剩下的就是配置了。我这里只是把参数打印日志没有做处理,你甚至可以在这里修改请求体和响应体。
代理ModifyRequestBodyGatewayFilterFactory和ModifyResponseBodyGatewayFilterFactory类中的GatewayFilter来定义GlobalFilter过滤器。

这里是使用内部类直接实现的,但是指定GlobalFilter的顺序有问题导致无法监控,最好的办法还是单独设计两个GlobalFilter过滤器类。

查看代码:
```
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.web.server.ServerWebExchange;

import reactor.core.publisher.Mono;

@Configuration
@ConditionalOnProperty(prefix = "logging.filter", name = "enabled", havingValue = "true", matchIfMissing = false)
public class CustomGatewayConfig {

@Bean
public BodyRewrite bodyRewrite() {
return new BodyRewrite();
}
/**
* 定义全局拦截器拦截请求体
*/
@Bean
@Order(NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 2)   //指定顺序必须在之前
public GlobalFilter requestFilter(ModifyRequestBodyGatewayFilterFactory modifyRequestBody) {
GatewayFilter delegate=modifyRequestBody.apply(new ModifyRequestBodyGatewayFilterFactory.Config()
.setRewriteFunction(byte[].class, byte[].class, bodyRewrite()));
return new GlobalFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return delegate.filter(exchange, chain);
}
};
}
/**
* 定义全局拦截器拦截响应体
*/
@Bean
@Order(NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 2) //指定顺序必须在之前
public GlobalFilter responseFilter(ModifyResponseBodyGatewayFilterFactory modifyResponseBody) {
GatewayFilter delegate=modifyResponseBody.apply(new ModifyResponseBodyGatewayFilterFactory.Config()
.setRewriteFunction(byte[].class, byte[].class, bodyRewrite()));
return new GlobalFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return delegate.filter(exchange, chain);
}
};
}
}
```
关键的地方就在于官方提供的类ModifyResponseBodyGatewayFilterFactory提供的方法apply
```
public GatewayFilter apply(Config config);
```
该方法返回的是GatewayFilter我们正好可以利用它的方法filter方法
```
public Mono<Void> filter(ServerWebExchange exchange,GatewayFilterChain chain)
```
我们只需要构造1个它的参数config就好了。分析源代码发现它的参数有个setRewriteFunction函数

```
/**
* @param inClass   请求体的原始数据类型,我们统一使用byte[]
* @param outClass 请求体变更过后的数据类型,我们统一使用byte[]
* @param rewriteFunction 对请求体进行处理的类。就是我们前面定义的BodyRewrite的对象
*/
public <T, R> Config setRewriteFunction(Class<T> inClass, Class<R> outClass, RewriteFunction<T, R> rewriteFunction)

```
执行了ModifyResponseBodyGatewayFilterFactory的apply方法传入config参数能够得到1个唯1个GatewayFilter对象
```
GatewayFilter delegate=modifyResponseBody.apply(new ModifyResponseBodyGatewayFilterFactory.Config()
.setRewriteFunction(byte[].class, byte[].class, bodyRewrite()));

```
只需要在我们自定义的 GlobalFilter过滤器中调用delegate.filter(exchange, chain)方法
```
return new GlobalFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return delegate.filter(exchange, chain);
}
};
```

以上简化的方法设置可能存在在某些版本上无法监控的问题,只需要把请求过滤器和响应过滤器完全独立出来开发就可以了。具体为什么,我还没来得及分析。以下是请求过滤器。响应过滤器类似。

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;

import reactor.core.publisher.Mono;

/**
* 全局请求体过滤器
* @author cqyhm
*
*/
public class RequestBodyFilter implements GlobalFilter, Ordered {

private GatewayFilter delegate;
public RequestBodyFilter(ModifyRequestBodyGatewayFilterFactory modifyRequestBody, BodyRewrite bodyRewrite) {
this.delegate=modifyRequestBody.apply(new ModifyRequestBodyGatewayFilterFactory.Config()
.setRewriteFunction(byte[].class, byte[].class, bodyRewrite));
}

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return delegate.filter(exchange, chain);
}

@Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 2;
}
}
```

至于响应过滤器跟这个差不多,一葫芦画瓢很快就能写1个出来。

/**
* 全局响应体过滤器
*/
public class ResponseBodyFilter implements GlobalFilter, Ordered  {

private GatewayFilter delegate;
public ResponseBodyFilter(ModifyResponseBodyGatewayFilterFactory modifyResponseBody, BodyRewrite bodyRewrite) {
this.delegate=modifyResponseBody.apply(new ModifyResponseBodyGatewayFilterFactory.Config()
.setRewriteFunction(byte[].class, byte[].class, bodyRewrite));
}

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return delegate.filter(exchange, chain);
}

@Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 2;
}
}

然后注册这两个Bean基本上都可以了。
这种方案应该是最将简便的,最优雅的,只需要实现我们的业务类BodyRewrite其它都直接使用Java config配置,对系统改动小,侵入性低。

这种方案目前正在验证当中,有什么问题欢迎交流。
好了,这就大功告成了。

# 优雅的实现spring-cloud-gateway修改请求和响应体## 引言 最近公司的微服务越来越多,管理起来比较麻烦,决定引入微服务网关。咨询了百度老师发现Spring Cloud gateway功能已经比较完善,性能也还很不错。决定就是它了。 建工程,加以来,写配置,一切看起来都那么都是很简单加顺利。 只是某天某人突然提了一个需求,为了方便调试让我增加1个可... mvn clean package # docker build命令用于使用Dockerfile创建镜像 # 语法:docker build [OPTIONS] PATH | URL | - # -f:指定要使用的Dockerfile路径 # -t:镜像的名字及标签,通常是name:tag或name格式 # .代表本次执行的上下文路径 docker build -f ./Dockerfile . -t emily gateway :1
通过过滤器和熔断器,可以在 Spring Cloud Gateway 中创建灵活的路由规则, 实现 请求 的预处理、后处理和熔断保护等功能。过滤器用于对 请求 进行预处理或后处理,如添加 请求 头、鉴权、限流等功能。熔断器用于监控和保护故障或不可用的服务,并提供备选的回退逻辑。 本文适用于具备一定 Spring Cloud和 Spring Cloud Gateway 知识的开发人员和架构师。读者应具备Java编程和 Spring 框架基础,并对微服务架构和API网关有一定了解。 使用过滤器和熔断器为 Spring Cloud Gateway 创建灵活的路由规则适用于以下场景: 请求 预处理:通过过滤器对 请求 进行预处理,如鉴权、 请求 参数校验、 请求 日志记录等,以提高 请求 的安全性和减轻后端服务的负担。 请求 后处理:通过过滤器对 响应 进行后处理,如添加 响应 头、格式化 响应 结果、异常处理等,以满足客户端的需求和统一 响应 格式。 熔断保护:通过熔断器对故障或不可用的服务进行监控和保护,及时发现故障并提供备选的回退逻辑,以避免故障扩散和提高系统的可靠性。
基于 Spring Cloud Gateway 的网关使用说明 通过对Http 请求 的拦截,根据接口配置数据 实现 对接口访问的限流和身份验证及鉴权功能。同时同时在信息级别日志中输出 请求 参数, 返回 数据以及接口 响应 时间。网关在转发 请求 前,将会添加以下 请求 头: requestId 请求 ID,用于调用拨号跟踪 客户端指纹,用于鉴别来源 loginInfo 包含应用ID,租户ID,用户ID等用户关键信息 网关的部分功能依赖于其他项目的配合 对于包含URL路径参数的接口,仅支持相对低效的正则匹配模式。所以请避免使用包含路径参数的URL。 请求 URL如未如仍替换匹配到接口,则再次从Redis中加载数据更新正则匹配表,再进行第二。次正则匹配。如二次匹配失败,则 返回 URL不存在的错误。 相关代码如下: InterfaceConfig config = getConfig(me
运行3个应用程序类作为 spring boot应用程序。 Zuul Gateway Application :在端口8080上运行zuul FooApplication :在端口9080上托管/foo服务 BarApplication :在端口7080上托管具有不同 实现 的/foo服务 实现 了许多过滤器: AddResponseHeader Filter :向 响应 中添加一个随机的X-Foo标头 ModifyResponseBody Filter :使用RequestContext.setResponseBody(String)向 响应 添加前缀。 仅在未设置service查询参数的情况下运行。 ModifyResponseDataStream Filter :使用RequestContext.setResponseDataStream(InputStream)在 响应 中添加前缀。 仅在设置了service查询参数时运行。 PrefixRequestEntity Filter :通过使用 请求 包装器和RequestContext.setRequest()将前缀添加到 请求 。 仅在 有个金融类项目,客户对系统安全性比较看重,要求接口 请求 响应 的数据,都要按特定要求进行加密,防止敏感业务数据被抓包截取。 现在设计流程已经拟定,客户端也解决了如何解密 响应 数据。服务端还没 实现 响应 数据进行加密。 抽象出来,本质上要解决的问题是,如何 修改 响应 数据。 项目已经使用了 Spring Cloud Gateway 技术, 响应 数据可以在网关拦截。 现在的问题是,如何 修改 响应 数据。 关键词: spring cloud gateway modify response body
webflux - 统一 响应 ModifyResponseBody Gateway Filter Factory webflux提供了对 响应 结果的 修改 的过滤器 ModifyResponseBody Gateway Filter Factory, 如果需要 返回 自定义格式,只需要重写指定 ModifyResponseBody Gateway Filter Factory.Config中的 rewriteFunction ModifyResponseBody Gateway Filter Factory.Config: public s
各种业务场景,我们可能需要在网关中 修改 请求 body或 响应 body( 修改 请求 body请看 Spring Cloud gateway request的body验证或 修改 ),下文参考 spring 提供的ModifyResponseBody Gateway Filter Factory, 实现 自己的拦截器。 直接贴代码 package com.tuzhanai. gateway . filter .facotry;...
1.无脑的 实现 了一种自己写一个过滤器, 修改 返回 此方法需注意fluxBody.buffer().map(dataBuffers -&gt; {})中的buffer方法调用,不然有些 返回 会分段传输的。 package com.wukala. gateway server. filter ; import cn.wukala.common.commonbase.result.WrapMapper...
本文介绍了如何使用 Spring Cloud Gateway 构建一个简单的微服务网关。我们将讨论网关的基本概念和功能,并提供一个简单示例来演示如何配置和使用 Spring Cloud Gateway 。 本文适用于具有一定 Spring 框架和微服务基础知识的开发人员和架构师。读者应该对微服务架构、路由、过滤和负载均衡等概念有基本的了解。 Spring Cloud Gateway 适用于构建和管理大规模微服务架构中的网关服务。它可以用于以下场景: 1.路由管理:将外部 请求 路由到不同的微服务实例,根据 请求 路径、主机名等条件进行路由转发。 2.过滤和增强功能:通过过滤器添加、 修改 或删除 请求 / 响应 的头信息、参数、主 内容等。 3.负载均衡:根据负载均衡策略将 请求 分发到不同的微服务实例,提高系统的吞吐量和可扩展性。 4.安全性和认证:通过集成认证和授权服务, 实现 请求 的安全验证和访问控制。 5.监控和统计:收集网关 请求 的指标数据,用于监控、分析和故障排除。
import org.apache.commons.lang3.StringUtils; import org.apache.dubbo.config.annotation.DubboReference; import org.reactivestreams.Publisher; import org. spring framework.cloud. gateway . filter .factory.rewrite.RewriteFunction; import org. spring frame
spring cloud gateway Greenwich modify request body 首先 spring cloud gateway 的官方文档真的很坑,基本都是基于yml的配置,这种程度的配置在实际的生产环境下是无法使用的,所以我们需要对各个过滤器进行编程的方式重写,主要是参考 spring 已有的 实现 类,加以改造。 spring cloud gateway greenwich版本对读取...
https://www.cnblogs.com/fdzang/p/11812348.html 1. Gateway 的拦截器 我们要在项目中 实现 一个拦截器,需要继承两个类:Global Filter , Ordered Global Filter :全局过滤拦截器,在 gateway 中已经有部分 实现 ,具 参照:https://www.cnblogs.com/liukaifeng/p/10055862.html Ordered:拦截器的顺序,不多说 于是一个简单的拦截器就有了 @Slf4j @Component
Spring Cloud Alibaba Sentinel Gateway 是基于 Spring Cloud Gateway 和Sentinel的网关组件。它提供了一种通过Sentinel进行流量控制和熔断降级的方式来保护和管理微服务的能力。 使用 Spring Cloud Alibaba Sentinel Gateway ,您可以通过配置规则来限制对微服务的访问流量,包括QPS(每秒查询率)、线程数、并发连接数等。当流量超过配置的阈值时,系统可以自动触发限流措施,以保护下游服务免受过载的影响。 此外,Sentinel Gateway 还提供了熔断降级的功能。当下游服务出现异常或 响应 时间超过阈值时,可以通过配置规则来触发熔断操作,避免错误的 请求 继续传递下去,从而保护整个系统的稳定性。 Spring Cloud Alibaba Sentinel Gateway 是一个强大的工具,能够提供微服务网关层面的流量控制和熔断降级功能,帮助开发者构建健壮可靠的分布式系统。
YinChenLeilei: [code=java] public Publisher<byte[]> apply(ServerWebExchange exchange, byte[] body) { //TODO 可以在这里对请求体进行修改 } else { //已经路由应该是响应过滤器执行 //TODO 可以在这里对响应体进行修改 [/code] 为啥我这里body一直为null 优雅的实现spring-cloud-gateway修改请求和响应体 zhangyunxin-java: <= 0才行,get请求有些是-1