61

I have, what I think to be, a very simple Spring WebSocket application. However, I'm trying to use path variables for the subscription as well as the message mapping.

I've posted a paraphrased example below. I would expect the @SendTo annotation to return back to the subscribers based on their fleetId. ie, a POST to /fleet/MyFleet/driver/MyDriver should notify subscribers of /fleet/MyFleet, but I'm not seeing this behavior.

It's worth noting that subscribing to literal /fleet/{fleetId} works. Is this intended? Am I missing some piece of configuration? Or is this just not how it works?

I'm not very familiar with WebSockets or this Spring project yet, so thanks in advance.

Controller.java

...
@MessageMapping("/fleet/{fleetId}/driver/{driverId}")
@SendTo("/topic/fleet/{fleetId}")
public Simple simple(@DestinationVariable String fleetId, @DestinationVariable String driverId) {
    return new Simple(fleetId, driverId);
}
...

WebSocketConfig.java

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/live");
    }

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

index.html

var socket = new SockJS('/fleet');
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
    // Doesn't Work
    stompClient.subscribe('/topic/fleet/MyFleet', function(greeting) {
    // Works
    stompClient.subscribe('/topic/fleet/{fleetId}', function(greeting) {
        // Do some stuff
    });
});

Send Sample

    stompClient.send("/live/fleet/MyFleet/driver/MyDriver", {}, JSON.stringify({
        // Some simple content
    }));
bvulaj
  • 5,023
  • 5
  • 31
  • 45

3 Answers3

138

Even though @MessageMapping supports placeholders, they are not exposed / resolved in @SendTo destinations. Currently, there's no way to define dynamic destinations with the @SendTo annotation (see issue SPR-12170). You could use the SimpMessagingTemplate for the time being (that's how it works internally anyway). Here's how you would do it:

@MessageMapping("/fleet/{fleetId}/driver/{driverId}")
public void simple(@DestinationVariable String fleetId, @DestinationVariable String driverId) {
    simpMessagingTemplate.convertAndSend("/topic/fleet/" + fleetId, new Simple(fleetId, driverId));
}

In your code, the destination '/topic/fleet/{fleetId}' is treated as a literal, that's the reason why subscribing to it works, just because you are sending to the exact same destination.

If you just want to send some initial user specific data, you could return it directly in the subscription:

@SubscribeMapping("/fleet/{fleetId}/driver/{driverId}")
public Simple simple(@DestinationVariable String fleetId, @DestinationVariable String driverId) {
    return new Simple(fleetId, driverId);
}

Update: In Spring 4.2, destination variable placeholders are supported it's now possible to do something like:

@MessageMapping("/fleet/{fleetId}/driver/{driverId}")
@SendTo("/topic/fleet/{fleetId}")
public Simple simple(@DestinationVariable String fleetId, @DestinationVariable String driverId) {
    return new Simple(fleetId, driverId);
}
Sergi Almar
  • 8,054
  • 3
  • 32
  • 30
  • Thanks, @sergi! I was hoping to get around using the MessagingTempalte, but that works just as well. What is the difference between using `@SubscribeMapping` and `@SendTo`? Does the former just skip the message broker? – bvulaj Nov 21 '14 at 14:17
  • 2
    `@SubscribeMapping` intercepts only subscriptions (not messages to the channel), return value will be sent directly to the user, but can be overridden with `@SendTo` to send it to another destination (will be forwarded to the brokerChannel). `@SendTo` can also be used with `@MessageMapping` to send the response to a specific destination. So basically, `@SendTo` can be used along with `@MessageMapping` and `@SubscribeMapping` – Sergi Almar Nov 21 '14 at 15:40
  • i'm to do same project equals, but can't start, same has prototype project can use, in github for example. I'm know work Spring but not work WebSocket, i'm need send message two person and, the message same equal Skype. one to one, tree if invite more one user comunications – Marcelo Ferreira Dec 15 '16 at 18:11
  • 3
    Thanks for your updates on this answer it was really helpful! – Vladlen Gladis Dec 16 '16 at 12:55
  • Hello @SergiAlmar, is there a way to accomplish the same thing without using stomp? We are comtemplating using plain websockets to do provide streaming data to dynamic endpoint, something like /path/{id} – myspri Aug 09 '17 at 20:46
  • @SergiAlmar do you know of any example projects that use the variable placeholders? Not sure about the rest of the configuration to achieve this. – Jordan Mackie Jun 28 '18 at 06:23
  • 3
    @JordanMackie check https://github.com/salmar/spring-websocket-chat/blob/c77467a77905abc24100d4e8040158c450d12a79/src/main/java/com/sergialmar/wschat/web/ChatController.java#L54 – Sergi Almar Jun 28 '18 at 12:29
  • @myspri no, if you use a raw WebSocket you won't have any application protocol, so you'll have to implement it – Sergi Almar Jun 28 '18 at 12:30
  • @SergiAlmar the line "@MessageMapping("/chat.private.{username}")" is there anything stopping other users listening on this endpoint? i.e. if I have the username jmack, I can still access /chat.private.someoneelse? Also - Just realised I watched your talk on websockets this morning! Loved it! – Jordan Mackie Jun 28 '18 at 12:34
  • @JordanMackie that destination is only used to send private messages, but not for receiving them. I use a user destination (just for that user) to receive messages, please read the docs on user destinations https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#websocket-stomp-user-destination – Sergi Almar Jun 28 '18 at 12:37
  • @SergiAlmar I have read them, my current issue is trying to set up something like multicast groups with websockets where one user publishes and many users listen. All I can find in the docs and examples are one-to-one private communication and broadcasting. – Jordan Mackie Jun 28 '18 at 12:45
  • @JordanMackie you can broadcast the message and the secure the destination with Spring Security https://docs.spring.io/spring-security/site/docs/current/reference/html/websocket.html. If that's not enough, implement a channel interceptor with your own security. – Sergi Almar Jun 28 '18 at 14:22
0

you can send a variable inside the path. for example i send "este/es/el/chat/java/" and obtaned in the server as "este:es:el:chat:java:"

client:

stompSession.send("/app/chat/este/es/el/chat/java/*", ...);

server:

@MessageMapping("/chat/**")
@SendToUser("/queue/reply")
public WebsocketData greeting(Message m,HelloMessage message,@Header("simpSessionId") String sessionId) throws Exception {
    Map<String, LinkedList<String>> nativeHeaders = (Map<String, LinkedList<String>>) m.getHeaders().get("nativeHeaders");
    String value= nativeHeaders.get("destination").getFirst().replaceAll("/app/chat/","").replaceAll("/",":");
ivan rc
  • 71
  • 5
0

Actually I think this is what you might be looking for:

@Autorwired
lateinit var template: SimpMessageTemplate;

@MessageMapping("/class/{id}")
@Throws(Exception::class)
fun onOffer(@DestinationVariable("id") id: String?, @Payload msg: Message) {
    println("RECEIVED " + id)
    template.convertAndSend("/topic/class/$id", Message("The response"))
}

Hope this helps someone! :)

João Rodrigues
  • 766
  • 8
  • 12