Spring Boot WebSockets notifications - spring

In my Spring Boot application I'm trying to implement a notifications functionality based on WebSockets.
I have provided a following configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/notifications").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic", "/queue");
}
}
and trying to use SimpMessagingTemplate in order to send a message from server side to a specific client(user).
#Autowired
private SimpMessagingTemplate simpMessagingTemplate;
public void sendMessages() {
simpMessagingTemplate.convertAndSendToUser(%user%, "/horray", "Hello, World!");
}
Right now I don't understand a few things:
What value should be used for %user% parameter of
simpMessagingTemplate.convertAndSendToUser method ?
What is the correlation between my /notifications endpoint
registered in WebSocketConfig.registerStompEndpoints method and
destination parameter of
simpMessagingTemplate.convertAndSendToUser method and how to properly use it?
How to protect the users from reading other people's messages on the
client ?

The user parameter is the name that the client use when he subscribes the destination, see Spring Reference Chapter 26.4.11 User Destinations
Destination vs Endpoint:
Endpoint is the url where the websocket/message brocker is listening
Destination is the topic or subject within the message brocker

Related

Postman, Spring boot, WebSocket doesn't send notification

I'm trying send websocket notification to specific endpoint. I created the following configuration
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/my-address");
registry.addEndpoint("/my-address").withSockJS();
}
}
I'm able to connect using postman on address ws://localhost:8080/my-address (session is established). Next I want to send notification to this endpoint within the same application (I'll be generating some messages internally). I use class SimpMessageSendingOperations:
simpMessageSendingOperations.convertAndSend("/my-address", exampleMessage);
None error message is generated and notification does not appear for websocket clients. I also tried
simpMessageSendingOperations.convertAndSend("/topic/my-address", exampleMessage);
and also
simpMessageSendingOperations.convertAndSend("/app/my-address", exampleMessage);
I don't know what I'm doing wrong. Can you help me with this issue?
Thanks in advance for help

Invalid SockJS path /topic/mytopic - required to have 3 path segments

I've got a spring boot app in which I'm adding the websocket feature so that websocket client can make subscription request to subscribe messages off the websocket topic.
In my controller's method, I've added an annotation #SubscribeMapping("/topic/mytopic").
My WebSocketConfig looks like this:
#Configuration
#ComponentScan
#EnableWebSocket
public class WebSocketConfig extends WebSocketMessageBrokerConfiguratioSupport{
#Override
public void registerStompEndpoints(StompEndpointRegistry registry){
registry.addEndpoint("/my-app")
.setAllowedOrigin("*")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry){
registry.enableSimpleBroker("/topic/");
registry.setApplicationDestinationPrefixes("/");
}
}
When I go to the browser and type:
http://localhost:<MY_PORT>/my-app
Then I get a response "Welcome to SockJS!". This indicates that Websocket server is indeed up and running.
But to my surprise, when I'm using my Postman's Websocket feature and trying to do a websocket subscription using the url:
ws://localhost:<MY_PORT>/my-app/topic/mytopic
This error is logged in the console: Invalid SockJS path /topic/mytopic - required to have 3 path segments
and the connection gets disconnected automatically.
Am I doing something wrong here? Please advise.

Scaling web sockets with Spring Boot

I'm considering the performance implications of trying to horizontally scale a server which offers websocket connections to clients.
My current implementation of the server uses Spring Boot on the backend, and uses pure web sockets (without STOMP) to transport messages between the server and client.
In a world of containers and horizontal scaling, how should I design the server that it does not matter which replica the client connects to, that delivery of messages is guaranteed?
I am considering using Redis Pub/Sub for this, however, unsure what it should look like.
Currently, my code looks like the following:
#Configuration
#EnableWebSocket
public class WebSocketServerConfiguration implements WebSocketConfigurer {
#Autowired
protected ControllerHandler webSocketHandler;
#Autowired
private AuthHandshakeInterceptor interceptor;
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(webSocketHandler, "/ws")
.addInterceptors(interceptor)
.setAllowedOriginPatterns("*")
.withSockJS();
}
}
And the webSocketHandler looks like:
#Component
public class ControllerHandler extends TextWebSocketHandler {
#Autowired
private InitController initController;
#Autowired
private ProjectController projectController;
#Autowired ObjectMapper objectMapper;
#Override
protected void handleTextMessage(WebSocketSession session, TextMessage jsonTextMessage) throws Exception {
// get the message from a controller
session.sendMessage(new TextMessage("foo"));
}
}
I am guessing that in the controller handler, I'll need to publish a message to Redis, and that there should also be a subscriber to actually send the message across the websocket?

WebSocket messages are not delivered all the times

I have an application with WebSockets using spring-boot application as backend and Stomp/SockJS in the client side, the spring-boot application consume JMS queue messages and notify the changes to the right user. What is the problem? Sometimes works and sometimes doesn't work, same code and users could work or not.
The client side code is a bit more difficult to copy here because it's integrate over react/redux application but basically is a subscription to two different channels, both defined in the configuration of Spring. The sessions are created correctly according to debug information but just sometimes the message is processed to send it to connected sessions.
This is the configuration class for Spring.
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/stomp")
.setAllowedOrigins("*")
.withSockJS();
}
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry
.setApplicationDestinationPrefixes("/app")
.enableSimpleBroker("/xxxx/yyyy", "/ccccc");
}
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new ChannelInterceptor() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
Object raw = message
.getHeaders()
.get(SimpMessageHeaderAccessor.NATIVE_HEADERS);
if (raw instanceof Map) {
Object name = ((Map<?,?>) raw).get("email");
if (name instanceof LinkedList) {
String user = ((LinkedList<?>) name).get(0).toString();
accessor.setUser(new User(user));
}
}
}
return message;
}
});
}
}
This is the JMS listener to process queue message and send it to specific user.
#Component
public class UserEventListener {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final SimpMessagingTemplate template;
#Autowired
public UserEventListener(SimpMessagingTemplate pTemplate) {
this.template = pTemplate;
}
#JmsListener(destination="user/events")
public void onStatusChange(Map<String, Object> props) {
if (props.containsKey("userEmail")) {
logger.debug("Event for user received: {}", props.get("userEmail"));
template.convertAndSendToUser((String)props.get("userEmail"), "/ccccc", props);
}
}
}
Edit 1:
After more debugging the times when doesn't work the "session" for WebSocket seems to be lost by Spring configuration. I don't see any log information about "Disconnected" messages or something similar, besides if I debug remotely the server when this happens the problem doesn't appears during debugging session. Some idea? The class from Spring where session disappear is DefaultSimpUserRegistry.
After more research I found a question with the same problem and the solution here. Basically the conclusion is this:
Channel interceptor is not the right place to authenticate user, we need to change it with a custom handshake handler.

Spring Boot WebSockets unable to find the current user (principal)

After signing-in, the websockets cannot find the current user by session.getPrincipal() (it returns null).
Here is the Java code for WebSockets:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/queue", "/topic");
config.setApplicationDestinationPrefixes("/socket");
config.setUserDestinationPrefix("/user");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/app").withSockJS();
}
}
It seems like a Spring Boot bug - I am using 1.3.8 RELEASE. After refreshing the page, it gets the logged-in user properly.
And here is the front-end (subscription)
ngstomp.subscribeTo('/user/queue/message')
.callback(function(response) {
console.log('Test');
})
.withBodyInJson()
.connect();
I tried this solution: https://www.javacodegeeks.com/2014/11/spring-boot-based-websocket-application-and-capturing-http-session-id.html
But it's not working.
Please help me!
Why you required to have session.getPricncipal(). Spring provides Principal object to be injected automatically in your controller as following.
#MessageMapping("/message")
public String processMessageFromClient(#Payload String message, Principal principal) throws Exception {
messagingTemplate.convertAndSendToUser(principal.getName(), "/queue/reply", name);
return name;
}
Reference: Spring Boot Websocket Example

Resources