在接入Spring-Cloud-Gateway时,可能有需求进行缓存Json-Body数据或者Form-Urlencoded数据的情况。

由于Spring-Cloud-Gateway是以WebFlux为基础的响应式架构设计,所以在原有Zuul基础上迁移过来的过程中,传统的编程思路,并不适合于Reactor Stream的开发。

网络上有许多缓存案例,但是在测试过程中出现各种Bug问题,在缓存Body时,需要考虑整体的响应式操作,才能更合理的缓存数据

下面提供缓存Json-Body数据或者Form-Urlencoded数据的具体实现方案,该方案经测试,满足各方面需求,以及避免了网络上其他缓存方案所出现的问题

定义一个GatewayContext类,用于存储请求中的数据

import org.springframework.util.MultiValueMap;

public class GatewayContext {

public static final String CACHE_GATEWAY_CONTEXT = "cacheGatewayContext";

/**

* cache json body

*/

private String cacheBody;

/**

* cache formdata

*/

private MultiValueMap formData;

/**

* cache reqeust path

*/

private String path;

public String getCacheBody() {

return cacheBody;

}

public void setCacheBody(String cacheBody) {

this.cacheBody = cacheBody;

}

public MultiValueMap getFormData() {

return formData;

}

public void setFormData(MultiValueMap formData) {

this.formData = formData;

}

public String getPath() {

return path;

}

public void setPath(String path) {

this.path = path;

}

}

1 . 该示例只支持缓存下面3种MediaType

APPLICATION_JSON--Json数据

APPLICATION_JSON_UTF8--Json数据

APPLICATION_FORM_URLENCODED--FormData表单数据

2 . 经验总结:

在缓存Body时,不能够在Filter内部直接进行缓存,需要按照响应式的处理方式,在异步操作路途上进行缓存Body,由于Body只能读取一次,所以要读取完成后要重新封装新的request和exchange才能保证请求正常传递到下游

在缓存FormData时,FormData也只能读取一次,所以在读取完毕后,需要重新封装request和exchange,这里要注意,如果对FormData内容进行了修改,则必须重新定义Header中的content-length已保证传输数据的大小一致

package com.weiresearch.idss.weiark.gateway.LogReader;

import java.io.UnsupportedEncodingException;

import java.net.URLEncoder;

import java.nio.charset.Charset;

import java.nio.charset.StandardCharsets;

import java.util.List;

import java.util.Map;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;

import org.springframework.cloud.gateway.filter.GlobalFilter;

import org.springframework.core.io.ByteArrayResource;

import org.springframework.core.io.buffer.DataBuffer;

import org.springframework.core.io.buffer.DataBufferUtils;

import org.springframework.core.io.buffer.NettyDataBufferFactory;

import org.springframework.http.HttpHeaders;

import org.springframework.http.HttpMethod;

import org.springframework.http.MediaType;

import org.springframework.http.codec.HttpMessageReader;

import org.springframework.http.server.reactive.ServerHttpRequest;

import org.springframework.http.server.reactive.ServerHttpRequestDecorator;

import org.springframework.stereotype.Component;

import org.springframework.util.MultiValueMap;

import org.springframework.web.reactive.function.server.HandlerStrategies;

import org.springframework.web.reactive.function.server.ServerRequest;

import org.springframework.web.server.ServerWebExchange;

import io.netty.buffer.ByteBufAllocator;

import reactor.core.publisher.Flux;

import reactor.core.publisher.Mono;

// https://segmentfault.com/a/1190000017898354

@Component

public class GatewayContextFilter

implements GlobalFilter {

/**

* default HttpMessageReader

*/

private static final List> messageReaders =

HandlerStrategies.withDefaults().messageReaders();

private Logger log = LoggerFactory.getLogger(GatewayContextFilter.class);

@Override

public Mono filter(

ServerWebExchange exchange,

GatewayFilterChain chain) {

/**

* save request path and serviceId into gateway context

*/

ServerHttpRequest request = exchange.getRequest();

String path = request.getPath().pathWithinApplication().value();

GatewayContext gatewayContext = new GatewayContext();

gatewayContext.setPath(path);

/**

* save gateway context into exchange

*/

exchange.getAttributes().put(GatewayContext.CACHE_GATEWAY_CONTEXT,

gatewayContext);

HttpHeaders headers = request.getHeaders();

MediaType contentType = headers.getContentType();

log.info("start-------------------------------------------------");

log.info("HttpMethod:{},Url:{}", request.getMethod(),

request.getURI().getRawPath());

if (request.getMethod() == HttpMethod.GET) {

log.info("end-------------------------------------------------");

}

if (request.getMethod() == HttpMethod.POST) {

Mono voidMono = null;

if (MediaType.APPLICATION_JSON.equals(contentType)

|| MediaType.APPLICATION_JSON_UTF8.equals(contentType)) {

voidMono =

readBody(exchange, chain, gatewayContext);

}

if (MediaType.APPLICATION_FORM_URLENCODED.equals(contentType)) {

voidMono =

readFormData(exchange, chain, gatewayContext);

}

return voidMono;

}

/* log.debug(

"[GatewayContext]ContentType:{},Gateway context is set with {}",

contentType, gatewayContext);*/

return chain.filter(exchange);

}

/**

* ReadFormData

*

* @param exchange

* @param chain

* @return

*/

private Mono readFormData(

ServerWebExchange exchange,

GatewayFilterChain chain,

GatewayContext gatewayContext) {

final ServerHttpRequest request = exchange.getRequest();

HttpHeaders headers = request.getHeaders();

return exchange.getFormData()

.doOnNext(multiValueMap -> {

gatewayContext.setFormData(multiValueMap);

log.info("Post x-www-form-urlencoded:{}",

multiValueMap);

log.info(

"end-------------------------------------------------");

})

.then(Mono.defer(() -> {

Charset charset = headers.getContentType().getCharset();

charset = charset == null ? StandardCharsets.UTF_8 : charset;

String charsetName = charset.name();

MultiValueMap formData =

gatewayContext.getFormData();

/**

* formData is empty just return

*/

if (null == formData || formData.isEmpty()) {

return chain.filter(exchange);

}

StringBuilder formDataBodyBuilder = new StringBuilder();

String entryKey;

List entryValue;

try {

/**

* repackage form data

*/

for (Map.Entry> entry : formData

.entrySet()) {

entryKey = entry.getKey();

entryValue = entry.getValue();

if (entryValue.size() > 1) {

for (String value : entryValue) {

formDataBodyBuilder.append(entryKey).append("=")

.append(

URLEncoder.encode(value, charsetName))

.append("&");

}

} else {

formDataBodyBuilder

.append(entryKey).append("=").append(URLEncoder

.encode(entryValue.get(0), charsetName))

.append("&");

}

}

} catch (UnsupportedEncodingException e) {

// ignore URLEncode Exception

}

/**

* substring with the last char '&'

*/

String formDataBodyString = "";

if (formDataBodyBuilder.length() > 0) {

formDataBodyString = formDataBodyBuilder.substring(0,

formDataBodyBuilder.length() - 1);

}

/**

* get data bytes

*/

byte[] bodyBytes = formDataBodyString.getBytes(charset);

int contentLength = bodyBytes.length;

ServerHttpRequestDecorator decorator =

new ServerHttpRequestDecorator(

request) {

/**

* change content-length

*

* @return

*/

@Override

public HttpHeaders getHeaders() {

HttpHeaders httpHeaders = new HttpHeaders();

httpHeaders.putAll(super.getHeaders());

if (contentLength > 0) {

httpHeaders.setContentLength(contentLength);

} else {

httpHeaders.set(HttpHeaders.TRANSFER_ENCODING,

"chunked");

}

return httpHeaders;

}

/**

* read bytes to Flux

*

* @return

*/

@Override

public Flux getBody() {

return DataBufferUtils

.read(new ByteArrayResource(bodyBytes),

new NettyDataBufferFactory(

ByteBufAllocator.DEFAULT),

contentLength);

}

};

ServerWebExchange mutateExchange =

exchange.mutate().request(decorator).build();

/* log.info("[GatewayContext]Rewrite Form Data :{}",

formDataBodyString);*/

return chain.filter(mutateExchange);

}));

}

/**

* ReadJsonBody

*

* @param exchange

* @param chain

* @return

*/

private Mono readBody(

ServerWebExchange exchange,

GatewayFilterChain chain,

GatewayContext gatewayContext) {

/**

* join the body

*/

return DataBufferUtils.join(exchange.getRequest().getBody())

.flatMap(dataBuffer -> {

/*

* read the body Flux, and release the buffer

* //TODO when SpringCloudGateway Version Release To G.SR2,this can be update with the new version's feature

* see PR https://github.com/spring-cloud/spring-cloud-gateway/pull/1095

*/

byte[] bytes = new byte[dataBuffer.readableByteCount()];

dataBuffer.read(bytes);

DataBufferUtils.release(dataBuffer);

Flux cachedFlux = Flux.defer(() -> {

DataBuffer buffer =

exchange.getResponse().bufferFactory().wrap(bytes);

DataBufferUtils.retain(buffer);

return Mono.just(buffer);

});

/**

* repackage ServerHttpRequest

*/

ServerHttpRequest mutatedRequest =

new ServerHttpRequestDecorator(exchange.getRequest()) {

@Override

public Flux getBody() {

return cachedFlux;

}

};

/**

* mutate exchage with new ServerHttpRequest

*/

ServerWebExchange mutatedExchange =

exchange.mutate().request(mutatedRequest).build();

/**

* read body string with default messageReaders

*/

return ServerRequest.create(mutatedExchange, messageReaders)

.bodyToMono(String.class)

.doOnNext(objectValue -> {

log.info("PostBody:{}", objectValue);

log.info(

"end-------------------------------------------------");

gatewayContext.setCacheBody(objectValue);

/* log.debug("[GatewayContext]Read JsonBody:{}",

objectValue);*/

}).then(chain.filter(mutatedExchange));

});

}

}

在后续Filter中,可以直接从ServerExchange中获取GatewayContext,就可以获取到缓存的数据,如果需要缓存其他数据,则可以根据自己的需求,添加到GatewayContext中即可

GatewayContext gatewayContext = exchange.getAttribute(GatewayContext.CACHE_GATEWAY_CONTEXT);

gateway请求拦截_spring cloud gateway 拦截request Body相关推荐

  1. gateway中的局部过滤器_Spring Cloud Gateway中的过滤器工厂:重试过滤器

    Spring Cloud Gateway基于Spring Boot 2,是Spring Cloud的全新项目,该项目提供了一个构建在Spring 生态之上的API网关.本文基于的Spring Clou ...

  2. csrf token invalid什么意思_Spring Cloud Gateway 实现Token校验

    在我看来,在某些场景下,网关就像是一个公共方法,把项目中的都要用到的一些功能提出来,抽象成一个服务.比如,我们可以在业务网关上做日志收集.Token校验等等,当然这么理解很狭隘,因为网关的能力远不止如 ...

  3. spring gateway 限流持久化_Spring Cloud Gateway 扩展支持动态限流

    之前分享过 一篇 <Spring Cloud Gateway 原生的接口限流该怎么玩>, 核心是依赖Spring Cloud Gateway 默认提供的限流过滤器来实现 原生Request ...

  4. angularjs 让当前路由重新加载_Spring Cloud Gateway的动态路由怎样做?集成Nacos实现很简单...

    一.说明 网关的核心概念就是路由配置和路由规则,而作为所有请求流量的入口,在实际生产环境中为了保证高可靠和高可用,是尽量要避免重启的,所以实现动态路由是非常有必要的:本文主要介绍 Spring Clo ...

  5. spring gateway 限流持久化_Spring Cloud Gateway网关如何快速实施限流方案?-Part 6

    熔断降级 在分布式系统中,网关作为流量的入口,大量请求进入网关,向后端远程系统或服务发起调用,后端服务不可避免的会产生调用失败(超时或者异常),失败时不能让请求堆积在网关上,需要快速失败并返回回去,这 ...

  6. gatewayfilter详解_Spring Cloud Gateway 之 Filter

    简介 网关经常需要对路由请求进行过滤,进行一些操作,如鉴权之后构造头部之类的,过滤的种类很多,如增加请求头.增加请求 参数 .增加响应头和断路器等等功能,这就用到了Spring Cloud Gatew ...

  7. cloub spring 拦截器_Spring Cloud Gateway之全局异常拦截器

    /** * @version 2019/8/14 * @description: 异常拦截器 * @modified: */ @Slf4j public class JsonExceptionHand ...

  8. zuul配置请求拦截_spring cloud网关通过Zuul RateLimit 限流配置

    在平常项目中为了防止一些没有token访问的API被大量无限的调用,需要对一些服务进行API限流.就好比拿一些注册或者发验证码的一些接口,如果被恶意无限的调用,多少会造成一些费用的产生,发短信或者邮件 ...

  9. ribbon设置权重_spring cloud gateway+ribbon 组合指定版本权重分流(简易灰度发布实现)...

    0.参考 1.思路 改造gataway的### Weight Route Predicate Factory,在指定权重的同时指定每个对应权重的服务的版本号.主要需要改写的地方是要在分流之后,将版本号 ...

最新文章

  1. python绘制3d图形-万万没想到,Python 竟能绘制出如此酷炫的三维图
  2. ios php 推送测试工具,IOS PushNotification - IOS推送测试 PHP 版
  3. php - Api 接口写法规范和要求
  4. jQuery EasyUI datagrid本地分页
  5. 文字输入限制_从拼音输入法的兴起看汉字文化圈的衰落
  6. 阿里云与A站在一起后,悄悄干了件大事
  7. 016医疗项目 数据字典(概念和在本系统中的应用)
  8. 手把手教你学会用Delve调试Go程序
  9. 2021-2025年中国云计算数据中心IT资产处置(ITAD)行业市场供需与战略研究报告
  10. C++malloc,calloc,realloc,free函数
  11. 2020中软java面试题,通过这9个Java面试题,就可以入职华为啦
  12. Tensorflow相关学习笔记(一)GPU处理相关
  13. 微信小程序电商实战-购物车(上)
  14. HTML5视频放完自动跳转,炫酷html5 网站视频自动跳转代码,零基础秒学
  15. 腾讯会议发布3.0版本;微软将推出元宇宙产品;Firefox启动最大WebRTC升级|WebRTC风向
  16. 基础网络连接及拓扑图
  17. 只知道努力工作的人,失去了赚钱的机会,过去是适者生存,现在是强者生存,沉思你将来会怎样?
  18. Qt之鼠标滑过控件由箭头变成手型
  19. WannaCryptor 勒索蠕虫样本深度技术分析
  20. 想做程序首先就学正则表达式

热门文章

  1. 图形模式下“文章伪原创工具”出汉字来
  2. 修复Bug大幅升级 Sun发布MySQL 5.1版
  3. 一个优秀的程序员应该如何提高工作效率?
  4. no route to host什么意思_Day 74:Vue里的route和router
  5. birt脚本for循环语句_python循环语句(while amp; for)
  6. MEGA | 多序列比对及系统发育树的构建
  7. 信奥中的数学:计算几何
  8. 【Cocos新手入门】cocos creator + Visual Studio 做游戏开发的基础教程
  9. 13产品经理要懂的-人性的恶要怎么利用
  10. STM32H743+CubeMX-定时器TIM输出PWM(Output Compare模式)