背景
这个情况出现在,我需要进行验证码的校验,因此用户的请求首先需要被验证码过滤器校验,而验证码过滤器不需要设定为全局过滤器,因此我就单纯的把它设定为了一个局部过滤器,代码如下
@Component
public class ValidateCodeFilter //implements GlobalFilter, Ordered
extends AbstractGatewayFilterFactory<Object>
{
//需要生成验证码的路径
private final static String[] VALIDATE_URL =
new String[] { "/auth/login", "/auth/register" };
//验证码服务
@Autowired
private ValidateCodeService validateCodeService;
//验证码配置学习
@Autowired
private CaptchaProperties captchaProperties;
//验证码内容
private static final String CODE = "code";
//验证码的uuid
private static final String UUID = "uuid";
@Override
public GatewayFilter apply(Object config)
{
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// 非登录/注册请求或验证码关闭,不处理
if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(),
VALIDATE_URL) || !captchaProperties.getEnabled())
{
return chain.filter(exchange);
}
try
{
String rspStr = resolveBodyFromRequest(request);
//接收JSON格式的请求
JSONObject obj = JSON.parseObject(rspStr);
validateCodeService.checkCaptcha(obj.getString(CODE), obj.getString(UUID));
}
catch (Exception e)
{
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
}
return chain.filter(exchange);
};
}
private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest)
{
// 获取请求体
Flux<DataBuffer> body = serverHttpRequest.getBody();
AtomicReference<String> bodyRef = new AtomicReference<>();
body.subscribe(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
DataBufferUtils.release(buffer);
bodyRef.set(charBuffer.toString());
});
return bodyRef.get();
}
}
然后我进行请求的时候,json参数如下
然后请求经过解析后会发现,字符串居然是null
具体原因不太确定,但是应该是网络传递的时候,这个数据丢失了,原本的数据应该封装在这里
然后我就想着,有没有可能是这个局部过滤器的位置的问题,因为我之前就是由于这个局部过滤器的位置,放在的位置比较靠后,导致他压根没有被执行。
例如这是我早期的配置,可以发现,请求的路径是重复处理的,那么就会导致之前的过滤器处理完毕之后,这个验证码的过滤器压根就不会被执行,所以我就试着把这个过滤器的位置放在了更前面,方法确实得到了执行。但是这样子并不能解决说ServerHttpRequest的getBody返回null的问题。
这是在我没有修改过滤器为之前的执行流程,后面我修改了代码。
我也明白为什么会导致null,其实原因是因为
request.getInputStream(); request.getReader(); 和request.getParameter(“key”)这三个方法中的任何一个方法执行之后,之后再次执行,就会失效。
所以我就想着,我应该可以考虑重写一下过滤器的流程,把传递过来的ServerHttpRequest进行修改,然后重载其getBody方法,让其去缓存中获取数据,而网络上其实已经有很多解决方式了
所以其实我只要能做一个缓存,让之后的ServerHttpRequest去这个缓存中获取数据就好。
代码如下
package com.towelove.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author: Blossom
* CacheBodyGlobalFilterk的作用是为了解决
* ServerHttpRequest中body的数据为NULL的情况
*/
@Slf4j
@Component
public class CacheBodyGlobalFilter implements Ordered, GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (exchange.getRequest().getHeaders().getContentType() == null) {
return chain.filter(exchange);
} else {
//获取databuffer
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> { //设定返回值并处理
DataBufferUtils.retain(dataBuffer); //设定存储空间
Flux<DataBuffer> cachedFlux = Flux//读取Flux中所有数据并且保存
.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator( //得到ServerHttpRequest
exchange.getRequest()) {
@Override //重载getBody方法 让其从我设定的缓存获取
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
//放行 并且设定exchange为我重载后的
return chain.filter(exchange.mutate().request(mutatedRequest).build());
});
}
}
//尽可能早的对这个请求进行封装
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
CacheBodyGlobalFilter这个全局过滤器的目的就是把原有的request请求中的body内容读出来,并且使用ServerHttpRequestDecorator这个请求装饰器对request进行包装,重写getBody方法,并把包装后的请求放到过滤器链中传递下去。这样后面的过滤器中再使用exchange.getRequest().getBody()来获取body时,实际上就是调用的重载后的getBody方法,获取的最先已经缓存了的body数据。这样就能够实现body的多次读取了。
值得一提的是,这个过滤器的order设置的是Ordered.HIGHEST_PRECEDENCE,即最高优先级的过滤器。优先级设置这么高的原因是某些系统内置的过滤器可能也会去读body,这样就会导致我们自定义过滤器中获取body的时候报body只能读取一次这样的错误如下:
java.lang.IllegalStateException: Only one connection receive subscriber allowed.
at reactor.ipc.netty.channel.FluxReceive.startReceiver(FluxReceive.java:279)
at reactor.ipc.netty.channel.FluxReceive.lambda$subscribe$2(FluxReceive.java:129)
之后,只要让我们后面的请求去这个缓存中获取数据即可。增加全局过滤器之后的过滤器链如下。
之后再次发送请求,就可以发现我能拿到数据了,因为其getBody是从CacheBodyGlobalFilter这里获取的数据,所以当你的请求再次执行getBody的时候,他会去这个类中执行getBody方法,所以我在debug的时候,他会再次的执行getBody方法
然后下面是我获取body中数据并且进行解析为字符串的方法文章来源:https://www.toymoban.com/news/detail-487169.html
private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest)
{
// 获取请求体
Flux<DataBuffer> body = serverHttpRequest.getBody();
AtomicReference<String> bodyRef = new AtomicReference<>();
body.subscribe(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
DataBufferUtils.release(buffer);
bodyRef.set(charBuffer.toString());
});
return bodyRef.get();
}
到此为止,这个问题大概是结束了。文章来源地址https://www.toymoban.com/news/detail-487169.html
到了这里,关于【Java】SpringCloud Gateway自定义过滤器中获取ServerHttpRequest的body中的数据为NULL的问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!