解决Wildfly26 HTTPS下SSE报错RESTEASY002186的问题
问题现象
- 运行在Wildfly 26上的Web应用使用SSE广播功能,HTTP协议下运行完全正常
- 切换到HTTPS端点后,Wildfly日志输出如下告警:
WARN [org.jboss.resteasy.resteasy_jaxrs.i18n] (default task-1)
RESTEASY002186: Failed to set servlet request into asynchronous mode,
server sent events may not work
- 每次向HTTPS端点注册SSE都会触发该告警,HTTP端点注册无此问题
- 接口测试表现差异:
- curl测试HTTP端点:程序会持续等待事件,接收到事件就持续打印输出,直到手动终止
- curl测试HTTPS端点:返回响应头和HTTP端点完全一致
HTTP/1.1 200 OK Connection: keep-alive Transfer-Encoding: chunked Content-Type: text/event-stream
但打印完注册成功事件后,curl会判定数据流已关闭直接退出,返回命令提示符。
接口逻辑说明:注册端点使用@GET注解标注,生产MediaType.SERVER_SENT_EVENTS类型响应,方法内会创建OutboundSseEvent发送到SseEventSink,用于确认已成功注册到SseBroadcaster实例(该事件就是curl退出前收到并打印的注册成功事件),之后打印注册成功日志再退出方法。HTTP和HTTPS场景下这部分业务逻辑都能正常执行,但由于异步模式设置失败,请求端点方法执行完成后数据流无法保持打开状态。
排查过程
2022年6月6日更新
由于代码运行在隔离网络中,无法直接复制粘贴完整代码,将资源文件精简到最小可运行版本,仅保留客户端注册所需的最简逻辑,问题仍然存在。精简后的核心代码如下:
@Path("sse") public class SseResources { @GET @Produces(MediaType.SERVER_SENT_EVENTS) public void listen(@Context Sse sse, @Context SseEventSink sseEventSink) { SseRegComplete regComplete = new SseRegComplete("sse-server"); OutboundSseEvent event = sse.newEventBuilder() .name(regComplete.getType().toString()) .id(regComplete.getEventId()) .mediaType(MediaType.APPLICATION_JSON_TYPE) .data(SseRegComplete.class, regComplete) .comment("Event Stream Registration Completed Successfully") .build(); sseEventSink.send(event); } }
精简代码前,原资源类声明为@ApplicationScoped,注入Sse实例并维护SseBroadcaster引用用于后续事件推送;同时通过@Observes方法监听待广播的事件(该逻辑已在精简时移除);listen方法中会调用SseBroadcaster.register(sseEventSink)注册sink,后续有更新时调用broadcast(outboundEvent)推送事件。移除所有额外逻辑验证数据流保活能力,问题无改善,仍然会出现RESTEASY002186告警,curl打印完上述代码发送的regComplete事件后依然直接退出。
2022年6月7日更新
按照标准SSL配置流程在全新纯净版Wildfly 26中配置HTTPS端点,SSE功能可正常运行。原问题出在一个已运行多年的存量应用上,该应用6个月前因旧版本Wildfly存在log4j漏洞才升级到Wildfly 26,初步怀疑问题有两个可能方向:一是Wildfly配置错误,迁移时保留了不兼容的旧配置项;二是某个第三方依赖阻止Wildfly启用异步模式。
当前存量应用和纯净测试环境的已知差异点:
- 使用Shiro对接LDAP服务器实现认证授权,首次登录后所有后续请求都携带会话cookie,初步判断该因素不相关,因为SSE注册接口在执行前已经通过了认证
- 应用自带LogBack依赖,配置Wildfly不使用默认日志框架
后续计划先对比两个环境的standalone.xml文件查找核心配置差异,同时排查domain.xml等其他可能存在配置差异的文件。
2022年6月14日更新
确认问题和Shiro的拦截逻辑直接相关:修改web.xml中Shiro的filter-mapping的url-pattern配置,将SSE端点排除在Shiro拦截范围外后,SSE功能完全正常。
根因与修复
根因
Shiro默认Filter实现未正确支持Servlet 3.0+异步请求模式,当Shiro Filter拦截到SSE请求时,会在业务方法执行完成后提前关闭响应输出流,导致RESTEASY无法将请求切换为异步模式,SSE连接在初始注册事件发送完成后就被断开,无法保持长连接持续推送事件。
修复方案
两种方案选其一即可:
- 配置Shiro过滤规则,将SSE端点路径排除在Shiro拦截范围之外
- 升级Shiro到支持Servlet 3.0+异步请求的版本,同时在
web.xml的Shiro Filter配置中添加<async-supported>true</async-supported>配置项,确保Filter不会提前终止异步请求的响应流。
内容的提问来源于stack exchange,提问作者Erik Kartzmark

