J'aimerais comprendre le fonctionnement de convertAndSendToUser dans le cadre Spring SockJS + Websocket.
Dans le client, nous nous connecterions en tant que
stompClient.connect(login, password, callback())
qui se traduira par une demande de connexion avec "informations d'identification Stomp" de login et mot de passe, qui peut être vu par exemple. si nous gérons SessionConnectEvent http://www.sergialmar.com/2014/03/detect-websocket-connects-and-disconnects-in-spring-4/
Mais je ne sais toujours pas s'il s'agira de "l'utilisateur" dans une opération d'envoi côté serveur vers une file d'attente:
simpMessagingTemplate.convertAndSendToUser(username, "/queue/reply", message);
Le plus proche que je puisse obtenir est de lire ce fil Envoi d’un message à un utilisateur spécifique sur Spring Websocket , réponse de Thanh Nguyen Van, mais cela n’est pas encore clair.
Fondamentalement, ce que je dois faire est de souscrire certains clients au même sujet, mais sur un serveur, leur envoyer des données différentes. Le client peut fournir l'identifiant de l'utilisateur.
J'ai essayé de comprendre les websockets et suis tombé sur cette question. Je suis vraiment déçu de ne pas trouver de réponse. Voici quelque chose pour le futur lecteur.
Je suppose que le lecteur a une compréhension de base des Websockets à ressort utilisant Stomp. Les termes tels que abonnement, préfixes de destination, sujets, fichier de configuration de socket, etc. sont compris.
Nous savons que nous pouvons envoyer des messages au client à partir d'un serveur Stomp en utilisant les préfixes de rubrique auxquels il est abonné, par exemple. /topic/hello
. Nous savons également que nous pouvons envoyer des messages à un utilisateur spécifique car spring fournit l’API convertAndSendToUser(username, destination, message)
. Il accepte un nom d'utilisateur String, ce qui signifie que si nous avons en quelque sorte un nom d'utilisateur unique pour chaque connexion, nous devrions pouvoir envoyer des messages à des utilisateurs spécifiques abonnés à un sujet.
Ce qui est moins compris c'est d'où vient ce nom d'utilisateur?
Ce nom d'utilisateur fait partie d'une interface Java.security.Principal
. Chaque objet StompHeaderAccessor
ou WebSocketSession
a une instance de ce principal et vous pouvez obtenir le nom d'utilisateur. Cependant, selon mes expériences, il n'est pas généré automatiquement. Il doit être généré manuellement par le serveur pour chaque session.
Pour utiliser cette interface, vous devez d'abord la mettre en œuvre.
class StompPrincipal implements Principal {
String name
StompPrincipal(String name) {
this.name = name
}
@Override
String getName() {
return name
}
}
Ensuite, vous pouvez générer une StompPrincipal
unique pour chaque connexion en remplaçant le DefaultHandshakeHandler. Vous pouvez utiliser n'importe quelle logique pour générer le nom d'utilisateur. Voici une logique potentielle qui utilise UUID:
class CustomHandshakeHandler extends DefaultHandshakeHandler {
// Custom class for storing principal
@Override
protected Principal determineUser(ServerHttpRequest request,
WebSocketHandler wsHandler,
Map<String, Object> attributes) {
// Generate principal with UUID as name
return new StompPrincipal(UUID.randomUUID().toString())
}
}
Enfin, vous devez configurer vos websockets pour utiliser votre gestionnaire de prise de contact personnalisé.
@Override
void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry
.addEndpoint("/stomp") // Set websocket endpoint to connect to
.setHandshakeHandler(new CustomHandshakeHandler()) // Set custom handshake handler
.withSockJS() // Add Sock JS support
}
C'est tout. Votre serveur est maintenant configuré pour générer un nom de principal unique pour chaque connexion. Il transmettra ce principal dans le cadre des objets StomHeaderAccessor
auxquels vous pouvez accéder via les écouteurs d'événements de connexion, les fonctions MessageMapping, etc.
Des auditeurs de l'événement:
@EventListener
void handleSessionConnectedEvent(SessionConnectedEvent event) {
// Get Accessor
StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage())
}
À partir des API mappées de messages
@MessageMapping('/hello')
protected void hello(SimpMessageHeaderAccessor sha, Map message) {
// sha available in params
}
Une dernière remarque sur l'utilisation de convertAndSendToUser(...)
. Lors de l'envoi de messages à un utilisateur, vous utiliserez quelque chose comme ceci
convertAndSendToUser(sha.session.principal.name, '/topic/hello', message)
Cependant, pour abonner le client, vous utiliserez
client.subscribe('/user/topic/hello', callback)
Si vous abonnez le client à /topic/hello
, vous ne recevrez que les messages diffusés.
Je n'ai pas fait de configuration spécifique et je peux simplement faire ceci:
@MessageMapping('/hello')
protected void hello(Principal principal, Map message) {
String username = principal.getName();
}