在 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 始终使用queue
和sendToUser
在客户端
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
。例如:它找到了两个会话 wsxedc123
和 thnujm456
,Spring 会将其转换为 2 个目的地 queue/reply-userwsxedc123
和 queue/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 关闭帧?