A Quick Example of Spring Websockets’ @SendToUser Annotation
1. Overview
In this quick tutorial, we’re going to illustrate how to send a message to a specific session or particular user using Spring WebSockets.
For an introduction to the above module, please refer to this article.
2. WebSocket Configuration
@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");
}
}
With @EnableWebSocketMessageBroker we enabled a broker-backed messaging over WebSocket using *STOMP*, which stands for Streaming Text Oriented Messaging Protocol. It’s important to remark that this annotation needs to be used in conjunction with the @Configuration.
It isn’t mandatory to extend the AbstractWebSocketMessageBrokerConfigurer but, for the quick example, it’s easier to customize the imported configuration.
In the first method, we set up a simple memory-based message broker to carry the messages back to the client on destinations prefixed with “/topic” and “/queue”.
And, in the second, we registered stomp endpoints at “/greeting”.
In case that we want to enable SockJS, we have to amend the register part:
registry.addEndpoint("/greeting").withSockJS();
3. Get Session Id By Interceptor
One way to obtain the session id is adding a Spring Interceptor which will be trigger during the handshake and get the information from the request data.
This interceptor can be added directly in 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 Endpoint
Starting with Spring 5.0.5.RELEASE, it isn’t necessary to do any customization because of the improvement of @SendToUser annotation, that allows us to send a message to a user destination via “/user/{sessionId}/…” rather than “/user/{user}/…“.
That means the annotation works relying on the session id of the input message, effectively sending a reply to destination private to the session:
@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();
}
}
It’s import to remark that, @SendToUser indicates that the return value of a message-handling method should be sent as a Message to the specified destination(s) prepended with “/user/{username}“.
5. WebSocket Client
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"); }
A new WebSocket is created pointing to “/greeting” for the mapping in WebSocketConfiguration.
When we subscribe the client to “/user/queue/errors” and “/user/queue/reply” is where we use the remarked information from the last section.
As we can see, @SendToUser points to “queue/errors” but the message will be sent to “/user/queue/errors“.
6. Conclusion
In this article, we’have explored a way to send a message directly to a user or session id with Spring WebSocket
As always, the full source code of the examples is available over on GitHub.