websocket stomp连接一段时间后断开

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了websocket stomp连接一段时间后断开相关的知识,希望对你有一定的参考价值。

参考技术A 因为项目中存在频繁的由服务器发起的数据交换,相比使用Ajax轮训的方式,websocket长连接和双向保持的特点能够较好的提升数据交换的性能。
为了简便,直接使用spring boot + shiro + stomp和socketJs作为构建的工具。
但是由于使用时,主要是由服务端进行数据的推送,通过stomp自行保持心跳,就会存在session过期导致连接断开的情况,而且由于stomp本身不会提示错误原因,导致排查起来比较麻烦,因此记录下整个纠错过程以备忘。

在一开始打开时,一切正常,数据能推送成功,但是在大约10分钟左右(时长不定)能通过chrome的前端调试工具发现stomp提示连接断开。服务器端无任何异常提示。

因为服务器端无任何异常,且断连时长不定,因此推测为可能是session导致的自动断链或者心跳丢失导致的。因为stomp本身不会报错误原因,因此想要找到具体的错误原因比较麻烦。后来发现可以通过断点到stomp的onclose时间就能够获取到具体的错误码,这就为我们定位问题提供了很好的帮助(具体断点位置如下图所示)

最后通过错误码发现,提示的错误码是1008,reason部分提示的是http session被关闭,因此问题就比较清楚了,是由于session释放导致的断链。这一点就很有意思,因为在使用过程中为了避免不必要的数据传输就一直没有发起websocket的send事件,只是一直在subscribe监听服务器的推送。因此导致了服务器的session一直没有被touch(),从而释放。(其实个人觉得这个可能也是一个比较有意思的问题,为什么心跳没有被处理会触发到session,具体没有试验是不是和我使用了shiro的session作为管理有关,有兴趣的同学们可以尝试一下)
这里也顺便贴上对于错误码的描述和相关参考资料,便于大家解决其它的错误码问题:

那么问题找到了响应的解决方案就比较好处理了,但是究竟哪种方案比较好,还是得看具体的业务需求和对利弊的取舍。

问题本身倒是不复杂,就是对stomp不熟悉,确定具体的错误原因上花了很多时间,进行了很多白费的尝试,所以希望能记录下,做个以后的备忘,防止再走很多弯路。

在 Stomp Disconnect 命令上拒绝 Spring WebSocket 访问

【中文标题】在 Stomp Disconnect 命令上拒绝 Spring WebSocket 访问【英文标题】:Spring WebSocket Access Denied on Stomp Disconnect Command 【发布时间】:2021-08-18 17:28:07 【问题描述】:

我遇到的一个问题需要您的帮助。我正在使用 Spring WebSocket |弹簧安全。

发生了什么

Stomp 客户端能够成功连接,但一段时间后我收到此异常

2021-05-31 11:35:24.563  INFO 1 --- [nio-8080-exec-4] o.s.w.s.m.SubProtocolWebSocketHandler    : No messages received after 114650 ms. Closing HtmlFileStreamingSockJsSession[id=hkui12fj].
Error Occured on stomp Command : DISCONNECT
2021-05-31 11:35:24.563 ERROR 1 --- [nio-8080-exec-4] c.s.c.w.e.ConnectDisconnectEventHandler  : Error Occured in the system null
2021-05-31 11:35:24.564  WARN 1 --- [nio-8080-exec-4] w.s.h.ExceptionWebSocketHandlerDecorator : Unhandled exception after connection closed for ExceptionWebSocketHandlerDecorator [delegate=LoggingWebSocketHandlerDecorator [delegate=SubProtocolWebSocketHandler[StompSubProtocolHandler[v10.stomp, v11.stomp, v12.stomp]]]]

org.springframework.messaging.MessageDeliveryException: Failed to send message to ExecutorSubscribableChannel[clientInboundChannel]; nested exception is org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:149) ~[spring-messaging-5.3.5.jar!/:5.3.5]
    at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:125) ~[spring-messaging-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.messaging.StompSubProtocolHandler.afterSessionEnded(StompSubProtocolHandler.java:650) ~[spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.messaging.SubProtocolWebSocketHandler.clearSession(SubProtocolWebSocketHandler.java:530) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.messaging.SubProtocolWebSocketHandler.afterConnectionClosed(SubProtocolWebSocketHandler.java:399) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.handler.WebSocketHandlerDecorator.afterConnectionClosed(WebSocketHandlerDecorator.java:85) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator.afterConnectionClosed(LoggingWebSocketHandlerDecorator.java:72) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator.afterConnectionClosed(ExceptionWebSocketHandlerDecorator.java:78) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession.close(AbstractSockJsSession.java:224) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.handler.WebSocketSessionDecorator.close(WebSocketSessionDecorator.java:160) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator.close(ConcurrentWebSocketSessionDecorator.java:271) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.messaging.SubProtocolWebSocketHandler.checkSessions(SubProtocolWebSocketHandler.java:507) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.messaging.SubProtocolWebSocketHandler.handleMessage(SubProtocolWebSocketHandler.java:339) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.handler.WebSocketHandlerDecorator.handleMessage(WebSocketHandlerDecorator.java:75) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator.handleMessage(LoggingWebSocketHandlerDecorator.java:56) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator.handleMessage(ExceptionWebSocketHandlerDecorator.java:58) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.sockjs.transport.session.AbstractSockJsSession.delegateMessages(AbstractSockJsSession.java:387) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSession.handleMessage(WebSocketServerSockJsSession.java:195) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.sockjs.transport.handler.SockJsWebSocketHandler.handleTextMessage(SockJsWebSocketHandler.java:93) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.handler.AbstractWebSocketHandler.handleMessage(AbstractWebSocketHandler.java:43) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.handleTextMessage(StandardWebSocketHandlerAdapter.java:114) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.access$000(StandardWebSocketHandlerAdapter.java:43) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:85) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:82) [spring-websocket-5.3.5.jar!/:5.3.5]
    at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:415) [tomcat-embed-websocket-9.0.44.jar!/:9.0.44]
    at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageText(WsFrameServer.java:129) [tomcat-embed-websocket-9.0.44.jar!/:9.0.44]
    at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:515) [tomcat-embed-websocket-9.0.44.jar!/:9.0.44]
    at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:301) [tomcat-embed-websocket-9.0.44.jar!/:9.0.44]
    at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:133) [tomcat-embed-websocket-9.0.44.jar!/:9.0.44]
    at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:85) [tomcat-embed-websocket-9.0.44.jar!/:9.0.44]
    at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:183) [tomcat-embed-websocket-9.0.44.jar!/:9.0.44]
    at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:162) [tomcat-embed-websocket-9.0.44.jar!/:9.0.44]
    at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:156) [tomcat-embed-websocket-9.0.44.jar!/:9.0.44]
    at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:60) [tomcat-embed-core-9.0.44.jar!/:9.0.44]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:59) [tomcat-embed-core-9.0.44.jar!/:9.0.44]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) [tomcat-embed-core-9.0.44.jar!/:9.0.44]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707) [tomcat-embed-core-9.0.44.jar!/:9.0.44]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.44.jar!/:9.0.44]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_212]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_212]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.44.jar!/:9.0.44]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_212]
Caused by: org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:73) ~[spring-security-core-5.4.5.jar!/:5.4.5]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.attemptAuthorization(AbstractSecurityInterceptor.java:238) ~[spring-security-core-5.4.5.jar!/:5.4.5]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:208) ~[spring-security-core-5.4.5.jar!/:5.4.5]
    at org.springframework.security.messaging.access.intercept.ChannelSecurityInterceptor.preSend(ChannelSecurityInterceptor.java:70) ~[spring-security-messaging-5.4.5.jar!/:5.4.5]
    at org.springframework.messaging.support.AbstractMessageChannel$ChannelInterceptorChain.applyPreSend(AbstractMessageChannel.java:181) ~[spring-messaging-5.3.5.jar!/:5.3.5]
    at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:135) ~[spring-messaging-5.3.5.jar!/:5.3.5]
    ... 41 common frames omitted

我发现它发生在 Stomp Disconnect Command 上。

WebSocket 安全配置只需几件事。

@Override
    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) 
        messages.anyMessage().authenticated();
    

    @Override
    protected boolean sameOriginDisabled() 
        return true;
    

任何帮助将不胜感激。

我有两个以这种方式注册的拦截器。

首先WebSocketAuthenticationFilter它只是一个通道拦截器,用于拦截传入的请求并将用户设置为在websocket中经过身份验证。

第二个 - ConnectDisconnectChannelInterceptor 我跟踪用户何时连接和断开连接。

【问题讨论】:

【参考方案1】:
 @Override
    protected void configureInbound(MessageSecurityMetadataSourceRegistry messageSecurityMetadataSourceRegistry) 
        messageSecurityMetadataSourceRegistry
                .simpTypeMatchers(SimpMessageType.CONNECT, SimpMessageType.DISCONNECT, SimpMessageType.OTHER).permitAll()
                .anyMessage().authenticated();
    

【讨论】:

您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center。

以上是关于websocket stomp连接一段时间后断开的主要内容,如果未能解决你的问题,请参考以下文章

通过 Spring Websocket STOMP 打开连接会导致我们的服务器死机

Websocket 连接断开 - Apache MQ

在 Stomp Disconnect 命令上拒绝 Spring WebSocket 访问

nodejs socket 怎么检测客户端主动断开连接

jquery stomp websockets服务器重新连接重新初始化

Spring 4 websocket和stomp - 未到达控制器