首页 > 其他分享 >(WebFlux)004、WebFilter踩坑记录

(WebFlux)004、WebFilter踩坑记录

时间:2022-09-28 22:12:17浏览次数:65  
标签:return chain exchange request WebFlux filter token 004 WebFilter

一、背景

使用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、多看代码,发现问题,实现完美的解决方案

标签:return,chain,exchange,request,WebFlux,filter,token,004,WebFilter
From: https://www.cnblogs.com/lifacheng/p/16739743.html

相关文章

  • [ZJOI2004]午餐
    [ZJOI2004]午餐题目描述上午的训练结束了,THUACM小组集体去吃午餐,他们一行N人来到了著名的十食堂。这里有两个打饭的窗口,每个窗口同一时刻只能给一个人打饭。由于每个人......
  • ASEMI快恢复二极管SF3004,SF3004特性,SF3004机械数据
    编辑-ZASEMI快恢复二极管SF3004参数:型号:SF3004最大重复峰值反向电压(VRRM):400V最大RMS电桥输入电压(VRMS):280V最大直流阻断电压(VDC):400V最大平均正向整流输出电流(IF):30A峰......
  • 甲级1004
    https://pintia.cn/problem-sets/994805342720868352/problems/994805521431773184普通静态数遍历1#include<bits/stdc++.h>//dfs2usingnamespacestd;3#de......
  • SF3004-ASEMI高压大电流快恢复管SF3004
    编辑:llSF3004-ASEMI高压大电流快恢复管SF3004型号:SF3004品牌:ASEMI封装:TO-220AB特性:快恢复二极管正向电流:30A反向耐压:400V恢复时间:35ns引脚数量:3芯片个数:2芯片尺......
  • Process finished with exit code -1073740791 (0xC0000409) tensorflow显存不足
    显存问题:1、这种情况需要去官网下载zlib的文件,http://www.winimage.com/zLibDll/zlib123dllx64.zip这是下载地址。2、在解压后的文件夹dll_x64中找到zlibwapi.dll文......
  • ORA-00439 未启用的功能
    在oracle11.2.0.1里面创建分区表的时候出现ora-00439未启用功能:partitioning进行检查:1:安装的版本为OracleDatabase11gEnterpriseEditionRelease11.2.0.1.0-64bit......
  • Windows 10 版本 2004 以下安装 WSL
    安装Linux官方文档旧版WSL的手动安装步骤由于Windows版本实在太老,不能安装WSL2。手动安装这里选择下载KaliLinux发行版进行安装。下载安装后,『开始』->『K......
  • 剑指 Offer II 004. 只出现一次的数字 【模拟】【位数统计取余】
    题目给你一个整数数组nums,除某个元素仅出现一次外,其余每个元素都恰出现三次。请你找出并返回那个只出现了一次的元素。难度:中等提示:1<=nums.length<=3*10......
  • SVN: E155004: THERE ARE UNFINISHED WORK ITEMS IN ''; RUN 'SVN CLEANUP' FIRST
    eclipse开发过程中,检出项目时报错执行项目右键-team-runcleanup-也还是会报这个错误;解决办法下载软件https://www.sqlite.org/download.html解压放到项目.svn目录......
  • P5914 [POI2004]MOS 题解
    题目传送门分析这是一道小学经典的数学题,对于这种求最短时间的题目,我们要认真考虑两组人员:首先,跑的快的人应当跑的最多,能者多劳。其次,跑的慢的人应当跑的最少,否则会拉......