Spring WebSocket @SendToSession: envíe un mensaje a una sesión específica

¿Es posible enviar un mensaje a una sesión específica?

Tengo un websocket no autenticado entre clientes y un servlet de Spring. Necesito enviar un mensaje no solicitado a una conexión específica cuando finaliza un trabajo asincrónico.

@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? } } 

Como puede ver en este código, el cliente puede iniciar un trabajo asincrónico y cuando termina, necesita el mensaje final. Obviamente, necesito enviar un mensaje solo al solicitante y no transmitirlo a todos. Sería genial tener una anotación @SendToSession o el método messagingTemplate.convertAndSendToSession .

ACTUALIZAR

Intenté esto:

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

Pero esto se transmite a todas las sesiones, no solo a la especificada.

ACTUALIZACIÓN 2

Prueba con el método convertAndSendToUser (). Esta prueba es un truco del tutorial oficial de Spring: https://spring.io/guides/gs/messaging-stomp-websocket/

Este es el código del servidor:

 @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; } 

y este es el código de cliente:

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

Lamentablemente, el cliente no recibe su respuesta por sesión cada 5000 ms como se esperaba. Estoy seguro de que “1” es una sessionId válida para el segundo cliente conectado porque la veo en modo de depuración con SimpMessageHeaderAccessor.getSessionId()

ESCENARIO DE ANTECEDENTES

Quiero crear una barra de progreso para un trabajo remoto, el cliente solicita al servidor un trabajo asincrónico y comprueba su progreso mediante un mensaje de websocket enviado desde el servidor. Esto NO es una carga de archivo sino un cálculo remoto, por lo que solo el servidor conoce el progreso de cada trabajo. Necesito enviar un mensaje a una sesión específica porque cada trabajo se inicia por sesión. El cliente solicita un cómputo remoto. El servidor inicia este trabajo y para cada paso del trabajo responde al cliente solicitante con su estado de progreso del trabajo. El cliente recibe mensajes sobre su trabajo y crea una barra de progreso / estado. Es por eso que necesito un mensaje por sesión. También podría usar mensajes por usuario, pero Spring no proporciona mensajes no solicitados por usuario. ( No se puede enviar un mensaje de usuario con Spring Websocket )

SOLUCIÓN DE TRABAJO

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

A partir de la solución UPDATE2 tuve que completar el método convertAndSendToUser con el último parámetro (MessageHeaders):

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

donde createHeaders() es este método:

 private MessageHeaders createHeaders(String sessionId) { SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE); headerAccessor.setSessionId(sessionId); headerAccessor.setLeaveMutable(true); return headerAccessor.getMessageHeaders(); } 

No es necesario crear destinos específicos, ya está hecho de fábrica a partir de Spring 4.1 (ver SPR-11309 ).

Dado que los usuarios se suscriben a una /user/queue/something , pueden enviar un mensaje a una sola sesión con:

Como se indica en el Javadoc de SimpMessageSendingOperations , dado que su nombre de usuario es realmente un ID de sesión, DEBE establecerlo también como encabezado, de lo contrario, DefaultUserDestinationResolver no podrá enrutar el mensaje y lo descartará.

 SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor .create(SimpMessageType.MESSAGE); headerAccessor.setSessionId(sessionId); headerAccessor.setLeaveMutable(true); messagingTemplate.convertAndSendToUser(sessionId,"/queue/something", payload, headerAccessor.getMessageHeaders()); 

No necesita que los usuarios sean autenticados para esto.

Es muy complicado y, en mi opinión, no vale la pena. Debe crear una suscripción para cada usuario (incluso los no autenticados) mediante su ID de sesión.

Digamos que cada usuario se suscribe a una cola única solo para él:

 stompClient.subscribe('/session/specific' + uuid, handler); 

En el servidor, antes de que el usuario se suscriba, deberá notificar y enviar un mensaje para la sesión específica y guardarlo en un mapa:

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

Después de eso, cuando desee enviar un mensaje al usuario deberá:

 messagingTemplate.convertAndSend("/session/specific" + key); 

Pero realmente no sé qué intenta hacer y cómo va a encontrar la sesión específica (quién es anónimo).

Solo tiene que agregar la ID de sesión en

  • Lado del servidor

    convertAndSendToUser (sessionId, apiName, responseObject);

  • Lado del cliente

    $ stomp.subscribe (‘/ user / + sessionId +’ / apiName ‘, manejador);

Nota:
No olvide agregar '/user' en su punto final en el lado del servidor.