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

Spring Reactive连接终止异常:客户端关闭连接时报错

解决Spring Reactive SSE服务关闭连接时的IOException异常

嘿,我来帮你搞定这个问题!你遇到的java.io.IOException: An established connection was aborted by the software in your host machine异常,其实是SSE长连接场景下的正常“噪音”——当客户端主动断开连接(比如关掉浏览器标签页),服务器端的Reactive流还在尝试发送数据,就会触发这个连接中断的异常。不过我们可以通过几种方式优雅处理,避免日志被这种无关异常刷屏。

为什么会出现这个异常?

你的代码里用Flux.interval和Stream.generate生成了一个无限流,当客户端断开连接时,Reactor底层的Netty网络层会检测到连接已关闭,进而抛出这个IO异常。默认情况下Spring Boot会把它打印出来,但这并不是代码Bug,只是客户端主动断开的预期情况。

具体解决方案

这里有几种实用的处理方式,你可以根据需求选择:

1. 在Flux流上捕获并忽略特定异常

直接在返回的Flux链上添加onErrorResume操作符,判断异常类型和消息,如果是连接中断的异常,就返回空流终止处理,这样就不会输出错误日志了:

@GetMapping(value = "/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Event> getEvents() {
    Flux<Event> eventFlux = Flux.fromStream(Stream.generate(() -> new Event(new Random().nextLong(), "Hello Event")));
    Flux<Long> emitFlux = Flux.interval(Duration.ofSeconds(1));
    
    return Flux.zip(eventFlux, emitFlux)
            .map(Tuple2::getT1)
            .onErrorResume(ex -> {
                // 精准匹配连接中断的IO异常
                if (ex instanceof IOException && ex.getMessage().contains("An established connection was aborted")) {
                    return Flux.empty(); // 返回空流优雅终止
                }
                // 其他异常正常抛出,不影响真正的错误排查
                return Flux.error(ex);
            });
}

2. 通过日志框架过滤该异常

如果不想修改业务代码,也可以在日志配置里添加过滤规则,让日志框架忽略这个特定的异常。比如用Logback的话,在logback.xml里加这段配置:

<logger name="reactor.netty" level="ERROR">
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
        <level>ERROR</level>
        <onMatch>ACCEPT</onMatch>
        <onMismatch>DENY</onMismatch>
    </filter>
    <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
        <evaluator>
            <!-- 匹配异常消息,过滤掉连接中断的日志 -->
            <expression>return message.contains("An established connection was aborted by the software in your host machine");</expression>
        </evaluator>
        <onMatch>DENY</onMatch>
        <onMismatch>ACCEPT</onMismatch>
    </filter>
</logger>

3. 简化流生成逻辑(可选优化)

另外,你原来的流生成方式可以简化一下,用Flux.interval直接生成事件,代码更简洁,底层的异常处理逻辑是一致的:

@GetMapping(value = "/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Event> getEvents() {
    return Flux.interval(Duration.ofSeconds(1))
            .map(tick -> new Event(new Random().nextLong(), "Hello Event"))
            .onErrorResume(ex -> {
                if (ex instanceof IOException && ex.getMessage().contains("An established connection was aborted")) {
                    return Flux.empty();
                }
                return Flux.error(ex);
            });
}

最后再啰嗦一句

这个异常真的不是你的代码有问题,就是SSE长连接的正常现象——客户端随时可能断开,服务器端收到通知就会抛出这个异常。我们的核心目标就是把这种预期内的异常从日志里过滤掉,让日志只保留真正需要关注的错误。

内容的提问来源于stack exchange,提问作者Karan Khanna

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 07:47:54