7

I'm developing a realtime notification system in Spring 4 by using a build-in Message Broker, and STOMP over WebSocket.

I would like to be able to send messages to a specific user, according with his username. In order to achieve this goal, I'm using the convertAndSendToUser method of org.springframework.messaging.simp.SimpMessagingTemplate class, as follows:

private final MessagingTemplate messagingTemplate;

@Autowired
public LRTStatusListener(SimpMessagingTemplate messagingTemplate) {
    this.messagingTemplate = messagingTemplate;
}


@Scheduled(fixedDelay=5000)
public void sendMessages(Principal principal)
    messagingTemplate
        .convertAndSendToUser(principal.getName(), "/horray", "Horray, " + principal.getName() + "!");
}

As configuration:

@Configuration
@EnableScheduling
@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", "/user");
    }

}

Client-side (via JavaScript), I should subscribe to a channel by specifing the username (according with another very similar question: Sending message to specific user on Spring Websocket).

stompClient.subscribe('/user/' + username + '/horray, ...) 

This last point sounds weird...

Supposing that I'm logged as w.white on my webapp, by subscribing:

stompClient.subscribe('/user/w.white/horray, ...)

... I will be able to see messages sent to w.white, and this is awesome... But subscribing:

stompClient.subscribe('/user/j.pinkman/horray, ...)

... I will be able to see also messages sent to j.pinkman, despide that I'm currently logged as w.white.

It is a way to overcome this problem?


Update

Below there is the log about the connection over WebSocket:

Opening Web Socket... 
Web Socket Opened... 
>>> CONNECT
accept-version:1.1,1.0
heart-beat:10000,10000

<<< CONNECTED
user-name:w.white
heart-beat:0,0
version:1.1

connected to server undefined
Connected: CONNECTED
version:1.1
heart-beat:0,0
user-name:w.white

>>> SUBSCRIBE
id:sub-0
destination:/topic/lrt

>>> SUBSCRIBE
id:sub-1
destination:/user/lrt
Community
  • 1
  • 1
vdenotaris
  • 13,297
  • 26
  • 81
  • 132

2 Answers2

25

I found the solution.

First of all, it is important to know that the /user channel is already managed by Spring STOMP, and by the way, no registration is required.

So:

@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
    registry.enableSimpleBroker("/topic", "/queue");
}

Then, I setup the destination channel as /queue/horray:

@Scheduled(fixedDelay=5000)
public void sendMessages(Principal principal)
    messagingTemplate
        .convertAndSendToUser(principal.getName(), "/queue/horray", "Horray, " + principal.getName() + "!");
}

At last, on client:

stompClient.subscribe('/user/queue/horray', '...');

Now, it works fine! Messages are sent only to the specified recipient, according to the Principal fetched by the security context.

vdenotaris
  • 13,297
  • 26
  • 81
  • 132
  • 2
    Congratulations on your solution. Do you know if there would be a similar version using the @SendToUser annotation? – hawaii Sep 29 '14 at 03:13
  • It should be the same. Have you tried using `@SendToUser` with `/user/queue/horray` as destination? – vdenotaris Sep 29 '14 at 09:13
  • Yes. For some weird reason it did not worked (client never received the subscription callback, even if the method had being called successfully). My solution was to add a custom header ("toUser") and send the message using SimpMessagingTemplate.convertAndSendToUser. I retrieve the headers adding the Message> message parameter in the controller method. User is subscribed to '/user/topic/chat'. If you subscribe to '/user/{username}/topic/chat' you do not receive the messages (which is intended). Also, there is no need to enable "/user" as a broker. – hawaii Sep 29 '14 at 09:26
  • 1
    My conf almost same with yours (I'm using queue/reply instead of queue/horray) but my console output is empty. Did you skip some important points in your post or that's all? Could you post total config? –  Dec 15 '15 at 02:53
  • 3
    Hello, @vdenotaris. I have noticed your scheduled method for publishing messages: @Scheduled(fixedDelay=5000) public void sendMessages(Principal principal) messagingTemplate .convertAndSendToUser(principal.getName(), "/queue/horray", "Horray, " + principal.getName() + "!"); } Spring scheduled methods do not work with parameters. How can I get Principal inside the scheduled method? Thanks in advance! – 0bj3ct Feb 28 '16 at 08:58
  • 2
    Any method annotated with @Scheduled can not accept any argument. @Scheduled(fixedDelay=5000) public void sendMessages(Principal principal){} this will not work. – Ashish Bhosle Jul 12 '17 at 06:04
  • This question had been published and accepted 3 years ago. Please, stop with the necroposting! – vdenotaris Jul 12 '17 at 17:42
  • 1
    @vdenotaris I'm not using Spring security `:-(`, How do I get user here `messagingTemplate .convertAndSendToUser(?, "/queue/horray", "Horray, ?");` – Shantaram Tupe Dec 02 '17 at 11:34
7

Since users on my application are not authenticated I just used the session Id to differenciate the various topics

on the server:

template.convertAndSend("/topic/warnings/" + sessionId, ...)

And the client is pretty straightforward

stompClient.subscribe('/topic/warnings/${pageContext.session.id}', ...

Maybe not the cleanest way but it works, and without authentication I couldn't make use of /user channel

  • I my case If **two users** are logged into the **same `browser`**, their `sessionId` will be the same, so one can see other's subscriptions, this should be done on user_id or user_name... – Shantaram Tupe Dec 02 '17 at 11:21
  • @ShantaramTupe ***two users** are logged into the **same browser*** is not a valid case IMHO. In that case you will have to switch the sessions internally. Then again, practically and ultimately it won't be multiple users, but a single user having multiple accounts. (Gmail for an instance, you could think of).. – Romeo Sierra Dec 31 '18 at 10:00