简体   繁体   中英

Spring Boot WebSocket - how to get notified on client subscriptions

I have an application with a large number of groups, where my server is using a message queue (RabbitMQ) to observe the groups and post notification to the user upon changes over WebSocket. I'm using Spring boot and their WebSocket implementation inspired by this guide: https://spring.io/guides/gs/messaging-stomp-websocket/

Here is an example of the JavaScript client subscribing to the channel:

var socket = new SockJS('http://localhost/ws');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
    console.log('Connected: ' + frame);
    stompClient.subscribe('/topic/group/1/notification', function (message) {
        // to something..
    });
});

My Java Spring WebSocket controller has this broadcastNotification method sending messages to the /topic/group/{groupId}/notification channel.

@Controller
public class GroupController {
    private SimpMessagingTemplate template;

    @Autowired
    public GroupController(SimpMessagingTemplate template) {
        this.template = template;
    }

    public void broadcastNotification(int groupId, Notification notification) {
        this.template.convertAndSend("/topic/group/." + tenantId + "/notification", Notification);
    }
}

Thats working fine, but with performacne in mind I would like my to business logic to only observe groups currently beeing subscribed on WebSocket.

How can I be notified on my server when clients subscribe to the /topic/group/1/notification or /topic/group/1/* channel? The web users will be subscribing and unsubscribing as they browse the web page.

You can listen to the event SessionSubscribeEvent like this:

@Component
public class WebSocketEventListener {

  @EventListener
  public void handleSessionSubscribeEvent(SessionSubscribeEvent event) {
      GenericMessage message = (GenericMessage) event.getMessage();
      String simpDestination = (String) message.getHeaders().get("simpDestination");

      if (simpDestination.startsWith("/topic/group/1")) {
        // do stuff
      }
  }
}

You can use annotation-driven event listener (Kotlin code):

@EventListener
private fun onSubscribeEvent(event: SessionSubscribeEvent) {
    // do stuff...
}

Such event listeners can be registered on any public method of a managed bean via the @EventListener annotation.

You can detect when a client subscribes to a topic using interceptors in the WebSocketConfig class:

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@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("/gs-guide-websocket").withSockJS();
    }
  
    @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())){
                    System.out.println("Connect ");
                } else if(StompCommand.SUBSCRIBE.equals(accessor.getCommand())){
                    System.out.println("Subscribe ");
                } else if(StompCommand.SEND.equals(accessor.getCommand())){
                    System.out.println("Send message " );
                } else if(StompCommand.DISCONNECT.equals(accessor.getCommand())){
                    System.out.println("Exit ");
                } else {
                }
                return message;
            }
        });
    }
}

The accessor object contains all information sent from the client.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM