【问题标题】:Disconnect client session from Spring websocket stomp server从 Spring websocket stomp 服务器断开客户端会话
【发布时间】:2015-04-17 14:09:36
【问题描述】:

我已经搜索了很多,但无法找到:Spring websocket stomp 服务器是否可以根据 sessionId(或实际上基于任何东西)断开客户端?

在我看来,一旦客户端连接到服务器,就没有任何东西可以让服务器断开客户端的连接。

【问题讨论】:

    标签: spring spring-boot stomp spring-websocket


    【解决方案1】:

    实际上使用一些变通方法可以实现您想要的。 为此,您应该这样做:

    1. 使用 java 配置(不确定是否可以使用 XML 配置)
    2. WebSocketMessageBrokerConfigurationSupport 扩展您的配置类并实现 WebSocketMessageBrokerConfigurer 接口
    3. 创建自定义子协议 websocket 处理程序并从 SubProtocolWebSocketHandler 类扩展它
    4. 在您的自定义子协议 websocket 处理程序中覆盖 afterConnectionEstablished 方法,您将可以访问 WebSocketSession :)

    我创建了示例 spring-boot 项目来展示我们如何断开客户端会话与服务器端的连接: https://github.com/isaranchuk/spring-websocket-disconnect

    【讨论】:

    • 这个回复应该被接受,它不需要客户合作
    • 这仍然是关闭 websocket 的唯一方法吗?
    • 这行得通。虽然我不得不添加 spring.main.allow-bean-definition-overriding=true 这个作为 SubProtocolWebSocketHandler 的 bean 已经存在
    【解决方案2】:

    您还可以通过实现自定义WebSocketHandlerDecorator 来断开会话:

    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig<S extends ExpiringSession> extends AbstractSessionWebSocketMessageBrokerConfigurer<S> {
    
        @Override
        public void configureWebSocketTransport(final WebSocketTransportRegistration registration) {
            registration.addDecoratorFactory(new WebSocketHandlerDecoratorFactory() {
                @Override
                public WebSocketHandler decorate(final WebSocketHandler handler) {
                    return new WebSocketHandlerDecorator(handler) {
                        @Override
                        public void afterConnectionEstablished(final WebSocketSession session) throws Exception {
    
                            session.close(CloseStatus.NOT_ACCEPTABLE);
                            super.afterConnectionEstablished(session);
                        }
                    };
                }
            });
            super.configureWebSocketTransport(registration);
        }
    
    
        @Override
        protected void configureStompEndpoints(final StompEndpointRegistry registry) {
        registry.addEndpoint("/home")
                .setHandshakeHandler(new DefaultHandshakeHandler(
                        new UndertowRequestUpgradeStrategy() // If you use undertow
                        // new JettyRequestUpgradeStrategy()
                        // new TomcatRequestUpgradeStrategy()
                ))
                .withSockJS();
        }
    }
    

    【讨论】:

    • 您好,感谢您的解决方案。就是想。如果代码命中afterConnectionEstablished 时消息仍未处理怎么办。这种情况可能吗?
    • 所以我尝试了您的代码,甚至在处理消息之前会话就被关闭了。
    【解决方案3】:

    据我所知,API 没有提供您正在寻找的内容,在服务器端您只能检测断开连接事件。如果您想断开某个客户端的连接,我认为您必须采取一些解决方法,例如这个:

    1. 编写一个能够触发断开连接的客户端javascript函数
    2. 一旦您的客户端连接到服务器,在您的 javascript 中生成一个客户端 ID 并将其发送到服务器。记住客户端上的 ID,您将在第 (4) 步中用到它。
    3. 当您希望服务器断开与特定客户端(由 ID 标识)的连接时,将包含 ID 的消息发送回客户端。
    4. 现在您的客户端 javascript 评估从服务器发送的消息并决定调用您在步骤 (1) 中编写的断开连接函数。
    5. 您的客户端自行断开连接。

    解决方法有点麻烦,但会奏效。

    【讨论】:

    • 不幸的是,假设客户端遵守断开连接消息。最终,我不希望客户对此有发言权。标记为答案,因为它似乎不可能。
    • 根据SubProtocolWebSocketHandler.javaafterConnectionEstablished()方法的源码可以阅读:// WebSocketHandlerDecorator could close the session
    【解决方案4】:

    我依靠@Dániel Kis 的想法并实现了websocket 会话管理,其关键点是将经过身份验证的用户的websocket 会话存储在类似Singleton 的对象中。

    // WebSocketConfig.java
    
    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    
        @Override
        public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
            registration.addDecoratorFactory(new WebSocketHandlerDecoratorFactory() {
                @Override
                public WebSocketHandler decorate(final WebSocketHandler handler) {
                    return new WebSocketHandlerDecorator(handler) {
    
                        @Override
                        public void afterConnectionEstablished(final WebSocketSession session) throws Exception {
    
                            // We will store current user's session into WebsocketSessionHolder after connection is established
                            String username = session.getPrincipal().getName();
                            WebsocketSessionHolder.addSession(username, session);
    
                            super.afterConnectionEstablished(session);
                        }
                    };
                }
            });
        }
    }
    

    存储 websocket 用户会话的类 WebsocketSessionHolder。我使用“同步”块来保证线程安全。实际上,这些块并不是昂贵的操作,因为每个方法(addSession 和 closeSessions)都不经常使用(在建立和终止连接时)。此处无需使用 ConcurrentHashMap 或 SynchronizedMap,因为我们在这些方法中对列表执行了一堆操作。

    // WebsocketSessionHolder.java
    
    public class WebsocketSessionHolder {
    
        static {
            sessions = new HashMap<>();
        }
        
        // key - username, value - List of user's sessions
        private static Map<String, List<WebSocketSession>> sessions;
    
        public static void addSession(String username, WebSocketSession session)
        {
            synchronized (sessions) {
                var userSessions = sessions.get(username);
                if (userSessions == null)
                    userSessions = new ArrayList<WebSocketSession>();
    
                userSessions.add(session);
                sessions.put(username, userSessions);
            }
        }
    
        public static void closeSessions(String username) throws IOException 
        {
            synchronized (sessions) {
                var userSessions = sessions.get(username);
                if (userSessions != null)
                {
                    for(var session : userSessions) {
                        // I use POLICY_VIOLATION to indicate reason of disconnecting for a client
                        session.close(CloseStatus.POLICY_VIOLATION);
                    }
                    sessions.remove(username);
                }
            }
        }
    }
    

    最后一步 - 终止(断开)指定的用户 websocket 会话(示例中为“ADMIN”),例如在某些控制器中

    //PageController.java
    
    @Controller
    public class PageController {
        @GetMapping("/kill-sessions")
        public void killSessions() throws Exception {
    
            WebsocketSessionHolder.closeSessions("ADMIN");
        }
    }
    

    【讨论】:

      【解决方案5】:

      如果是 xml 配置,您可以在 &lt;websocket:message-broker&gt;&lt;websocket:transport&gt; 中使用 &lt;websocket:decorator-factories&gt;。 创建自定义 WebSocketHandlerDecoratorWebSocketHandlerDecoratorFactory 实现 decorate 方法。

      【讨论】:

        【解决方案6】:

        这可能看起来很简短,但我不确定在您的情况下实现会是什么样子。但是,我认为在某些情况下需要这种解决方法/解决方案:

        1. 在后端设置超时(比如 30 秒):
          • 这就是使用 Spring Boot Websocket(和 Tomcat)的方式:
            @Bean
            public ServletServerContainerFactoryBean websocketContainer() {
                ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
                container.setMaxSessionIdleTimeout(MAX_SESSION_IDLE_TIMEOUT); 
                return container;
            }
        
        1. 如果您想保持会话打开 - 继续发送消息或主动发送 ping/pong。如果您希望会话断开连接,请在您的应用程序中合适的地方停止 ping/pong 交互。

        当然,如果您想立即断开连接,这似乎不是一个合适的解决方案。但是,如果您只是想减少活动连接的数量,那么 ping/pong 可能是一个不错的选择,因为它仅在主动发送消息时才保持会话​​打开,从而防止会话过早关闭。

        【讨论】:

          【解决方案7】:

          首先你必须通过继承引入一个类作为你的用户类,然后像这样使用它:

          if (userObject instanceof User) {
              User user = (User) userObject;
              if (user.getId().equals(userDTO.getId())) {
                 for (SessionInformation information : sessionRegistry.getAllSessions(user, true)) {
                    information.expireNow();
                 }
              }
          }    
          

          【讨论】:

            猜你喜欢
            • 2017-05-27
            • 1970-01-01
            • 2021-02-08
            • 1970-01-01
            • 2011-07-19
            • 2014-08-22
            • 2012-03-21
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多