在接入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<String, String> formData;
/**
* cache reqeust path
*/
private String path; public String getCacheBody() {
return cacheBody;
} public void setCacheBody(String cacheBody) {
this.cacheBody = cacheBody;
} public MultiValueMap<String, String> getFormData() {
return formData;
} public void setFormData(MultiValueMap<String, String> 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<HttpMessageReader<?>> messageReaders =
HandlerStrategies.withDefaults().messageReaders(); private Logger log = LoggerFactory.getLogger(GatewayContextFilter.class); @Override
public Mono<Void> 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<Void> 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<Void> 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<String, String> formData =
gatewayContext.getFormData();
/**
* formData is empty just return
*/
if (null == formData || formData.isEmpty()) {
return chain.filter(exchange);
}
StringBuilder formDataBodyBuilder = new StringBuilder();
String entryKey;
List<String> entryValue;
try {
/**
* repackage form data
*/
for (Map.Entry<String, List<String>> 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<Databuffer>
*
* @return
*/
@Override
public Flux<DataBuffer> 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<Void> readBody(
ServerWebExchange exchange,
GatewayFilterChain chain,
GatewayContext gatewayContext) {
/**
* join the body
*/
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
/*
* read the body Flux<DataBuffer>, 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<DataBuffer> 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<DataBuffer> 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);

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

spring cloud gateway 拦截request Body的更多相关文章

  1. 从0开始构建你的api网关--Spring Cloud Gateway网关实战及原理解析

    API 网关 API 网关出现的原因是微服务架构的出现,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题 ...

  2. Spring Cloud Gateway 之获取请求体(Request Body)的几种方式

    Spring Cloud Gateway 获取请求体 一.直接在全局拦截器中获取,伪代码如下 private String resolveBodyFromRequest(ServerHttpReque ...

  3. 最全面的改造Zuul网关为Spring Cloud Gateway(包含Zuul核心实现和Spring Cloud Gateway核心实现)

    前言: 最近开发了Zuul网关的实现和Spring Cloud Gateway实现,对比Spring Cloud Gateway发现后者性能好支持场景也丰富.在高并发或者复杂的分布式下,后者限流和自定 ...

  4. 快速突击 Spring Cloud Gateway

    认识 Spring Cloud Gateway Spring Cloud Gateway 是一款基于 Spring 5,Project Reactor 以及 Spring Boot 2 构建的 API ...

  5. 深入学习spring cloud gateway 限流熔断

    前言 Spring Cloud Gateway 目前,Spring Cloud Gateway是仅次于Spring Cloud Netflix的第二个最受欢迎的Spring Cloud项目(就GitH ...

  6. 【Spring Cloud & Alibaba 实战 | 总结篇】Spring Cloud Gateway + Spring Security OAuth2 + JWT 实现微服务统一认证授权和鉴权

    一. 前言 hi,大家好~ 好久没更文了,期间主要致力于项目的功能升级和问题修复中,经过一年时间的打磨,[有来]终于迎来v2.0版本,相较于v1.x版本主要完善了OAuth2认证授权.鉴权的逻辑,结合 ...

  7. Spring Cloud Gateway GatewayFilter的使用

    Spring Cloud Gateway GatewayFilter的使用 一.GatewayFilter的作用 二.Spring Cloud Gateway内置的 GatewayFilter 1.A ...

  8. Spring Cloud Gateway夺命连环10问?

    大家好,我是不才陈某~ 最近有很多小伙伴私信我催更 <Spring Cloud 进阶>,陈某也总结了一下,最终原因就是陈某之前力求一篇文章将一个组件重要知识点讲透,这样导致了文章篇幅很长, ...

  9. Spring cloud gateway

    ==================================为什么需要API gateway?==================================企业后台微服务互联互通, 因为 ...

随机推荐

  1. Filter 和Listener

    Filter 和Listener Filter : 过滤器 1.概念 浏览器发出请求访问服务器的资源,过滤器将请求拦截,完成一些特殊的功能. 作用:一般用于完成通过的操作.如:登陆验证.统一编码处理. ...

  2. sql注入判断流程(结合sqli-labs学习)

    sql注入判断流程(结合sqli-labs学习) 类型一 类型判断 ?id=1 and 1=2 --+ 如果返回结果正常,说明不是数字类型 and 为两方都为真才算争取 ?id=1' --+ 显示不正 ...

  3. eclipse不小心删除文件如何恢复

    转自:https://blog.csdn.net/u012129031/article/details/78791277 1.右键点击java项目工程名,选择restort from history, ...

  4. python常用函数 J

    (1)join(iterable)/(2) join(str, str) (1)join()连接字符串数组.(2)os.path.join()将多个路径组合后返回. 例子:

  5. 《YC创业营:硅谷顶级创业孵化器如何改变世界》:YC2011批量天使投资记录 三星推荐

    这个YC创业营是一个硅谷的天使投资基金,每年两次批量投资创业公司.本书说的是2011年YC批量选择了64个创业团队,让他们集中到硅谷办公3个月,给他们创业指导,帮他们找A轮投资. YC创始人偏爱25岁 ...

  6. day17 python re模块 简易爬虫

    day17 python   一.re模块     1.re模块的基础方法         查找findall() import re #re.findall(pattern,string,flags ...

  7. Java 实现文件复制的不同方法

    用不同的方法实现文件的复制 1. 通道 Channel,它是一个对象,可以通过它读取和写入数据.拿NIO与原来的I/O比较,通道就像是流.是对接操作系统底层和缓冲区的桥梁. 2. 性能比较 内存映射最 ...

  8. hive之压缩

    对数据进行压缩可以节约磁盘空间,提高系统吞吐量和性能,但是压缩和解压缩会增加CPU的开销. 1.hive的压缩编/解码器 BZip2和GZip压缩率高,但是需要消耗较多的CPU开销.LZO和Snapp ...

  9. 3.1.2 Socket网络通信开发

    Socket语法 Python中,我们用Socket()函数来创建套接字,语法如下: socket.socket([family[, type[, proto]]]) 参数 family:套接字家族可 ...

  10. @PostMapping

    @PostMapping映射一个POST请求 Spring MVC新特性 提供了对Restful风格的支持 @GetMapping,处理get请求 @PostMapping,处理post请求 @Put ...