You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何监听Reactor Netty触发doOnError的原因并避免Mono触发doOnCancel

问题分析

你遇到的doOnCancel触发是WebFlux的正常行为:当浏览器刷新/断开HTTP连接时,WebFlux会感知到TCP通道关闭,进而取消当前请求对应的Mono订阅,触发取消回调。下面针对你的需求给出具体解决方案:


一、监听Reactor Netty的错误原因

要捕获Reactor Netty底层的错误,可以从全局配置和Mono链内捕获两个层面入手:

1. 全局Netty错误监听(WebFlux配置类)

通过定制Netty服务器的连接处理器,捕获所有底层通道错误:

@Configuration
public class NettyErrorConfig {
    @Bean
    public WebServerFactoryCustomizer<NettyReactiveWebServerFactory> nettyErrorListener() {
        return factory -> factory.addServerCustomizers(server -> 
            server.doOnConnection(conn -> 
                conn.addHandlerLast(new ChannelDuplexHandler() {
                    @Override
                    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                        // 打印Netty底层错误详情,包含连接断开、通道异常等信息
                        System.err.printf("Reactor Netty 底层错误: %s%n", cause.getMessage());
                        // 继续传递异常,不影响原有错误处理逻辑
                        ctx.fireExceptionCaught(cause);
                    }
                })
            )
        );
    }
}

2. 在Mono链内捕获特定错误

针对HTTP请求链路,直接在Mono中判断并捕获Netty相关异常:

@GetMapping("/api/testOnCancel")
public Mono<String> testOnCancel() {
    return Mono.just("1")
            .flatMap(str -> Mono.just("test" + str))
            .delayElement(Duration.ofMinutes(2))
            .doOnCancel(() -> System.out.println("on cancel"))
            .doOnSuccess(str -> System.out.println("success"))
            .doOnError(throwable -> {
                // 识别连接断开类错误(如IOException是TCP连接关闭的常见异常)
                if (throwable instanceof IOException || 
                    (throwable.getCause() != null && throwable.getCause() instanceof IOException)) {
                    System.err.printf("连接断开错误: %s%n", throwable.getMessage());
                } else {
                    System.err.printf("业务逻辑错误: %s%n", throwable.getMessage());
                }
            })
            .flatMap(str -> Mono.just("byby" + str));
}

二、避免Mono因浏览器中断触发doOnCancel

核心思路是将业务逻辑与HTTP请求的订阅生命周期解耦,让后台任务不受HTTP连接状态影响:

方案1:使用detach()分离订阅上下文

通过detach()让Mono的执行脱离当前HTTP请求的订阅上下文,即使浏览器断开连接,后台任务仍会继续执行,且不会触发doOnCancel:

@GetMapping("/api/testOnCancel")
public Mono<String> testOnCancel() {
    // 定义独立的后台任务
    Mono<String> backgroundTask = Mono.just("1")
            .flatMap(str -> Mono.just("test" + str))
            .delayElement(Duration.ofMinutes(2))
            .doOnSuccess(str -> System.out.println("后台任务执行完成: " + str))
            .doOnError(System.err::println)
            .flatMap(str -> Mono.just("byby" + str))
            // 分离订阅上下文,HTTP断开不会取消此任务
            .detach()
            // 放到弹性线程池执行,避免阻塞IO线程
            .subscribeOn(Schedulers.boundedElastic());

    // HTTP接口立即返回响应,后台任务异步执行
    return Mono.just("请求已受理,后台正在处理")
            .doOnSuccess(v -> backgroundTask.subscribe());
}

方案2:使用消息队列彻底解耦

如果业务允许,将耗时任务提交到消息队列(如Redis Stream、RabbitMQ),HTTP接口直接返回确认,后台服务消费队列执行任务,完全脱离HTTP请求生命周期。


内容的提问来源于stack exchange,提问作者dubbing youtube

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 11:45:23