java-ee – Tyrus websocket:IllegalStateException无法为非异步请求设置WriteListener

我有一个基于Tyrus实现的标准websocket端点,有时会触发java.lang.IllegalStateException:无法为非异步或非升级请求设置WriteListener.我们正在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谈论这个问题.

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

挖掘Tyrus实施

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

问题

>有人已经遇到过这样的情况吗?如果是的话,异常是否真的意味着某个地方存在瓶颈,或者意味着其他什么?
> tyrus异步端点是否真的异步,即“进程即忘”?
>我没有找到任何方法来排队和传出消息排队:如果消息A很长,等待消息A发送完成后再发送消息B.有没有办法处理websocket中的大消息或异步端点是唯一办法?
>我想确保发送没有遇到任何问题,因此我选择了异步解决方案.我应该回到同步方式吗?

我没有详细说明我的Tyrus调查.如果你觉得它有用,请随意提问,我很乐意发展.

最佳答案

java.lang.IllegalStateException: Cannot set WriteListener for non-async or non-upgrade request

为了使请求完全异步,请求响应链中的任何Filter必须明确设置为支持异步请求.特别是那些映射在/ *上的“全能”过滤器.

如果通过< filter>注册过滤器在web.xml中输入,这可以通过设置子元素< async-supported>来完成.为真.

<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说:

Throws
IllegalStateException – if this request is within the scope of a filter or servlet that does not support asynchronous operations (that is, isAsyncSupported() returns false), or if this method is called again without any asynchronous dispatch (resulting from one of the AsyncContext.dispatch() methods), is called outside the scope of any such dispatch, or is called again within the scope of the same dispatch, or if the response has already been closed

isAsyncSupported()默认为false,以便不使用实现不佳的servlet过滤器破坏现有的Web应用程序.从技术上讲,仅将目标Servlet标记为支持异步并将过滤器单独保留就足够了.一个理智的“catch-all”过滤器不会向HTTP响应显式写入任何内容,但Servlet API从未禁止过,因此不幸存在这样的过滤器.

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

转载注明原文:java-ee – Tyrus websocket:IllegalStateException无法为非异步请求设置WriteListener - 代码日志