Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multicast Websockets in Spring Boot

Context

First time working with websockets in spring. My application involves one type of user (CHAP) providing their current location to all others who are subscribed (USR) (and are authorized to subscribe to this info)

I'm reading through the documentation and have found this section which I believe holds my solution, but I'm just not 100% sure how exactly it works, and would love someone with a stronger understanding to put it in other words. I've seen similar questions on stack overflow but the solutions feel too specific (though this could just be my own lack of understanding).

The Problem

One CHAP per topic who can publish their location to the topic. Users can subscribe to any topics their authorized to subscribe to.

So essentially:

  • Multiple topics at a variable endpoint (something like /{route_id}/location )

  • Users can subscribe to these topics and receive updates when they are available

  • Users with the CHAP role can publish to one topic. (i.e. each CHAP has a {route_id} they can publish to.

  • Users with USR role can listen to multiple topics that they are part of (i.e. each USR has several routes they can listen for updates on)

This is similar problem to having multiple chat rooms, which is the common example for websockets. However all examples I can find either have static chat room names, single chat rooms, or can only target messages to one user (not a group)

Current Code

@MessageMapping("/chaperone/location") // chaperone sends data to here
@SendTo("/{route_id}/location") // users can listen in on this
public BusModel updateLocation(@DestinationVariable long route_id, BusModel busModel) {
    return routeService.updateBusLocation(busModel);
}

My thinking here is that chaperones post to that url, and all users subscribed to their route will get an update.

Thanks!

like image 880
Jordan Mackie Avatar asked Jun 16 '18 15:06

Jordan Mackie


People also ask

Does spring boot support WebSocket?

Spring Framework 4 includes a new spring-websocket module with comprehensive WebSocket support. It is compatible with the Java WebSocket API standard (JSR-356) and also provides additional value-add as explained in the rest of the introduction.

What is registerStompEndpoints?

The registerStompEndpoints method registers the “/chat” endpoint, enabling Spring's STOMP support. Keep in mind that we are also adding an endpoint here that works without the SockJS for the sake of elasticity. This endpoint, when prefixed with “/app”, is the endpoint that the ChatController.

What is spring boot SockJS?

SockJS is WebSocket emulation library. It gives you a coherent, cross-browser, Javascript API which creates a low latency, full duplex, cross-domain communication channel between the browser and the web server, with WebSockets or without.


1 Answers

This turned out to be a solution, and didn't require much configuration as I previously thought. Here's my version here:

WebSocketConfig

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    public static final String TOPIC="/topic";

    /**
     * Consumers connect to endpoint/live to connect to the websocket
     *
     * @param registry
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/live").setAllowedOrigins("*").withSockJS();
    }

    /**
     * Once connected to the websocket, users can subscribe to endpoints prefixed with /topic
     * as these are managed by the message broker.
     * @param registry
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker(TOPIC);
        registry.setApplicationDestinationPrefixes("/");
    }
}

LiveController

@SubscribeMapping(TOPIC + "/routes/{route_id}")
public MessageWrapper subscribeToRouteLocation(
        @DestinationVariable(value = "route_id") long route_id) {
    LOG.debug("New user subscribed to location of route %d", route_id);
    return new MessageWrapper(LOCATION_MESSAGE, routeService.getBusForRoute(route_id));
}

@MessageMapping("/routes/{route_id}")
@SendTo(TOPIC + "/routes/{route_id}")
public MessageWrapper updateRouteLocation(
        @DestinationVariable(value = "route_id") long route_id,
        @Payload BusStatusUpdateModel busLocation) {
    if (busLocation.getLat() == 0 && busLocation.getLon() == 0) {
        LOG.debug("Ending route %d", route_id);
        return new MessageWrapper(ROUTE_TERMINATED_MESSAGE, routeService.endBusForRoute(route_id));
    } else {
        LOG.debug("Updating location of route %d", route_id);
        BusStatusUpdateModel statusUpdateModel = routeService.updateBusLocation(busLocation, route_id);
        return new MessageWrapper(LOCATION_MESSAGE, statusUpdateModel);
    }
}

So messages sent to /routes/{route_id} will be passed on to subscribers of /topic/routes/{route_id}

I am yet to test the authorization stuff, will fill this in once I have it!

like image 120
Jordan Mackie Avatar answered Oct 16 '22 07:10

Jordan Mackie