在 Spring Websocket 上向特定用户发送消息

Posted

技术标签:

【中文标题】在 Spring Websocket 上向特定用户发送消息【英文标题】:Sending message to specific user on Spring Websocket 【发布时间】:2014-04-17 12:20:45 【问题描述】:

如何仅将 websocket 消息从服务器发送给特定用户?

我的 webapp 有 spring 安全设置并使用 websocket。我在尝试将消息从服​​务器发送到仅限特定用户时遇到了棘手的问题。

我阅读the manual的理解来自我们可以做的服务器

simpMessagingTemplate.convertAndSend("/user/username/reply", reply);

在客户端:

stompClient.subscribe('/user/reply', handler);

但我永远无法调用订阅回调。我尝试了许多不同的路径,但没有运气。

如果我将它发送到 /topic/reply,它会起作用,但所有其他连接的用户也会收到它。

为了说明问题,我在 github 上创建了这个小项目:https://github.com/gerrytan/wsproblem

重现步骤:

1) 克隆并构建项目(确保您使用的是 jdk 1.7 和 maven 3.1)

$ git clone https://github.com/gerrytan/wsproblem.git
$ cd wsproblem
$ mvn jetty:run

2) 导航到http://localhost:8080,使用 bob/test 或 jim/test 登录

3) 单击“请求用户特定消息”。预期:仅针对此用户的“仅接收给我的消息”旁边显示消息“hello username”,实际:未收到任何内容

【问题讨论】:

你一直在看 convertAndSendToUser(String user, String destination, T message) 吗? docs.spring.io/spring/docs/4.0.0.M3/javadoc-api/org/… 是的也试过了,但没有运气 我一直在从事私人项目,这是我们使用的方法,它对我们有用。我认为一个潜在的问题可能是您订阅了“/user/reply”并且您正在向“/user/username/reply”发送消息。我认为您应该删除 username 部分并使用 convertAndSendToUser(String user, String destination, T message)。 谢谢,但我试过simpMessagingTemplate.convertAndSendToUser(principal.getName(), "/user/reply", reply);,当从服务器发送消息时,它会抛出这个异常java.lang.IllegalArgumentException: Expected destination pattern "/principal/userId/**" @ViktorK。是对的,你非常接近正确的解决方案。您在客户端的订阅是正确的,您只需尝试:convertAndSendToUser(principal.getName(), "/reply", reply); 【参考方案1】:

我也是这样做的,而且它在不使用用户的情况下工作

@Configuration
@EnableWebSocketMessageBroker  
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer 

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) 
       registry.addEndpoint("/gs-guide-websocket").withSockJS();
    

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) 
        config.enableSimpleBroker("/topic" , "/queue");
        config.setApplicationDestinationPrefixes("/app");
    

【讨论】:

嘿,你在哪里用过这个/app?在哪种情况下?【参考方案2】:

哦,client side no need to known about current user,服务器会帮你做的。

在服务器端,使用以下方式向用户发送消息:

simpMessagingTemplate.convertAndSendToUser(username, "/queue/reply", message);

注意:使用queue,而不是topic,Spring 始终使用queuesendToUser

在客户端

stompClient.subscribe("/user/queue/reply", handler);

解释

当任何 websocket 连接打开时,Spring 会为其分配一个session id(不是HttpSession,为每个连接分配)。当您的客户端订阅以/user/ 开头的频道时,例如:/user/queue/reply,您的服务器实例将订阅名为queue/reply-user[session id] 的队列

当使用发送消息给用户时,例如:用户名是admin 你会写simpMessagingTemplate.convertAndSendToUser("admin", "/queue/reply", message);

Spring 将确定哪个session id 映射到用户admin。例如:它找到了两个会话 wsxedc123thnujm456,Spring 会将其转换为 2 个目的地 queue/reply-userwsxedc123queue/reply-userthnujm456,并将您的带有 2 个目的地的消息发送到您的消息代理。

消息代理接收消息并将其提供回您的服务器实例,该实例持有与每个会话对应的会话(WebSocket 会话可以由一个或多个服务器持有)。 Spring 会将消息翻译为destination(例如:user/queue/reply)和session id(例如:wsxedc123)。然后,它将消息发送到相应的Websocket session

【讨论】:

你能解释一下你是怎么知道用户名的吗?在您的示例中,您说用户名是“管理员”,您从用户那里收到用户名? 用户名是在发起websocket连接时从HttpSession获取的 请您提供更多关于如何设置用户名的信息?无论如何我可以在订阅消息中发送用户名吗? @Andres:您可以扩展DefaultHandshakeHandler 并覆盖方法determineUser 你的解释很好。但是官方文档中的queue/reply-user[session id]部分在哪里?【参考方案3】:

我也使用 STOMP 创建了一个示例 websocket 项目。 我注意到的是

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer 

@Override
public void configureMessageBroker(MessageBrokerRegistry config) 
    config.enableSimpleBroker("/topic", "/queue");// including /user also works
    config.setApplicationDestinationPrefixes("/app");


@Override
public void registerStompEndpoints(StompEndpointRegistry registry) 
    registry.addEndpoint("/getfeeds").withSockJS();

无论“/user”是否包含在 config.enableSimpleBroker(...

【讨论】:

【参考方案4】:

我的解决方案基于 Thanh Nguyen Van 的最佳解释,但另外我还配置了 MessageBrokerRegistry:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer 

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) 
        config.enableSimpleBroker("/queue/", "/topic/");
        ...
    
    ...

【讨论】:

【参考方案5】:

啊,我发现了我的问题所在。首先我没有在简单代理上注册/user前缀

<websocket:simple-broker prefix="/topic,/user" />

那我发送时不需要额外的/user前缀:

convertAndSendToUser(principal.getName(), "/reply", reply);

Spring 会自动将"/user/" + principal.getName() 添加到目的地,因此它会解析为“/user/bob/reply”。

这也意味着在 javascript 中我必须为每个用户订阅不同的地址

stompClient.subscribe('/user/' + userName + '/reply,...) 

【讨论】:

嗯...有没有办法避免设置用户名客户端?通过更改该值(例如使用其他用户名),您将能够看到其他人的消息。 查看我的解决方案:***.com/questions/25646671/… 如何订阅以通过@RequestMapping@MessageMapping 注释的方法回复用户,请参见此处How to subscribe using Sping Websocket integration on specific userName(userId) + get notifications from method anotated with @RequestMapping? 嘿,我的朋友,你能告诉我这个吗?这个“用户”到底是什么?我正在使用 Spring Security,我的用户(用户名)是电子邮件地址。当每个用户登录时,他都会在 Spring Security 上获得他的 JWT 令牌。 我遇到了同样的问题,搜索了几个小时才得到你的答案。只有你的解释对我有帮助。非常感谢!!

以上是关于在 Spring Websocket 上向特定用户发送消息的主要内容,如果未能解决你的问题,请参考以下文章

弹簧+WebSocket+STOMP。给特定会话的消息(非用户)

JBoss 上带有 SockJS 的 Spring-MVC Websocket:如果没有 IO 事件,无法从 HTTP/1.1 升级

如何在 PHP 服务器上向客户端发送 WebSocket 关闭帧?

如何使用 Spring-websocket 拒绝基于用户权限的主题订阅

私信 spring websocket

多服务器环境中的用户目标? (Spring WebSocket和RabbitMQ)