文章目录

背景

背景

这个情况出现在,我需要进行验证码的校验,因此用户的请求首先需要被验证码过滤器校验,而验证码过滤器不需要设定为全局过滤器,因此我就单纯的把它设定为了一个局部过滤器,代码如下

@Component

public class ValidateCodeFilter //implements GlobalFilter, Ordered

extends AbstractGatewayFilterFactory

{

//需要生成验证码的路径

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 body = serverHttpRequest.getBody();

AtomicReference 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 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 cachedFlux = Flux//读取Flux中所有数据并且保存

.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));

ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator( //得到ServerHttpRequest

exchange.getRequest()) {

@Override //重载getBody方法 让其从我设定的缓存获取

public Flux 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中数据并且进行解析为字符串的方法

private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest)

{

// 获取请求体

Flux body = serverHttpRequest.getBody();

AtomicReference bodyRef = new AtomicReference<>();

body.subscribe(buffer -> {

CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());

DataBufferUtils.release(buffer);

bodyRef.set(charBuffer.toString());

});

return bodyRef.get();

}

到此为止,这个问题大概是结束了。

文章来源

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: 

发表评论

返回顶部暗黑模式