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
First of all, we need to configure our message broker and WebSocket application endpoint:
@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.