Spring WebSocket @SendToSession: envoyer un message à une session spécifique
est-il possible d'envoyer un message à une session spécifique?
j'ai un websocket non authentifié entre des clients et un servlet à ressort. J'ai besoin d'envoyer un message non sollicité à une connexion spécifique quand un travail async se termine.
@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?
}
}
comme vous pouvez le voir dans ce code, le client peut commencer un travail asynchrone et quand il finit, il a besoin du message de fin. Évidemment, je dois envoyer un message seulement à la requérante et ne pas diffuser à tout le monde.
Il serait génial d'avoir une annotation @SendToSession
ou messagingTemplate.convertAndSendToSession
méthode.
mise à JOUR
j'ai essayé ceci:
messagingTemplate.convertAndSend("/queue/jobend", true, Collections.singletonMap(SimpMessageHeaderAccessor.SESSION_ID_HEADER, sessionId));
mais ceci diffuse à toutes les sessions, pas seulement celle spécifiée.
UPDATE 2
Test avec convertAndSendToUser() la méthode. Ce test est et le piratage du tutoriel officiel du printemps: https://spring.io/guides/gs/messaging-stomp-websocket /
C'est le code du serveur:
@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;
}
et voici le code client:
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));
});
});
}
malheureusement le client ne reçoit pas sa réponse par session tous les 5000ms comme prévu. Je suis sûr que "1" est un sessionId valide pour le 2e client connecté car je le vois en mode débogage avec SimpMessageHeaderAccessor.getSessionId()
BACKGROUND Scénario
je veux créer une barre de progression pour une tâche distante, le client demande au serveur une tâche async et il vérifie sa progression par message websocket envoyé par le serveur. Ce n'est pas un téléchargement de fichier mais un calcul à distance, donc seul le serveur connaît la progression de chaque tâche. J'ai besoin d'envoyer un message à une session spécifique parce que chaque tâche est commencée par session. Le Client demande un calcul à distance Le serveur commence ce travail et pour chaque étape de travail répondre au client candidat avec son travail état d'avancement. Le Client reçoit des messages sur son travail et établit une barre de progression/statut. C'est pourquoi j'ai besoin de messages par session. Je pourrais également utiliser un message par utilisateur, mais le printemps ne fournit pas par utilisateur messages non sollicités. ( Ne peut pas envoyer de message de l'utilisateur avec le Printemps Websocket )
WORKING SOLUTION
__ __ ___ ___ _ __ ___ _ _ ___ ___ ___ _ _ _ _____ ___ ___ _ _
/ // _ | _ | |/ /|_ _|| | | / __| / __| / _ | | | | | ||_ _||_ _|/ _ | | |
// /| (_) || /| ' < | | | .` || (_ | __ | (_) || |__| |_| | | | | || (_) || .` |
_/_/ ___/ |_|_|_|_|___||_|_| ___| |___/ ___/ |____|___/ |_| |___|___/ |_|_|
à partir de la solution UPDATE2 J'ai dû compléter la méthode convertAndSendToUser avec last param (MessageHeaders):
messagingTemplate.convertAndSendToUser("1","/queue/test", new Return("test"), createHeaders("1"));
où createHeaders()
est cette méthode:
private MessageHeaders createHeaders(String sessionId) {
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
return headerAccessor.getMessageHeaders();
}
3 réponses
pas besoin de créer des destinations spécifiques, il est déjà fait hors de la boîte à partir du printemps 4.1 (voir SPR-11309 ).
étant donné que les utilisateurs s'abonnent à une file d'attente /user/queue/something
, vous pouvez envoyer un message à une seule session avec:
Comme , a déclaré dans le SimpMessageSendingOperations Javadoc , car votre nom d'utilisateur est en fait un id de session, vous DEVEZ le définir comme un en-tête, sinon le DefaultUserDestinationResolver
il ne pourra pas acheminer le message et le laissera tomber.
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor
.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
messagingTemplate.convertAndSendToUser(sessionId,"/queue/something", payload,
headerAccessor.getMessageHeaders());
vous n'avez pas besoin que les utilisateurs soient authentifiés pour cela.
c'est très compliqué et à mon avis, ça n'en vaut pas la peine. Vous devez créer un abonnement pour chaque utilisateur (même non authentifié) par leur identifiant de session.
disons que chaque utilisateur s'abonne à une file d'attente unique uniquement pour lui:
stompClient.subscribe('/session/specific' + uuid, handler);
sur le serveur, avant que l'utilisateur ne s'abonne, vous devrez notifier et envoyer un message pour la session spécifique et enregistrer sur une carte:
@MessageMapping("/putAnonymousSession/{sessionId}")
public void start(@DestinationVariable sessionId) throws Exception {
anonymousUserSession.put(key, sessionId);
}
après cela, quand vous souhaitez envoyer un message à l'utilisateur, vous devrez:
messagingTemplate.convertAndSend("/session/specific" + key);
mais je ne sais pas vraiment ce que vous essayez de faire et comment vous allez trouver la session spécifique (qui est anonyme).
vous devez simplement ajouter l'id de session dans
-
Côté Serveur
convertAndSendToUser (sessionId,apiName,responseObject);
-
Côté Client
$ stomp.abonnez-vous('/user/+sessionId+'/apiName',handler);
Note:
N'oubliez pas d'ajouter '/user'
dans votre point final Côté Serveur.