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



我有一个基于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            }
} 
}

非法状态异常

到目前为止,没有什么特别的。我可以完美地沟通和响应我收到的请求,并且,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)

第二次发送消息方法尝试

起初,我认为我的异步端点配置错误,所以我尝试了 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);
}

我也得到了例外。

到目前为止和症状

令人惊讶的是,我只找到一个讨论这个问题的链接。

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

挖掘泰勒斯实现

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

问题

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

我还没有详细说明我的泰勒斯调查。如果你觉得它相关,请随时询问,我很乐意开发。

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

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

如果过滤器是通过web.xml中的<filter>条目注册的,这可以通过将子元素<async-supported>设置为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(),以便保持请求-响应管道"永远"打开,直到显式关闭响应。它的javadoc说:

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

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

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

相关内容

  • 没有找到相关文章

最新更新