# 优雅的实现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 -> {})中的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:
优雅的实现spring-cloud-gateway修改请求和响应体
zhangyunxin-java: