I'm building a small web chat to learn about Spring and Spring WebSocket. You can create different rooms, and each room has it's own channel at /topic/room/{id}
.
My goal is to detect when users join and leave a chat room and I thought I could use Spring WebSocket's SessionSubscribeEvent
and SessionUnsubscribeEvent
for this.
Getting the Destination from the SessionSubscribeEvent
is trivial:
@EventListener
public void handleSubscribe(final SessionSubscribeEvent event) {
final String destination =
SimpMessageHeaderAccessor.wrap(event.getMessage()).getDestination();
//...
}
However, the SessionUnsubscribeEvent
does not seem to carry the destination channel, destination
is null
in the following snippet:
@EventListener
public void handleUnsubscribe(final SessionUnsubscribeEvent event) {
final String destination =
SimpMessageHeaderAccessor.wrap(event.getMessage()).getDestination();
//...
}
Is there a better way to watch for subscribe/unsubscribe events and should I even be using those as a way for a user to "log in" to a chat room, or should I rather use a separate channel to send separate "log in"/"log out" messages and work with those?
I thought using subscribe/unsubscribe would've been very convenient, but apparently Spring makes it very hard, so I feel like there has to be a better way.
STOMP Headers only appear in the frames relevant to your question as described here: https://stomp.github.io/stomp-specification-1.2.html#SUBSCRIBE and here: https://stomp.github.io/stomp-specification-1.2.html#UNSUBSCRIBE
Only the SUBSCRIBE
frame has both destination and id, the UNSUBSCRIBE
frame has only an id. This means you have to remember the subscription id with the destination for future lookup. Care must be taken because different Websocket connections usually use/assign the same subscription ids, so to save destinations reliably, you have to include the websocket session id in your storage key.
I wrote the following method to get it:
protected String getWebsocketSessionId(StompHeaderAccessor headerAccessor)
{
// SimpMessageHeaderAccessor.SESSION_ID_HEADER seems to be set in StompSubProtocolHandler.java:261 ("headerAccessor.setSessionId(session.getId());")
return headerAccessor.getHeader(SimpMessageHeaderAccessor.SESSION_ID_HEADER).toString();
}
StompHeaderAccessor
is created like this:
StompHeaderAccessor headerAccessor=StompHeaderAccessor.wrap(((SessionSubscribeEvent)event).getMessage());
StompHeaderAccessor headerAccessor=StompHeaderAccessor.wrap(((SessionUnsubscribeEvent)event).getMessage());
This can then be used to create a unique subscription id which can be used as a key for a map to save data about the subscription, including the destination:
protected String getUniqueSubscriptionId(StompHeaderAccessor headerAccessor)
{
return getWebsocketSessionId(headerAccessor)+"--"+headerAccessor.getSubscriptionId();
}
Like this:
Map<String, String> destinationLookupTable=...;
// on subscribe:
destinationLookupTable.put(getUniqueSubscriptionId(headerAccessor), destination);
// on other occasions, including unsubscribe:
destination=destinationLookupTable.get(getUniqueSubscriptionId(headerAccessor));
I think using SessionSubscribeEvent and SessionUnsubscribeEvent is a good idea for that matter. You can get the destination if you keep track of the SessionID:
private Map<String, String> destinationTracker = new HashMap<>();
@EventListener
public void handleSubscribe(final SessionSubscribeEvent event) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(event.getMessage());
destinationTracker.put(headers.getSessionId(), headers.getDestination());
//...
}
@EventListener
public void handleUnsubscribe(final SessionUnsubscribeEvent event) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(event.getMessage());
final String destination = destinationTracker.get(headers.getSessionId());
//...
}
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.