Search code examples
javaspringstompspring-websocketsockjs

Websocket keep track of connections in Spring


Today I've searched a couple of hours for an implementation or tutorial in how to keep track of websocket connections in Spring.

I've done the (very good) Spring tutorial about websockets and STOMP. link here

So what's my setup, I have an Ionic Hybrid app with an Spring backend and I want to send a notification to the client whenever a new notification-event arises in the backend. All this code is already implemented and the connection works, but right now there is no way to specify where the notifications need to go to.

There is no tutorial or explanation on this matter that follows the structure in the Spring tutorial (at least not after 5 hours of research) and I am a little overwhelmed by all the information about websockets and security on the web. (I've been learning about websockets for just 2 days)

So for all that been before me and will come after me, I think it can be very usefull to have a compact and lightweight answer following the structure taught by the Spring Tutorial.

I've found this unanswered question on StackOverflow about the same problems as I have, so I'm sure this questions will prove it's worth.

TL;DR

How to implement a list in the backend that keeps track of the connections based on the Spring WebSocket Tutorial?

How to send data from the client to the backend when the connection is established? (for example a userid or token)


Solution

  • So I figured it out myself.

    My notifications have a recipient id (the user id where the notifications needs to be send to)

    So I'm going to send to '/ws-user/'+id+'/greetings' where the id is the user that is logged in.

    On the clientside this is fairly easy to achieve.

     var stompClient = null;
    
      // init
      function init() {
              /**
               * Note that you need to specify your ip somewhere globally
               **/
          var socket = new SockJS('http://127.0.0.1:9080/ws-notification');
          stompClient = Stomp.over(socket);
          stompClient.connect({}, function(frame) {
              console.log('Connected: ' + frame);
              /**
               * This is where I get the id of the logged in user
               **/
              barService.currentBarAccountStore.getValue().then(function (barAccount) {
                  subscribeWithId(stompClient,barAccount.user.id);
              });
          });
      }
    
              /**
               * subscribe at the url with the userid
               **/
      function subscribeWithId(stompClient,id){
          stompClient.subscribe('/ws-user/'+id+'/greetings', function(){
              showNotify();
          });
      }
              /**
               * Broadcast over the rootscope to update the angular view 
               **/
      function showNotify(){
          $rootScope.$broadcast('new-notification');
      }
    
      function disconnect() {
          if (stompClient != null) {
              stompClient.disconnect();
          }
          // setConnected(false);
          console.log("Disconnected");
      }
    

    Next up we add "setUserDestinationPrefix" to the MessageBrokerRegistry in the WebSocketConfig.java class :

    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
    
        private final static String userDestinationPrefix = "/ws-user/";
    
        @Override
        public void configureMessageBroker(MessageBrokerRegistry config){
            config.enableSimpleBroker("/ws-topic","/ws-user");
            config.setApplicationDestinationPrefixes("/ws-app");
            config.setUserDestinationPrefix(userDestinationPrefix);
        }
    
        @Override
        public void registerStompEndpoints(StompEndpointRegistry registry) {
            registry.addEndpoint("/ws-notification").setAllowedOrigins("*").withSockJS();
        }
    }
    

    Note that I'm using internal RestTemplate calls to access my controllermethod that sends out a notification to the subscribed client. This is done by a Event Consumer class (ask to see code, it's just to trigger controller function, could be done differently)

    @RequestMapping(value = "/test-notification", method = RequestMethod.POST)
    public void testNotification(@RequestBody String recipientId) throws InterruptedException {
        this.template.convertAndSendToUser(recipientId,"/greetings", new Notify("ALERT: There is a new notification for you!"));
    }
    

    Please review my code and warn me if you see any issues and/or security concerns.