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

时间:2017-08-03 14:10:04

标签: java-ee websocket grizzly java-websocket tyrus

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

到目前为止,没什么特别的。我可以完美地沟通并回应我收到的请求,我可以推送信息并获得反馈。但是,我有时会收到一个例外:

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正在讨论这个问题。

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

    挖掘Tyrus实施

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

    问题

    1. 有人已经遇到过这样的情况吗?如果是的话,异常是否真的意味着某个地方存在瓶颈,或者意味着什么呢?
    2. tyrus异步端点是否真的异步,即“进程忘记”?
    3. 我没有找到任何方法来排队和传出消息排队:如果消息A很长,请等待消息A发送完成后再发送消息B.有没有办法处理websocket或异步端点中的大消息是唯一的方法吗?
    4. 我想确保发送没有遇到任何问题,因此我选择了异步解决方案。我应该回到同步方式吗?
    5. 我没有详细说明我的Tyrus调查。如果你觉得它有用,请随意提问,我很乐意发展。

1 个答案:

答案 0 :(得分:1)

  

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

为了使请求完全异步,请求响应链中的任何 Filter必须明确设置为支持异步请求。特别是那些&#34; catch-all&#34;映射在/*上的过滤器。

如果过滤器是通过<filter>中的web.xml条目注册的,则可以通过将子元素<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(),以便保持请求 - 响应管道&#34;永远&#34;打开,直到明确关闭响应。 Its javadoc说以下内容:

  

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

isAsyncSupported()默认为false,以便不使用实施不当的servlet过滤器破坏现有的Web应用程序。从技术上讲,仅将目标Servlet标记为支持异步并且仅保留过滤器就足够了。一个理智的&#34;全能&#34; Filter不会向HTTP响应显式写入任何内容,但Servlet API从未禁止过,因此不幸的是存在这样的过滤器。

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