1、概览 {#1概览}
本文将带你了解如何在 Spring WebSockets 中使用 @SendToUser
注解向特定 Session 或特定用户发送消息。
有关上述 Spring WebSockets 的介绍,请参阅 上一篇文章。
2、WebSocket 配置 {#2websocket-配置}
首先,需要配置 Message Broker 和 WebSocket 应用端点:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig
extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic/", "/queue/");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/greeting");
}
}
通过 @EnableWebSocketMessageBroker
注解,启用了在 WebSocket 上使用 STOMP(Streaming Text Oriented Messaging Protocol)的基于 Broker 的消息传递。需要强调的是,这个注解需要与 @Configuration
一起使用。
继承 AbstractWebSocketMessageBrokerConfigurer
并不是必须的,但这可以更容易地自定义导入的配置。
在第一个方法中,建立了一个简单的基于内存的 Message Broker,通过以 /topic
和 /queue
为前缀的目标将消息传回客户端。
第二个方法,注册了 /greeting
stomp 端点。
可以考虑启用 SockJS。
registry.addEndpoint("/greeting").withSockJS();
3、通过 Interceptor 获取 Session ID {#3通过-interceptor-获取-session-id}
获取 Session ID 的一种方法是添加一个 Spring Interceptor,它在握手过程中触发,并从请求数据中获取信息。
此 Interceptor 可直接添加到 WebSocketConfig
中:
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/greeting")
.setHandshakeHandler(new DefaultHandshakeHandler() {
public boolean beforeHandshake(
ServerHttpRequest request,
ServerHttpResponse response,
WebSocketHandler wsHandler,
Map attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest
= (ServletServerHttpRequest) request;
HttpSession session = servletRequest
.getServletRequest().getSession();
attributes.put("sessionId", session.getId());
}
return true;
}}).withSockJS();
}
4、WebSocket 端点 {#4websocket-端点}
从 Spring 5.0.5.RELEASE 开始,无需进行任何自定义,因为 @SendToUser
注解的改进允许我们通过 /user/{sessionId}/...
而不是 /user/{user}/...
向目标用户发送消息。
这意味着该注解依赖于输入消息的 Session ID,从而有效地将回复发送到会话私有的目标位置:
@Controller
public class WebSocketController {
@Autowired
private SimpMessageSendingOperations messagingTemplate;
private Gson gson = new Gson();
@MessageMapping("/message")
@SendToUser("/queue/reply")
public String processMessageFromClient(
@Payload String message,
Principal principal) throws Exception {
return gson
.fromJson(message, Map.class)
.get("name").toString();
}
@MessageExceptionHandler
@SendToUser("/queue/errors")
public String handleException(Throwable exception) {
return exception.getMessage();
}
}
@SendToUser
注解表示消息处理方法的返回值应作为消息发送到指定目标,目标前缀为 /user/{username}
。
5、WebSocket 客户端 {#5websocket-客户端}
function connect() {
var socket = new WebSocket('ws://localhost:8080/greeting');
ws = Stomp.over(socket);
ws.connect({}, function(frame) {
ws.subscribe("/user/queue/errors", function(message) {
alert("Error " + message.body);
});
ws.subscribe("/user/queue/reply", function(message) {
alert("Message " + message.body);
});
}, function(error) {
alert("STOMP error " + error);
});
}
function disconnect() {
if (ws != null) {
ws.close();
}
setConnected(false);
console.log("Disconnected");
}
在 WebSocketConfiguration
中创建了一个新的 WebSocket,将其映射到 /greeting
。
当客户端订阅到 /user/queue/errors
和 /user/queue/reply
时,就会使用上一节中提到的信息。
可以看到,@SendToUser
指向的是 queue/errors
,但消息将被发送到 /user/queue/errors
。
6、总结 {#6总结}
本文介绍了如何在 Spring WebSocket 中使用 @SendToUser
注解直接向用户或 Session ID 发送消息。
Ref:https://www.baeldung.com/spring-websockets-sendtouser