【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参数如下
【Java】SpringCloud Gateway自定义过滤器中获取ServerHttpRequest的body中的数据为NULL的问题
然后请求经过解析后会发现,字符串居然是null
【Java】SpringCloud Gateway自定义过滤器中获取ServerHttpRequest的body中的数据为NULL的问题
具体原因不太确定,但是应该是网络传递的时候,这个数据丢失了,原本的数据应该封装在这里
【Java】SpringCloud Gateway自定义过滤器中获取ServerHttpRequest的body中的数据为NULL的问题
然后我就想着,有没有可能是这个局部过滤器的位置的问题,因为我之前就是由于这个局部过滤器的位置,放在的位置比较靠后,导致他压根没有被执行。
例如这是我早期的配置,可以发现,请求的路径是重复处理的,那么就会导致之前的过滤器处理完毕之后,这个验证码的过滤器压根就不会被执行,所以我就试着把这个过滤器的位置放在了更前面,方法确实得到了执行。但是这样子并不能解决说ServerHttpRequest的getBody返回null的问题。
【Java】SpringCloud Gateway自定义过滤器中获取ServerHttpRequest的body中的数据为NULL的问题
这是在我没有修改过滤器为之前的执行流程,后面我修改了代码。
【Java】SpringCloud Gateway自定义过滤器中获取ServerHttpRequest的body中的数据为NULL的问题
我也明白为什么会导致null,其实原因是因为
request.getInputStream(); request.getReader(); 和request.getParameter(“key”)这三个方法中的任何一个方法执行之后,之后再次执行,就会失效。
所以我就想着,我应该可以考虑重写一下过滤器的流程,把传递过来的ServerHttpRequest进行修改,然后重载其getBody方法,让其去缓存中获取数据,而网络上其实已经有很多解决方式了
【Java】SpringCloud Gateway自定义过滤器中获取ServerHttpRequest的body中的数据为NULL的问题
所以其实我只要能做一个缓存,让之后的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)

之后,只要让我们后面的请求去这个缓存中获取数据即可。增加全局过滤器之后的过滤器链如下。
【Java】SpringCloud Gateway自定义过滤器中获取ServerHttpRequest的body中的数据为NULL的问题
之后再次发送请求,就可以发现我能拿到数据了,因为其getBody是从CacheBodyGlobalFilter这里获取的数据,所以当你的请求再次执行getBody的时候,他会去这个类中执行getBody方法,所以我在debug的时候,他会再次的执行getBody方法
【Java】SpringCloud Gateway自定义过滤器中获取ServerHttpRequest的body中的数据为NULL的问题
然后下面是我获取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();
    }

到此为止,这个问题大概是结束了。文章来源地址https://www.toymoban.com/news/detail-487169.html

到了这里,关于【Java】SpringCloud Gateway自定义过滤器中获取ServerHttpRequest的body中的数据为NULL的问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • SpringCloud - Spring Cloud 之 Gateway网关,Route路由,Predicate 谓词/断言,Filter 过滤器(十三)

    SpringCloud - Spring Cloud 之 Gateway网关,Route路由,Predicate 谓词/断言,Filter 过滤器(十三)

    阅读本文前可先参考 ​​​​​​SpringCloud - Spring Cloud根/父项目,开发准备(二)_MinggeQingchun的博客-CSDN博客 SpringCloud - Spring Cloud 之 Gateway网关(十三)_MinggeQingchun的博客-CSDN博客 Web 有三大组件(监听器 过滤器 servlet),Spring Cloud GateWay 最主要的功能就是路由转发,而在定义

    2024年02月14日
    浏览(9)
  • Spring-Cloud-Gateway如何自定义路由过滤器?

    遇到这么一个面试题:自定义网关过滤器实现把url中的请求参数放到http的header中传递给微服务。 我们知道网关的一个重要的作用就是路由转发,路由表的配置大概是这个样子: 其中的filters就是配置的路由过滤器,Spring已经内置了31个路由的过滤器,这些过滤器都是 org.spring

    2024年02月16日
    浏览(10)
  • GateWay网关自定义过滤器实现token校验完成统一鉴权

    GateWay网关自定义过滤器实现token校验完成统一鉴权

    gateWay---API网关,也可以称为业务网关,主要服务于微服务的; (1)  三大组件 路由(Route)         构建网关的基本模块,由id(唯一标示)、目标URI、一组断言、一组过滤器组成,如果断言为true,则匹配该路由   断言(Predicate)          可以使用它匹配来自HTTP请求的任何

    2024年02月08日
    浏览(12)
  • 微服务Gateway网关(自动定位/自定义过滤器/解决跨域)+nginx反向代理gateway集群

    微服务Gateway网关(自动定位/自定义过滤器/解决跨域)+nginx反向代理gateway集群

    目录 Gateway网关 1.0.为什么需要网关? 1.1.如何使用gateway网关 1.2.网关从注册中心拉取服务 1.3.gateway自动定位 1.4.gateway常见的断言 1.5.gateway内置的过滤器 1.6.自定义过滤器-全局过滤器 1.7.解决跨域问题 2.nginx反向代理gateway集群 2.1.配置文件 继  nacos注册中心+Ribbon负载均衡+完成

    2024年02月06日
    浏览(24)
  • 从Spring Cloud Gateway过滤器中获取请求体的最优方案

    在spring cloud gateway出现这个问题的时候我们第一反应应该很简单,但是真正实现的时候却有点困难。我看了很多相关的文档,感觉太多都不清晰而且解决不了问题。下面我就把我的方便理解的解决方案写下来。 1. 先重写请求体(过滤器优先级一定要在要获取body之前执行) 这

    2024年02月16日
    浏览(10)
  • JAVA开发(通过网关gateway过滤器进行返回结果加密)

    JAVA开发(通过网关gateway过滤器进行返回结果加密)

    在对C的网站或者APP后端接口中,参数的传输往往需要加密传输。这时我们 可以通过springcloud的网关过滤器进行统一的控制。 网关过滤器的执行顺序: 请求进入网关会碰到三类过滤器:当前路由过滤器、DefaultFilter、GlobalFilter。 请求路由后,会将当前路由过滤器和DefaultFilter、

    2023年04月17日
    浏览(8)
  • SpringBoot自定义过滤器获取HttpServletRequest和HttpServletResponse的参数

    公司的老系统改造:由于接口日志不全,接口太多,也无法每个接口都加上日志,所以要在网关层统一记录一下日志,并存到数据库中,(以后计划要存储到ES中) 过滤器是基于Servlet规范的组件,作用于整个请求和响应过程,无法直接访问Spring MVC的上下文。过滤器先于拦截

    2024年01月25日
    浏览(7)
  • Spring MVC获取参数和自定义参数类型转换器及编码过滤器

    Spring MVC获取参数和自定义参数类型转换器及编码过滤器

    目录   一、使用Servlet原生对象获取参数 1.1 控制器方法 1.2 测试结果 二、自定义参数类型转换器 2.1 编写类型转换器类 2.2 注册类型转换器对象  2.3 测试结果  三、编码过滤器 3.1 JSP表单 3.2 控制器方法 3.3 配置过滤器 3.4 测试结果  往期专栏文章相关导读  1. Maven系列专

    2024年02月10日
    浏览(10)
  • gateway-过滤器执行顺序

    请求进入网关会碰到三类过滤器:当前路由过滤器、DefaultFilter、GlobalFilter。 请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器 过滤器执行顺序 1.每一个过滤器都必须指定一个int类型的order值,order值越小

    2024年02月13日
    浏览(7)
  • Gateway网关 全局过滤器

    Gateway网关 全局过滤器

    一、全局过滤器 全局过滤器GlobalFilter 全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。 区别在于GatewayFilter通过配置定义,处理逻辑是固定的。 需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件: 参数中是否有au

    2024年02月07日
    浏览(12)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包