¿Cómo enviar un mensaje websocket desde el servidor solo a un usuario específico?
Mi aplicación web tiene una configuración de seguridad de primavera y usa websocket. Me encuentro con un problema complicado al intentar enviar un mensaje desde el servidor solo a un usuario específico .
Mi comprensión de la lectura del manual es del servidor que podemos hacer
simpMessagingTemplate.convertAndSend("/user/{username}/reply", reply);
Y del lado del cliente:
stompClient.subscribe('/user/reply', handler);
Pero nunca pude invocar la devolución de llamada de suscripción. He probado muchos caminos diferentes pero no tuve suerte.
Si lo envío a / topic / reply , funciona, pero todos los demás usuarios conectados también lo recibirán.
Para ilustrar el problema, he creado este pequeño proyecto en github: https://github.com/gerrytan/wsproblem
Pasos para reproducir:
1) Clone y compile el proyecto (asegúrese de usar jdk 1.7 y maven 3.1)
$ git clone https://github.com/gerrytan/wsproblem.git
$ cd wsproblem
$ mvn jetty:run
2) Vaya a http://localhost:8080
, inicie sesión usando bob / test o jim / test
3) Haga clic en "Solicitar mensaje específico del usuario". Esperado: se muestra un mensaje "hola {nombre de usuario}" junto a "Mensaje recibido solo para mí" solo para este usuario, Real: no se recibe nada
fuente
simpMessagingTemplate.convertAndSendToUser(principal.getName(), "/user/reply", reply);
y cuando el mensaje se envía desde el servidor arroja esta excepciónjava.lang.IllegalArgumentException: Expected destination pattern "/principal/{userId}/**"
convertAndSendToUser(principal.getName(), "/reply", reply);
Respuestas:
Oh, el
client side no need to known about current user
servidor lo hará por ti.En el lado del servidor, use la siguiente forma para enviar un mensaje a un usuario:
simpMessagingTemplate.convertAndSendToUser(username, "/queue/reply", message);
Nota: Usando
queue
, notopic
, Spring siempre usandoqueue
consendToUser
Del lado del cliente
stompClient.subscribe("/user/queue/reply", handler);
Explique
Cuando cualquier conexión websocket está abierta, Spring le asignará un
session id
(noHttpSession
, asignar por conexión). Y cuando su cliente se suscribe a un canal comienza con/user/
, por ejemplo:,/user/queue/reply
su instancia de servidor se suscribirá a una cola llamadaqueue/reply-user[session id]
Cuando use enviar mensaje al usuario, por ejemplo: el nombre de usuario es
admin
Usted escribirásimpMessagingTemplate.convertAndSendToUser("admin", "/queue/reply", message);
Spring determinará cuál se
session id
asignó al usuarioadmin
. Por ejemplo: encontró dos sesioneswsxedc123
ythnujm456
, Spring lo traducirá a 2 destinosqueue/reply-userwsxedc123
yqueue/reply-userthnujm456
, y enviará su mensaje con 2 destinos a su agente de mensajes.El intermediario de mensajes recibe los mensajes y los devuelve a la instancia de su servidor que tiene la sesión correspondiente a cada sesión (las sesiones de WebSocket pueden ser retenidas por uno o más servidores). Spring traducirá el mensaje a
destination
(ej .user/queue/reply
:) ysession id
(ejwsxedc123
.:). Luego, envía el mensaje al correspondienteWebsocket session
fuente
HttpSession
cuando se inició una conexión websocketDefaultHandshakeHandler
y anular el métododetermineUser
queue/reply-user[session id]
parte en el documento oficial?Ah, encontré cuál era mi problema. Primero no registré el
/user
prefijo en el corredor simple<websocket:simple-broker prefix="/topic,/user" />
Entonces no necesito el
/user
prefijo adicional al enviar:convertAndSendToUser(principal.getName(), "/reply", reply);
Spring se antepondrá automáticamente
"/user/" + principal.getName()
al destino, por lo que se resuelve en "/ user / bob / reply".Esto también significa que en javascript tuve que suscribirme a diferentes direcciones por usuario
stompClient.subscribe('/user/' + userName + '/reply,...)
fuente
@RequestMapping
y también@MessageMapping
ver aquí Cómo suscribirse usando la integración de Sping Websocket en un nombre de usuario específico (userId) + recibir notificaciones del método anotado con @RequestMapping?También creé un proyecto de websocket de muestra usando STOMP. Lo que estoy notando es que
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic", "/queue");// including /user also works config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/getfeeds").withSockJS(); }
}
funciona tanto si "/ user" está incluido en config.enableSimpleBroker (...
fuente
Mi solución de eso se basó en la mejor explicación de Thanh Nguyen Van, pero además he configurado MessageBrokerRegistry:
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/queue/", "/topic/"); ... } ... }
fuente
Exactamente hice lo mismo y está funcionando sin usar el usuario.
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/gs-guide-websocket").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic" , "/queue"); config.setApplicationDestinationPrefixes("/app"); } }
fuente
/app
? ¿En ese caso?