我有一个基于 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)
第二次 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);
}
我也有例外。
到目前为止和症状
令人惊讶的是,我只发现一个链接谈论这个问题。
- github 链接突出显示了缓冲区大小问题。我不使用部分消息,只使用完整消息。此外,无论我使用默认缓冲区大小还是设置新缓冲区,都会出现异常
- 我找不到关于如何重现错误的全局规则
- 引发异常后,客户端可以继续发送消息,服务器会处理它,但服务器从未回复客户端。传出通信通道似乎被阻塞
- 由于服务器一直在处理传入的消息,异常后websocket通道没有关闭
挖掘 Tyrus 的实现
我浏览了 tyrus-core 的实现,发现发送方法依赖于一些 Grizzly 组件。我对灰熊一无所知,但由于灰熊的一些限制,发送似乎无论如何都必须是同步的
问题
- 有人遇到过这种情况吗?如果是,异常是否真的意味着某处存在瓶颈,或者它意味着其他什么?
- tyrus 异步端点真的是异步的,即像“处理后忘记”吗?
- 我还没有找到任何方法来排队和传出消息:如果消息 A 很长,请在发送消息 B 之前等待消息 A 发送完成。有没有办法在 websocket 中处理大消息或异步端点是唯一的方式?
- 我想确保发送没有遇到任何问题,因此我选择了异步解决方案。我应该回到同步方式吗?
我没有详细说明我的 Tyrus 调查。如果您觉得它相关,请随时询问,我会很乐意发展。