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"));

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();
    }
29
demandé sur Community 2016-01-21 19:49:38

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.

25
répondu Brian Clozel 2016-01-27 10:20:16

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).

3
répondu Aviad 2016-01-25 01:57:14

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.

1
répondu Sandy 2018-08-27 19:07:26