Is it possible to send a message to specific session?
I have an unauthenticated websocket between clients and a Spring servlet. I need to send an unsolicited message to a specific connection when an async job ends.
@Controller
public class WebsocketTest {
@Autowired
public SimpMessageSendingOperations messagingTemplate;
ExecutorService executor = Executors.newSingleThreadExecutor();
@MessageMapping("/start")
public void start(SimpMessageHeaderAccessor accessor) throws Exception {
String applicantId=accessor.getSessionId();
executor.submit(() -> {
//... slow job
jobEnd(applicantId);
});
}
public void jobEnd(String sessionId){
messagingTemplate.convertAndSend("/queue/jobend"); //how to send only to that session?
}
}
As you can see in this code, the client can start an async job and when it finishes, it needs the end message. Obviously, I need to message only the applicant and not broadcast to everyone.
It would be great to have an @SendToSession
annotation or messagingTemplate.convertAndSendToSession
method.
UPDATE
I tried this:
messagingTemplate.convertAndSend("/queue/jobend", true, Collections.singletonMap(SimpMessageHeaderAccessor.SESSION_ID_HEADER, sessionId));
But this broadcasts to all sessions, not only the one specified.
UPDATE 2
Test with convertAndSendToUser() method. This test is and hack of the official Spring tutorial: https://spring.io/guides/gs/messaging-stomp-websocket/
This is the server code:
@Controller
public class WebsocketTest {
@PostConstruct
public void init(){
ScheduledExecutorService statusTimerExecutor=Executors.newSingleThreadScheduledExecutor();
statusTimerExecutor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
messagingTemplate.convertAndSendToUser("1","/queue/test", new Return("test"));
}
}, 5000,5000, TimeUnit.MILLISECONDS);
}
@Autowired
public SimpMessageSendingOperations messagingTemplate;
}
and this is the client code:
function connect() {
var socket = new WebSocket('ws://localhost:8080/hello');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/user/queue/test', function(greeting){
console.log(JSON.parse(greeting.body));
});
});
}
Unfortunately client doesn't receive its per-session reply every 5000ms as expected. I'm sure that "1" is a valid sessionId for the 2nd client connected because I see it in debug mode with SimpMessageHeaderAccessor.getSessionId()
BACKGROUND SCENARIO
I want to create a progress bar for a remote job, client asks server for an async job and it checks its progress by websocket message sent from server. This is NOT a file upload but a remote computation, so only server knows the progress of each job. I need to send a message to specific session because each job is started by session. Client asks for a remote computation Server starts this job and for every job step reply to applicant client with its job progress status. Client gets messages about its job and build up a progress/status bar. This is why I need a per-session messages. I could also use a per-user messages, but Spring does not provide per user unsolicited messages. (Cannot send user message with Spring Websocket)
WORKING SOLUTION
__ __ ___ ___ _ __ ___ _ _ ___ ___ ___ _ _ _ _____ ___ ___ _ _
\ \ / // _ \ | _ \| |/ /|_ _|| \| | / __| / __| / _ \ | | | | | ||_ _||_ _|/ _ \ | \| |
\ \/\/ /| (_) || /| ' < | | | .` || (_ | \__ \| (_) || |__| |_| | | | | || (_) || .` |
\_/\_/ \___/ |_|_\|_|\_\|___||_|\_| \___| |___/ \___/ |____|\___/ |_| |___|\___/ |_|\_|
Starting from the UPDATE2 solution I had to complete convertAndSendToUser method with last param (MessageHeaders):
messagingTemplate.convertAndSendToUser("1","/queue/test", new Return("test"), createHeaders("1"));
where createHeaders()
is this method:
private MessageHeaders createHeaders(String sessionId) {
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
return headerAccessor.getMessageHeaders();
}
No need to create specific destinations, it's already done out of the box as of Spring 4.1 (see SPR-11309).
Given users subscribe to a
/user/queue/something
queue, you can send a message to a single session with:As stated in the SimpMessageSendingOperations Javadoc, since your user name is actually a sessionId, you MUST set that as a header as well otherwise the
DefaultUserDestinationResolver
won't be able to route the message and will drop it.You don't need users to be authenticated for this.
It is very complicated and in my opinion, isn't worth it. You need to create a subscription for every user (even unauthenticated ones) by their session id.
Let's say that every user subscribes to a unique queue only for him:
On the server, before the user subscribes you will need to notify and send a message for the specific session and save to a map:
After that, when you want to send message to the user you will need to:
But I don't really know what you are trying to do and how you will find the specific session (who is anonymous).
You need to simply add the session id in
Server Side
convertAndSendToUser(sessionId,apiName,responseObject);
Client Side
$stomp.subscribe('/user/+sessionId+'/apiName',handler);
Note:
Dont forget to add
'/user'
in your end point in server side.