【Java】SpringCloud Gateway自定义过滤器中获取ServerHttpRequest的body中的数据为NULL的问题

简介: 【Java】SpringCloud Gateway自定义过滤器中获取ServerHttpRequest的body中的数据为NULL的问题

背景

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

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

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();
    }

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


相关文章
|
2天前
|
安全 Java 大数据
|
2天前
|
SQL Java
java处理数据查看范围
java处理数据查看范围
|
2天前
|
数据采集 机器学习/深度学习 Java
数据猎手:使用Java和Apache HttpComponents库下载Facebook图像
本文介绍了如何使用Java和Apache HttpComponents库从Facebook获取图像数据。通过设置爬虫代理IP以避免限制,利用HttpClient发送请求,解析HTML找到图像链接,然后下载并保存图片。提供的Java代码示例展示了实现过程,包括创建代理配置、线程池,以及下载图片的逻辑。注意,实际应用需根据Facebook页面结构进行调整。
数据猎手:使用Java和Apache HttpComponents库下载Facebook图像
|
2天前
|
存储 监控 Java
如何在Java中实现等待文件修改后再读取数据的功能?
如何在Java中实现等待文件修改后再读取数据的功能?
19 0
|
2天前
|
安全 Java 网络安全
深入理解Java异常处理网络安全与信息安全:保护数据的关键策略
【4月更文挑战第29天】本文将深入探讨Java中的异常处理机制,包括异常的概念、分类、捕获和处理等方面。通过详细的解析和实例演示,帮助读者更好地理解Java异常处理的原理和应用。 【4月更文挑战第29天】在数字化时代,数据成为最宝贵的资产之一。随着网络攻击的日益猖獗,了解并实施有效的网络安全和信息保护措施变得至关重要。本文将深入探讨网络安全漏洞的概念、加密技术的重要性以及提升个人和企业的安全意识的必要性。我们将分析当前的安全挑战,并提供一系列实用的防护策略,旨在帮助读者构建一个更加安全的数字环境。
|
2天前
|
Java 关系型数据库 MySQL
【JDBC编程】基于MySql的Java应用程序中访问数据库与交互数据的技术
【JDBC编程】基于MySql的Java应用程序中访问数据库与交互数据的技术
|
2天前
|
Java
【专栏】Java 8 的 Streams 提供了一种处理数据集合的新方式,增强了代码的可读性和可维护性
【4月更文挑战第28天】Java 8 的 Streams 提供了一种处理数据集合的新方式,增强了代码的可读性和可维护性。本文介绍了 Streams 的基本概念,如从数据源创建 Stream,以及中间和终端操作。通过过滤、映射、归并、排序、分组等案例,展示了 Streams 的使用,包括并行 Streams 提高效率。学习 Streams 可以提升代码质量和效率,文章鼓励读者在实际开发中探索更多 Streams 功能。
|
2天前
|
Java API 开发者
【专栏】Java 8的Stream API是处理集合数据的新方式,强调简洁和声明式编程
【4月更文挑战第27天】Java 8的Stream API是处理集合数据的新方式,强调简洁和声明式编程。它基于延迟执行和惰性求值,提供创建、中间操作(如filter、map)和终端操作(如forEach、collect)。示例展示了如何通过Stream排序、过滤、映射和聚合数据。
|
2天前
|
Java Apache
java读取excel数据案例
Java代码示例使用Apache POI库读取Excel(example.xlsx)数据。创建FileInputStream和XSSFWorkbook对象,获取Sheet,遍历行和列,根据单元格类型(STRING, NUMERIC, BOOLEAN)打印值。需引入Apache POI库并确保替换文件路径。
11 1
|
2天前
|
SQL Java 数据库
java代码中调用dao层查询接口,代码没有返回数据,打印出的sql查出了数据
java代码中调用dao层查询接口,代码没有返回数据,打印出的sql查出了数据
17 1