【问题标题】:How to limit the number of stomp clients in Spring, subscribing to a specific topic, based on a condition?如何根据条件限制 Spring 中的 stomp 客户端数量,订阅特定主题?
【发布时间】:2020-10-17 14:33:21
【问题描述】:

我一直在研究一种方法来限制可以订阅特定 stomp 主题但尚未理解的客户数量,这可能是根据我的需要的正确方法。

我的用例是一个游戏,我正在使用 Angular(ng2-stompjs stomp 客户端)和 Spring Boot Websockets(目前正在使用 Spring in-memory 消息代理)进行开发。

这个想法是用户可以连接并订阅“/大厅”踩踏主题,在那里他可以看到打开的游戏房间,这些房间可能处于不同的状态。例如,由于加入的玩家数量少,进行中或尚未开始。 我想拦截并以编程方式将客户端的可能订阅限制为特定的“/room/{roomId}”主题,如果已达到最大玩家数量,例如 4。也可能有一些简单的客户端验证来限制这一点,但我认为只有客户端是不够的

所以我的主要问题是: Spring中如何拦截特定的stomp主题订阅? 是否可以向客户端请求者返回某种无法完成订阅的错误消息?

非常感谢您的帮助,在此先感谢您!

【问题讨论】:

  • 你能提供一个代码sn-p,配置,或者你到目前为止尝试过的。

标签: spring-boot websocket spring-websocket stomp


【解决方案1】:

您可以实现一个监听订阅的 StompEventListener,在此我们可以将目的地(房间号)与该特定房间中的玩家数量进行映射。如果计数已经达到最大值,则拒绝订阅。

@Service
class StompEventListener() {
   private Map<String, int> roomIdVsPlayerCount = new HashMap<>();
   
   @EventListener
   public void handleSubscription(SessionSubscribe event) {
     StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage());
     String destination = accessor.getDestination();

     String roomId = destination.substring(...); //Parsed RoomID
     if(roomIdVsPlayerCount.get(roomId) == MAX_ALLOWED_PLAYERS) {
       //Throw exception which will terminate that client connection 
         or, send an error message like so:
       simpMessagingTemplate.convertAndSend(<some_error_message>);
       return;
     }
     //So it is not at maximum do further logic to actually subscribe 
       user and
     roomIdVsPlayerCount.get(roomId) += 1; 
   }

   @EventListener
   public void handleUnsubscription(SessionUnsubscribe event) {
    ...
   }
}

有用的参考资料:

  1. SessionSubscribeEvent(用于处理订阅)
  2. ConvertAndSend。 (用于向客户端发送错误消息。)

编辑

请尝试从channel Interceptor 发送异常,因为上面没有发送异常,以便将其传播到客户端。我们之前定义的映射可以定义为一个单独的类中的 bean,可访问(使用 @Autowired)事件处理程序(用于递增和递减)和 TopicSubscriptionInterceptor(用于验证)。

@Component
class TopicSubscriptionInterceptor implements ChannelInterceptor {
    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel){
      StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
      String destination = accessor.getDestination();

      String roomId = destination.substring(...); //Parsed RoomID
      if(roomIdVsPlayerCount.get(roomId) == MAX_ALLOWED_PLAYERS) {
        //Throw exception which will terminate that client connection 
      }
     
     //Since it is not at limit continue 
    
    }
}

实现TopicSubscriptionInterceptor的有用参考:TopicSubscriptionInterceptor

【讨论】:

  • 看起来是个不错的建议。虽然我似乎无法停止实际订阅。尝试抛出异常,甚至使用 simpMessagingTemplate 发回消息,实际订阅发生并且客户端继续接收消息
  • @Luis 已添加修复,请在实现拦截器后查看。
  • 拥有自定义 ChannelInterceptor 必须是正确的方法。我现在将在返回 null 之前向客户端发送一条自定义消息 - 而不是抛出异常。如果我们看一下 ChannelInterceptor#preSend,它在消息发送到通道或消息代理之前被调用,但如果返回 null,则不会发生发送 - 因此代理中的实际订阅不会发生。抛出异常会关闭连接并发送一个错误帧,这在我的情况下不是很有用。不过感谢您的帮助:)
猜你喜欢
  • 2014-11-05
  • 1970-01-01
  • 2019-12-21
  • 2020-04-26
  • 2019-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多