web-dev-qa-db-fra.com

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 les clients et un servlet Spring. J'ai besoin d'envoyer un message non sollicité à une connexion spécifique à la fin d'un travail asynchrone.

@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 démarrer un travail asynchrone et lorsqu'il se termine, il a besoin du message de fin. De toute évidence, je dois envoyer un message uniquement au demandeur et ne pas diffuser à tout le monde. Ce serait formidable d'avoir une annotation @SendToSession Ou une méthode messagingTemplate.convertAndSendToSession.

[~ # ~] mise à jour [~ # ~]

J'ai essayé ceci:

messagingTemplate.convertAndSend("/queue/jobend", true, Collections.singletonMap(SimpMessageHeaderAccessor.SESSION_ID_HEADER, sessionId));

Mais cela diffuse à toutes les sessions, pas seulement celle spécifiée.

MISE À JOUR 2

Testez avec la méthode convertAndSendToUser (). Ce test est un hack du tutoriel officiel de Spring: https://spring.io/guides/gs/messaging-stomp-websocket/

Voici 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 toutes les 5000 ms comme prévu. Je suis sûr que "1" est un ID session valide pour le 2ème client connecté car je le vois en mode débogage avec SimpMessageHeaderAccessor.getSessionId()

SCÉNARIO DE CONTEXTE

Je veux créer une barre de progression pour un travail distant, le client demande au serveur un travail asynchrone et il vérifie sa progression par un 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 travail. J'ai besoin d'envoyer un message à une session spécifique car chaque travail est démarré par session. Le client demande un serveur de calcul distant démarre ce travail et pour chaque étape du travail, répondez au client demandeur avec son état de progression du travail. Le client reçoit des messages sur son travail et crée une barre de progression/d'état. C'est pourquoi j'ai besoin de messages par session. Je pourrais également utiliser des messages par utilisateur, mais Spring ne fournit pas de messages non sollicités par utilisateur. ( Impossible d'envoyer un message utilisateur avec Spring Websocket )

SOLUTION DE TRAVAIL

 __      __ ___   ___  _  __ ___  _  _   ___      ___   ___   _    _   _  _____  ___  ___   _  _ 
 \ \    / // _ \ | _ \| |/ /|_ _|| \| | / __|    / __| / _ \ | |  | | | ||_   _||_ _|/ _ \ | \| |
  \ \/\/ /| (_) ||   /| ' <  | | | .` || (_ |    \__ \| (_) || |__| |_| |  | |   | || (_) || .` |
   \_/\_/  \___/ |_|_\|_|\_\|___||_|\_| \___|    |___/ \___/ |____|\___/   |_|  |___|\___/ |_|\_|

À partir de la solution UPDATE2, j'ai dû compléter la méthode convertAndSendToUser avec le dernier paramètre (MessageHeaders):

messagingTemplate.convertAndSendToUser("1","/queue/test", new Return("test"), createHeaders("1"));

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();
    }
35
Tobia

Pas besoin de créer des destinations spécifiques, c'est déjà fait dès la sortie du Spring 4.1 (voir SPR-11309 ).

Étant donné que les utilisateurs souscrivent à un /user/queue/something file d'attente, vous pouvez envoyer un message à une seule session avec:

Comme indiqué dans le Javadoc SimpMessageSendingOperations , puisque votre nom d'utilisateur est en fait un sessionId, vous DEVEZ également le définir comme en-tête sinon le DefaultUserDestinationResolver ne pourra pas router le message et va le laisser 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.

33
Brian Clozel

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 son 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 l'enregistrer sur une carte:

    @MessageMapping("/putAnonymousSession/{sessionId}")
    public void start(@DestinationVariable sessionId) throws Exception {
        anonymousUserSession.put(key, sessionId);
    }

Après cela, lorsque 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 trouverez la session spécifique (qui est anonyme).

4
Aviad

Vous devez simplement ajouter l'ID de session dans

  • Du côté serveur

    convertAndSendToUser (sessionId, apiName, responseObject);

  • Côté client

    $ stomp.subscribe ('/ user/+ sessionId +'/apiName ', gestionnaire);

Remarque:
N'oubliez pas d'ajouter '/user' dans votre point de terminaison côté serveur.

2
Sandy