2
votes

The STOMP spec says that SUBSCRIBE MUST have id header.

https://stomp.github.io/stomp-specification-1.2.html#SUBSCRIBE_id_Header

SUBSCRIBE id Header

Since a single connection can have multiple open subscriptions with a server, an id header MUST be included in the frame to uniquely identify the subscription. The id header allows the client and server to relate subsequent MESSAGE or UNSUBSCRIBE frames to the original subscription. Within the same connection, different subscriptions MUST use different subscription identifiers.

However, in spring's example https://spring.io/guides/gs/messaging-stomp-websocket/, it doesn't specify an id when subscribing destination.

function connect() {
    var socket = new SockJS('/gs-guide-websocket');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        console.log('Connected: ' + frame);
        stompClient.subscribe('/topic/greetings', function (greeting) {
            showGreeting(JSON.parse(greeting.body).content);
        });
    });
}

In spring's API, the SimpMessageSendingOperations.convertAndSendToUser doesn't support an id header explicitly.

My question is how to specify id header when sending a message to client?

1

1 Answers

1
votes

I don't think you can use a Subscription ID to send a message to a specific client. Stomp defines this ID and Spring's implementation uses it internally to create messages to every client subscribed to the destination address. Therefore, the Subscription ID is transparent in the Stomp communication... You can specify it in the client side or let Stomp JS (STOMP Over WebSocket) create a unique one.

If you subscribe to a destination prefixed with "/user/" and use org.springframework.messaging.simp.SimpMessagingTemplate#convertAndSendToUser or org.​springframework.​messaging.​simp.​annotation.SendToUser to send a message to a single client, what Spring does is register and create a subscription to a custom session based destination based on the original destination. In another words, from Spring's Javadoc:

When a user attempts to subscribe, e.g. to "/user/queue/position-updates", the "/user" prefix is removed and a unique suffix added based on the session id, e.g. "/queue/position-updates-useri9oqdfzo" to ensure different users can subscribe to the same logical destination without colliding.

When sending to a user, e.g. "/user/{username}/queue/position-updates", the "/user/{username}" prefix is removed and a suffix based on active session id's is added, e.g. "/queue/position-updates-useri9oqdfzo".

See http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/messaging/simp/user/DefaultUserDestinationResolver.html

See https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-user-destination

EDITED:

You can't use the subscription ID to send a message direct to it's subscribed client, but you can use the client's session ID. According to here, you could use the user's name to send him a message. But you would need authenticated sessions with a Principal on it. Or you can force the destination's session ID in the header of the message, avoiding the internal step to discover it, as shown here.

private void sendMessageToUser(String destinationSessionId, String message) {
    SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
    headerAccessor.setSessionId(destinationSessionId);
    headerAccessor.setLeaveMutable(true);
    messagingTemplate.convertAndSendToUser(destinationSessionId, "/subscribe/private", message, headerAccessor.getMessageHeaders());
}

Doing like this, without a Principal in the session, I couldn't use @SendToUser annotation.