Tyrus websocket:IllegalStateException 无法为非异步请求设置 WriteListener

Posted

技术标签:

【中文标题】Tyrus websocket:IllegalStateException 无法为非异步请求设置 WriteListener【英文标题】:Tyrus websocket: IllegalStateException cannot set WriteListener for non-async request 【发布时间】:2017-08-03 14:10:04 【问题描述】:

我有一个基于 Tyrus 实现的标准 websocket 端点,它不时触发java.lang.IllegalStateException: Cannot set WriteListener for non-async or non-upgrade request。我们在 Payara 4.1 上运行。

我的标准实现

@ServerEndpoint(value = "...", decoders=MessageDecoder.class, encoders=MessageEncoder.class)
public class EndpointImpl extends AbstractEndpoint
    // onOpen, onClose, onMessage, onError methods

抽象类在哪里

public abstract class AbstractEndpoint

    // irrelevant onOpen, onOpen handling method

117        protected void sendMessage(Session session, Message message)
118            if(message == null)
119                LOGGER.error("null message");
120             else if(!session.isOpen())
121                LOGGER.error("session is not opened");
122             else
>>>123                session.getAsyncRemote().sendObject(message, (result) -> 
124                    if (result.isOK()) 
125                        LOGGER.info("success! yeah!");
126                     else 
127                        LOGGER.error("error when sending message", result.getException());
128                    
129                );
130            
     

IllegalStateException

到目前为止,没有什么特别的。我可以完美地沟通和响应我收到的请求,并且,websocket FTW,我可以推送信息并取回反馈。但是,我有时会收到异常:

java.lang.IllegalStateException: Cannot set WriteListener for non-async or non-upgrade request
        at org.apache.catalina.connector.OutputBuffer.setWriteListener(OutputBuffer.java:536)
        at org.apache.catalina.connector.CoyoteOutputStream.setWriteListener(CoyoteOutputStream.java:223)
        at org.glassfish.tyrus.servlet.TyrusServletWriter.write(TyrusServletWriter.java:140)
        at org.glassfish.tyrus.core.ProtocolHandler.write(ProtocolHandler.java:486)
        at org.glassfish.tyrus.core.ProtocolHandler.send(ProtocolHandler.java:274)
        at org.glassfish.tyrus.core.ProtocolHandler.send(ProtocolHandler.java:332)
        at org.glassfish.tyrus.core.TyrusWebSocket.sendText(TyrusWebSocket.java:317)
        at org.glassfish.tyrus.core.TyrusRemoteEndpoint.sendSyncObject(TyrusRemoteEndpoint.java:429)
        at org.glassfish.tyrus.core.TyrusRemoteEndpoint$Async.sendAsync(TyrusRemoteEndpoint.java:352)
        at org.glassfish.tyrus.core.TyrusRemoteEndpoint$Async.sendObject(TyrusRemoteEndpoint.java:249)
        at com.mycompany.websocket.AbstEndpoint.sendMessage(AbstEndpoint.java:123)

第二次 sendMessage 方法尝试

一开始,我以为我的异步端点配置错误,所以我尝试了Future方式而不是回调方式:

RemoteEndpoint.Async async = session.getAsyncRemote();
async.setSendTimeout(5000); // 5 seconds
Future<Void> future = async.sendObject(message);
try
    future.get();

catch(InterruptedException | ExecutionException ex)
    LOGGER.error("error when sending message", ex);

我也遇到了异常。

到目前为止和症状

令人惊讶的是,我只发现one link 在谈论这个问题。

    github 链接突出显示了缓冲区大小问题。我不使用部分消息,只使用完整消息。此外,无论我是使用默认缓冲区大小还是设置新缓冲区,都会出现异常 我找不到有关如何重现错误的全局规则 引发异常后,客户端可以继续发送消息,服务器会处理它,但服务器从未回复客户端。传出的通信通道似乎被阻塞了 由于服务器一直在处理传入的消息,异常后websocket通道没有关闭

挖掘 Tyrus 实现

我浏览了 tyrus-core 的实现,发现发送方法依赖于某个 Grizzly 组件。我对灰熊一无所知,但由于灰熊的一些限制,发送似乎必须是同步的

问题

    有人遇到过这种情况吗?如果是,该异常是否真的意味着某处存在瓶颈或其他原因? tyrus 异步端点真的是异步的吗,比如“处理后忘记”? 我还没有找到任何方法让和传出消息排队:如果消息 A 很长,在发送消息 B 之前等待消息 A 发送完成。有没有办法在 websocket 或异步端点中处理大消息是唯一的方法吗? 我想确保发送没有遇到任何问题,因此我选择了异步解决方案。我应该回到同步方式吗?

我没有详细说明我的 Tyrus 调查。如果您觉得它相关,请随时询问,我会很乐意发展。

【问题讨论】:

如果管道中有一个未设置 asyncSupported=true 的 servlet 过滤器,则请求不能是异步的。您需要首先通过检查 web.xml、@WebFilter 和 ServletContext#addFilter() 来排除它。 过滤器都是我自己的代码处理的吗?我的意思是,如果我使用的是第三方组件(例如 Apache Shiro 或我不知道的所谓的 Grizzly),是否可能存在一些我无法控制的过滤器? 在 web.xml 中以完全相同的过滤器名称重新定义时,可以覆盖任何第 3 方过滤器。 一些反馈,一周后,我再也没有遇到IllegalStateException。所以这一定是解决方案!由于我不确定所有的来龙去脉,我将等待一个月,然后发布一个自我回答,如果它可以帮助其他人。无论如何,非常感谢@BalusC! 我转发了评论作为答案。 【参考方案1】:

java.lang.IllegalStateException:无法为非异步或非升级请求设置 WriteListener

为了使请求完全异步,请求-响应链中的 any Filter 必须显式设置为支持异步请求。特别是那些映射在/* 上的“包罗万象”过滤器。

如果过滤器是通过web.xml 中的&lt;filter&gt; 条目注册的,这可以通过将子元素&lt;async-supported&gt; 设置为true 来完成。

<filter>
    ...
    <async-supported>true</async-supported>
</filter>

如果过滤器是通过@WebFilter 注解注册的,这可以通过将其asyncSupported 属性设置为true 来完成。

@WebFilter(..., asyncSupported="true")

如果过滤器是通过ServletContext#addFilter()注册的,这可以通过将Registration.Dynamic#setAsyncSupported().设置为true来完成。

Dynamic filter = servletContext.addFilter(name, type);
filter.setAsyncSupported(true);

原因是,WebSocket 实现在握手请求期间在内部使用ServletRequest#startAsync(),以保持请求-响应管道“永远”打开,直到响应显式关闭。 Its javadoc 说:

抛出IllegalStateException - 如果此请求在不支持异步操作的过滤器或 servlet 范围内(即,isAsyncSupported() 返回 false),或者如果在没有任何异步调度的情况下再次调用此方法(由 AsyncContext.dispatch() 方法之一产生),在任何此类调度的范围之外调用,或者在同一调度的范围内再次调用,或者如果响应已经已关闭

isAsyncSupported() 默认为false,以便不使用实施不佳的 servlet 过滤器破坏现有的 Web 应用程序。从技术上讲,只需将目标 Servlet 标记为支持异步并保留过滤器就足够了。一个理智的“包罗万象”Filter 不会明确向 HTTP 响应写入任何内容,但 Servlet API 从未禁止这样做,因此不幸的是,此类过滤器可能存在。

如果您有一个这样的过滤器,那么您应该修复它以不再向响应写入任何内容,以便您可以安全地将其标记为支持异步请求,或者调整其 URL 模式以不覆盖 WebSocket 请求。 IE。不要再将它映射到/*

【讨论】:

BalusC,你能帮我解决这个问题吗? ***.com/questions/45626939/…

以上是关于Tyrus websocket:IllegalStateException 无法为非异步请求设置 WriteListener的主要内容,如果未能解决你的问题,请参考以下文章

tyrus websocket ssl 握手失败

使用 Tyrus javax.websocket 的 Java Websocket 客户端?

Tyrus websocket:IllegalStateException 无法为非异步请求设置 WriteListener

如何以编程方式为 Tyrus WebSocket @ServerEndpoint 启用 WSS

如何使用 Tyrus java 客户端在初始 WebSocket 客户端请求中包含 cookie?

tyrus websocket connectToServer - 如何清理守护线程