2

I have this code: Client-side with javascript:

   socket = new SockJS(context.backend + '/myWebSocketEndPoint');
   stompClient = Stomp.over(socket);
   stompClient.connect({},function (frame) {
           stompClient.subscribe('/queue/'+clientId+'/notification', function(response){
               alert(angular.fromJson(response.body));
           });
   });

In this code, a client when connects, subscribe to receive notification using '/queue/'+ his client id + '/notification/. So i have a queue for every client. I use stomp with sockjs

In my server (Java + spring boot) i have a notification listener which when an event is published, it send a notification to all clients. So i have:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{

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


@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/myWebSocketEndPoint")
            .setAllowedOrigins("*")
            .withSockJS();
}
}

the class MenuItemNotificationChannel who call MenuItemNotificationSender to send the notification to the users.

@Component
public class MenuItemNotificationChannel extends AbstractNotificationChannel {

@Autowired
private MenuItemNotificationSender menuItemNotificationSender;

@Autowired
private UserRepository userRepository;

@Override
public void sendNotification(KitaiEvent<?> event, Map<String, Object> notificationConfiguration) throws Exception {
    String menuItem = Optional.ofNullable((String) notificationConfiguration.get(MENU_ENTRY_KEY)).orElseThrow(IllegalArgumentException::new);
    List<User> userList = userRepository.findAll();
    for(User u: userList){
        menuItemNotificationSender.sendNotification(new MenuItemDto(menuItem),u.getId());
    }

MenuItemNotificationSender class is:

@Component
public class MenuItemNotificationSender {

@Autowired
private SimpMessagingTemplate messagingTemplate;

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

public void sendNotification(MenuItemDto menuItem,Long id) {
    String address = "/queue/"+id+"/notification";
    messagingTemplate.convertAndSend(address, menuItem);
}
}

This code works perfectly: notifications are sent to every user. But if a user is not online, notifications are losts. My questions are:

  • How can i verify whit stomp what subscriptions are active and what are not?? (If i can verify if a subscription is active, i solve my problem because i save notification for users offline and then send them when they do login)

  • Can i use persistent queues? (i read something about it, but i have not understand if i can use it only with stomp and sockjs)

Sorry for my english! :D

Catechacha
  • 123
  • 4
  • 13

2 Answers2

1

You can put a spring event listener on the session connected event and the session disconnect event I tested this one with spring 4.3.4

@Component
public class WebSocketSessionListener
{
    private static final Logger logger = LoggerFactory.getLogger(WebSocketSessionListener.class.getName());
    private List<String> connectedClientId = new ArrayList<String>();

    @EventListener
    public void connectionEstablished(SessionConnectedEvent sce)
    {
        MessageHeaders msgHeaders = sce.getMessage().getHeaders();
        Principal princ = (Principal) msgHeaders.get("simpUser");
        StompHeaderAccessor sha = StompHeaderAccessor.wrap(sce.getMessage());
        List<String> nativeHeaders = sha.getNativeHeader("userId");
        if( nativeHeaders != null )
        {
            String userId = nativeHeaders.get(0);
            connectedClientId.add(userId);
            if( logger.isDebugEnabled() )
            {
                logger.debug("Connessione websocket stabilita. ID Utente "+userId);
            }
        }
        else
        {
            String userId = princ.getName();
            connectedClientId.add(userId);
            if( logger.isDebugEnabled() )
            {
                logger.debug("Connessione websocket stabilita. ID Utente "+userId);
            }
        }
    }

    @EventListener
    public void webSockectDisconnect(SessionDisconnectEvent sde)
    {
        MessageHeaders msgHeaders = sde.getMessage().getHeaders();
        Principal princ = (Principal) msgHeaders.get("simpUser");
        StompHeaderAccessor sha = StompHeaderAccessor.wrap(sde.getMessage());
        List<String> nativeHeaders = sha.getNativeHeader("userId");
        if( nativeHeaders != null )
        {
            String userId = nativeHeaders.get(0);
            connectedClientId.remove(userId);
            if( logger.isDebugEnabled() )
            {
                logger.debug("Disconnessione websocket. ID Utente "+userId);
            }
        }
        else
        {
            String userId = princ.getName();
            connectedClientId.remove(userId);
            if( logger.isDebugEnabled() )
            {
                logger.debug("Disconnessione websocket. ID Utente "+userId);
            }
        }
    }

    public List<String> getConnectedClientId()
    {
        return connectedClientId;
    }
    public void setConnectedClientId(List<String> connectedClientId)
    {
        this.connectedClientId = connectedClientId;
    }
}

When a client is connected you add in the List of clients id the client id; when it disconnects you remove it

Then you can inject this bean or its List where you want to check if the client is active or less and then you can check if the client id is between the connected clients ID you can send the message, otherwise you must save it and resend later

On client side you can do something like this:

var socket = new SockJS('/ws');
stompClient = Stomp.over(socket);
stompClient.connect({userId:"customUserId"}, function (frame) {
});

Angelo

Angelo Immediata
  • 6,635
  • 4
  • 33
  • 65
0

why not using some events like below, you can export classes to differents files and use SessionConnectedEvent and SessionDisconnectEvent OR SessionSubscribeEvent and SessionUnsubscribeEvent. see doc here http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-appplication-context-events

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.messaging.SessionConnectedEvent;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;
import org.springframework.web.socket.messaging.SessionSubscribeEvent;
import org.springframework.web.socket.messaging.SessionUnsubscribeEvent;

@Component
public class SessionConnectedListener extends SessionsListener implements ApplicationListener<SessionConnectedEvent> {

    @Override
    public void onApplicationEvent(SessionConnectedEvent event) {
        users.add(event.getUser().getName());
    }

}

@Component
class SessionDisconnectListener extends SessionsListener implements ApplicationListener<SessionDisconnectEvent> {

    @Override
    public void onApplicationEvent(SessionDisconnectEvent event) {
        users.remove(event.getUser().getName());
    }
}

@Component
class SessionSubscribeListener extends SessionsListener implements ApplicationListener<SessionSubscribeEvent> {

    @Override
    public void onApplicationEvent(SessionSubscribeEvent event) {
        users.add(event.getUser().getName());
    }
}

@Component
class SessionUnsubscribeListener extends SessionsListener implements ApplicationListener<SessionUnsubscribeEvent> {

    @Override
    public void onApplicationEvent(SessionUnsubscribeEvent event) {
        users.remove(event.getUser().getName());
    }
}

class SessionsListener {

    protected List<String> users = Collections.synchronizedList(new LinkedList<String>());

    public List<String> getUsers() {
        return users;
    }
}

and change your code :

@Autowired
private SessionsListener sessionsListener;

@Override
public void sendNotification(KitaiEvent<?> event, Map<String, Object> notificationConfiguration) throws Exception {
    String menuItem = Optional.ofNullable((String) notificationConfiguration.get(MENU_ENTRY_KEY)).orElseThrow(IllegalArgumentException::new);
    List<String> userList = sessionsListener.getUsers();
    for(String u: userList){
        menuItemNotificationSender.sendNotification(new MenuItemDto(menuItem),u);
    }
Hassen Bennour
  • 3,885
  • 2
  • 12
  • 20