(webflux)004webfilter踩坑记录(代码片段)

编号94530 编号94530     2022-12-08     671

关键词:

一、背景

使用SpringWebFlux的WebFilter时,由于不熟悉或一些思考疏忽,容易出现未知的异常。记录一下排查与解决方案,给大家分享一下。

二、问题

2.1 问题描述

在测试接口方法时,出现的错误信息如下(对一些项目路径做了修改):

java.lang.IllegalStateException: COMPLETED
	at org.springframework.http.server.reactive.AbstractListenerReadPublisher$State.subscribe(AbstractListenerReadPublisher.java:451)
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	*__checkpoint ⇢ springfox.boot.starter.autoconfigure.SwaggerUiWebFluxConfiguration$CustomWebFilter [DefaultWebFilterChain]
	*__checkpoint ⇢ com.xxx.config.LoginWebFilter$$EnhancerBySpringCGLIB$$f3da6bdf [DefaultWebFilterChain]
	*__checkpoint ⇢ com.xxx.config.TraceIdFilter [DefaultWebFilterChain]
	*__checkpoint ⇢ HTTP POST "/abc/test/testMethod" [ExceptionHandlingWebHandler]
Original Stack Trace:
		at org.springframework.http.server.reactive.AbstractListenerReadPublisher$State.subscribe(AbstractListenerReadPublisher.java:451)
		at org.springframework.http.server.reactive.AbstractListenerReadPublisher.subscribe(AbstractListenerReadPublisher.java:105)

2.2 解决问题

通过查看错误信息描述,checkpoint点都在webfilter中,由于对webflux也不是特别熟,所以就只有一个个测试。

通过一系列操作, 把swagger移除,细读TraceIdFilter(内容不多),主要归功于原方案是正确的,修改后错误,最后才定位问题出现在LoginWebFilter。

说说插曲,原实现方式(有阻塞逻辑,没出现上述异常),代码如下:

@Configuration
@Slf4j
@Order(-10)
public class LoginWebFilter implements WebFilter 
    // 略...

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) 
        ServerHttpRequest request = exchange.getRequest();

        if (!enableGateway) 
            String token = Optional.ofNullable(request.getHeaders().getFirst(Constants.TOKEN))
                    .orElse("");
            // 获取用户信息
            User user = getUser(token);
            if (user != null) 
                ServerHttpRequest mutateRequest = exchange.getRequest().mutate()
                        .build();
                exchange = exchange.mutate().request(mutateRequest).build();
            
        
        return chain.filter(exchange);
    

    private User getUser(String token) 
        if (StringUtils.isNotBlank(token)) 
            return redisTemplate.opsForValue().get("xxx:tk:" + token)
                    .flatMap(str -> Mono.justOrEmpty(JsonUtils.toObj(str, User.class))).block();
        
        return null;
    

这样写,没有复杂的业务逻辑,从上到下,完全OJBK,但是调整后,就出现了上述异常。

改完后的问题代码如下:

// 错误
public class LoginWebFilter implements WebFilter 
	/...@Autowired
    private ReactiveStringRedisTemplate redisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) 

        if (!enableGateway) 
            ServerHttpRequest request = exchange.getRequest();
            String token = Optional.ofNullable(request.getHeaders().getFirst(Constants.TOKEN))
                    .orElse("");

            return getUser(token).flatMap(user -> 
                ServerHttpRequest mutateRequest = exchange.getRequest().mutate()
                        .header(UserUtils.MEMBER_ID, user.getMemId())
                        .header(UserUtils.MOBILE, user.getMobile())
                        .build();

                ServerWebExchange newexchange = exchange.mutate().request(mutateRequest).build();
                return chain.filter(newexchange);
               // 问题点 
            ).switchIfEmpty(chain.filter(exchange));
        
        return chain.filter(exchange);
    
	// 不在用block
    private Mono<User> getUser(String token) 
        if (StringUtils.isNotBlank(token)) 
            return redisTemplate.opsForValue().get("xxx:tk:" + token)
                    .flatMap(str -> Mono.justOrEmpty(JsonUtils.toObj(str, User.class)));
        
        return Mono.empty();
    

2.3 如何解决

对比改造前和改造后的代码,其实差异不大,那问题出现在哪呢?

由于对webflux也不是特别熟,那就只能一点点试(太蠢了)。 最后发现问题出现在了switchIfEmpty(chain.filter(exchange)),在去掉了switchIfEmpty(chain.filter(exchange)),就不会在出现上述异常。

修改后部分代码如下:

// 半正确
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) 

    if (!enableGateway) 

        ServerHttpRequest request = exchange.getRequest();
        String token = Optional.ofNullable(request.getHeaders().getFirst(Constants.TOKEN))
            .orElse(“”);

        return getUser(token).flatMap(user -> 
            ServerHttpRequest mutateRequest = exchange.getRequest().mutate()
                .header(UserUtils.MEMBER_ID, user.getMemId())
                .header(UserUtils.MOBILE, user.getMobile())
                .build();

            ServerWebExchange newexchange = exchange.mutate().request(mutateRequest).build();
            return chain.filter(newexchange);
        );
    
    return chain.filter(exchange);

虽然现在不回在出现异常,但是去掉switchIfEmpty后,代码逻辑是不完整的,当获取不到User时,返回Mono.emtpy,那会直接结束流程,不在执行剩下的filter或其他逻辑。真是连环坑,一坑接一坑。所以对代码需要调整一番,调整后如下:

// 有点正确 但是不多
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) 

    if (!enableGateway) 

        ServerHttpRequest request = exchange.getRequest();
        String token = Optional.ofNullable(request.getHeaders().getFirst(Constants.TOKEN))
            .orElse(“”);

        return getUser(token).switchIfEmpty(Mono.error(() -> new BizException(ErrorCode.USER_IS_NULL_ERROR)))
            .flatMap(user -> 
                ServerHttpRequest mutateRequest = exchange.getRequest().mutate()
                    .header(UserUtils.MEMBER_ID, user.getMemId())
                    .header(UserUtils.MOBILE, user.getMobile())
                    .build();

                ServerWebExchange newexchange = exchange.mutate().request(mutateRequest).build();
                return chain.filter(newexchange);
            ).onErrorResume(e -> chain.filter(exchange));

    
    return chain.filter(exchange);

当获取用户为空后,抛出异常,然后在兜底,当异常的时候执行chain.filter(exchange)(好蠢的方式… 但是解决问题了)。

2.4 意外之喜

各位看官,就在我写完上完上面的代码修改方案之后,读了一下修改完后的代码,突然发现问题出在哪了,所以连夜修改了代码方式。现在我听我细细道来。

2.4.1 问题点

原因点chain.filter(exchange)重复执行

switchIfEmpty(chain.filter(exchange))这个点本意是想用在当getUser 方法为空时,执行其它WebFilter的逻辑,从而不影响主流程。

忽略了一点是:当chain.filter(newexchange)这个方法执行完后,返回的也是Mono<Void>,也是为空。所以无论如何,代码最后的逻辑都会走到switchIfEmpty(chain.filter(exchange))

但是当getUser获取到用户后,会重复执行chain.filter(exchange),如下

  • return chain.filter(newexchange)
  • switchIfEmpty(chain.filter(exchange))

由于第一次执行完chain.filter(exchange),request、response都已经关闭,所以出现了xx COMPLETE,那看来的确符合逻辑。

2.4.2 验证猜想

这个验证方式还是挺简单的,那就是分别传入正常的TOKEN和错误的TOKEN。

具体操作:…(本人已完成)

结论:

当传入错误的token的时候,确实没有抛出异常,完美执行。但是当传入正确的token,出现了熟悉的异常。

2.4.3 代码调整

知道问题的原因,那就好调整代码了。修改后如下:

public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) 
    if (!enableGateway) 
        ServerHttpRequest request = exchange.getRequest();
        String token = Optional.ofNullable(request.getHeaders().getFirst(Constants.TOKEN))
            .orElse(request.getHeaders().getFirst("suuid"));

        return getUser(token).map(user -> 
            ServerHttpRequest mutateRequest = exchange.getRequest().mutate()
                .header(UserUtils.MEMBER_ID, user.getMemId())
                .header(UserUtils.MOBILE, user.getMobile())
                .build();
            return exchange.mutate().request(mutateRequest).build();
            // 调整当getUser为空时,返回的内容
        ).switchIfEmpty(Mono.just(exchange)).flatMap(chain::filter);
        
    
    return chain.filter(exchange);

至此,问题就完全解决拉!心里美滋滋!

三、总结

1、遇到问题,还是要多看看呀,细细思考一下

2、多看代码,发现问题,实现完美的解决方案

(webflux)004webfilter踩坑记录(代码片段)

一、背景使用SpringWebFlux的WebFilter时,由于不熟悉或一些思考疏忽,容易出现未知的异常。记录一下排查与解决方案,给大家分享一下。二、问题2.1问题描述在测试接口方法时,出现的错误信息如下(对一些项... 查看详情

将 WebFilter Spring WebFlux 添加到路径

】将WebFilterSpringWebFlux添加到路径【英文标题】:AddaWebFilterSpringWebFluxtopaths【发布时间】:2022-01-1119:43:33【问题描述】:我有一个过滤器:@ComponentpublicclassRequestIdFilterimplementsWebFilter@OverridepublicMono<Void>filter(ServerWebExchangee 查看详情

为啥在 webflux 中使用 WebFilter corsFilter 时请求返回 404 状态?

】为啥在webflux中使用WebFiltercorsFilter时请求返回404状态?【英文标题】:Whyrequestsarereturned404statuswhenusingWebFiltercorsFilterinwebflux?为什么在webflux中使用WebFiltercorsFilter时请求返回404状态?【发布时间】:2019-11-2315:33:52【问题描述】:... 查看详情

如何使 WebFilter 在非 WebFlux/非反应式 Spring Boot 应用程序中工作?

】如何使WebFilter在非WebFlux/非反应式SpringBoot应用程序中工作?【英文标题】:HowtomakeWebFilterworkinanon-WebFlux/non-reactiveSpringBootapplication?【发布时间】:2020-12-2314:59:32【问题描述】:我正在尝试解决这个问题:HowtorewriteURLswithSpring(Boot... 查看详情

如何使用 Spring WebFlux WebFilter 结束请求并发送正确的响应?

】如何使用SpringWebFluxWebFilter结束请求并发送正确的响应?【英文标题】:HowtoendrequestandsendproperresponseusingSpringWebFluxWebFilter?【发布时间】:2018-07-0517:54:34【问题描述】:我在标头中使用JWT来验证用户请求。@OverridepublicMono<Void>... 查看详情

如何在 Spring Webflux Java 中记录请求正文

】如何在SpringWebfluxJava中记录请求正文【英文标题】:HowtologrequestbodyinspringWebfluxJava【发布时间】:2020-08-2515:37:23【问题描述】:我在POST请求中接收到一些XML有效负载,并希望看到收到的有效负载以进行调试。下面(我的自定义W... 查看详情

WebFilter bean 在安全的 Spring Boot Webflux 应用程序中调用了两次

】WebFilterbean在安全的SpringBootWebflux应用程序中调用了两次【英文标题】:WebFilterbeaninvokedtwiceinasecuredSpringBootWebfluxapplication【发布时间】:2019-12-1422:16:42【问题描述】:我正在使用带有webflux和安全性的SpringBoot2.1.x。我定义了一些Au... 查看详情

如何使用 spring webflux 读取请求正文

】如何使用springwebflux读取请求正文【英文标题】:Howtoreadtherequestbodywithspringwebflux【发布时间】:2018-10-0704:23:22【问题描述】:我正在使用Spring5、Netty和Springwebflux来开发API网关。有时我希望网关停止请求,但我也想读取请求的主... 查看详情

更改 Spring Security WebFilter 的顺序

...个使用SpringCloudGateway实现的APIGateway,它使用SpringSecurity。WebFlux的SpringSecurity在过滤器链的开头作为WebFilter实现。所以在认证成功后 查看详情

Spring WebFlux 请求主体在测试中为空

】SpringWebFlux请求主体在测试中为空【英文标题】:SpringWebFluxRequestPrincipalisnullintest【发布时间】:2021-03-0303:15:37【问题描述】:我们有一个库,它为我们的请求提供了一个自定义过滤器,我们用它来将有关请求的信息写入我们的... 查看详情

Spring boot + webflux:并行运行某些步骤时上下文丢失

】Springboot+webflux:并行运行某些步骤时上下文丢失【英文标题】:Springboot+webflux:contextlostwhenrunningsomestepsinparallel【发布时间】:2019-08-3113:40:43【问题描述】:春季启动:2.1.3.RELEASE你好,我正在尝试使用springwebflux的上下文特性来... 查看详情

踩坑记录

1. EntityUtils.toString(entity),EntityUtils流只存在一次,多次访问会报错  解决办法:  查看详情

如何使用 Webflux 在 Spring API 处理程序方法中访问 JWT 声明?

】如何使用Webflux在SpringAPI处理程序方法中访问JWT声明?【英文标题】:HowcanIaccessJWTclaimsinSpringAPIhandlermethodsusingWebflux?【发布时间】:2019-10-1111:39:50【问题描述】:我正在添加一个WebFilter以在SecurityWebFilterChain内执行JWT身份验证。... 查看详情

springeasypoi报错,踩坑记录

参考技术A最近项目上需要导入导出Excel,用到了easyPoi这个插件。踩坑过后终于执行成功了。特此记录:easyPoi->API接口,点击此处跳转如果直接上手该插件,点击此处看教程。会了多表就会单表的了(搬砖,哈哈哈) 查看详情

如何在 spring-mvc 中将日志记录添加到 webflux 端点?

】如何在spring-mvc中将日志记录添加到webflux端点?【英文标题】:Howtoaddloggingtoawebfluxendpointinspring-mvc?【发布时间】:2020-07-1108:50:29【问题描述】:我有一个现有的spring-mvc应用程序与不同的@RestController。现在我想添加一个Mono&lt... 查看详情

javascript两数相加(踩坑)记录

Addingtwonumbersconcatenatestheminsteadofcalculatingthesum JavaScript里两个变量 vara=2; varb=1; varc="("+a+b+")"; alert(c); 结果输出:(21) 正确写法: varc= +a +  查看详情

uibot踩坑记录

UiBot踩坑记录1.有的文本框输入点击后再输入,否则将无法输入,例如126邮箱2.判断元素是否存在,一定要检查是否将sRet的定义写在判断元素的下面了,这回导致sRet一直为true。3.连接字符串使用&4.判断特殊的输入框,最好使用... 查看详情

系统编译踩坑记录

问题1. Cause:executingexternalnativebuildforcmake报错  尝试1. 通过./gradlewbuild--stacktrace 查看编译报错细节 问题2. 使用gradle进行编译打包产生java.lang.OutOfMemoryError:GCoverheadlimitexceeded在gradle.properites文件中添加如下配置即可org.gr 查看详情