Senden einer Nachricht an einen bestimmten Benutzer in Spring Websocket

85

Wie sende ich eine Websocket-Nachricht vom Server nur an einen bestimmten Benutzer?

Meine Webapp hat Spring Security Setup und verwendet Websocket. Beim Versuch, eine Nachricht vom Server nur an einen bestimmten Benutzer zu senden, tritt ein schwieriges Problem auf .

Mein Verständnis vom Lesen des Handbuchs stammt von dem Server, den wir ausführen können

simpMessagingTemplate.convertAndSend("/user/{username}/reply", reply);

Und auf der Kundenseite:

stompClient.subscribe('/user/reply', handler);

Aber ich konnte den Abonnement-Rückruf niemals aufrufen. Ich habe viele verschiedene Wege ausprobiert, aber kein Glück.

Wenn ich es an / topic / reply sende, funktioniert es, aber alle anderen verbundenen Benutzer erhalten es auch.

Um das Problem zu veranschaulichen, habe ich dieses kleine Projekt auf Github erstellt: https://github.com/gerrytan/wsproblem

Schritte zum Reproduzieren:

1) Klonen und erstellen Sie das Projekt (stellen Sie sicher, dass Sie jdk 1.7 und maven 3.1 verwenden).

$ git clone https://github.com/gerrytan/wsproblem.git
$ cd wsproblem
$ mvn jetty:run

2) Navigieren Sie zu http://localhost:8080und melden Sie sich entweder mit Bob / Test oder Jim / Test an

3) Klicken Sie auf "Benutzerspezifische Nachricht anfordern". Erwartet: Eine Meldung "Hallo {Benutzername}" wird neben "Nur für mich empfangene Nachricht" nur für diesen Benutzer angezeigt. Tatsächlich: Es wird nichts empfangen

Gerrytan
quelle
Haben Sie sich convertAndSendToUser (String-Benutzer, String-Ziel, T-Nachricht) angesehen? docs.spring.io/spring/docs/4.0.0.M3/javadoc-api/org/…
Viktor K.
Ja, habe das auch versucht, aber kein Glück
Gerrytan
Ich habe an einem privaten Projekt gearbeitet und dies ist die Methode, die wir verwenden und die für uns funktioniert. Ich denke, dass ein potenzielles Problem darin bestehen könnte, dass Sie "/ user / reply" abonnieren und Nachrichten an "/ user / {username} / reply" senden. Ich denke, dass Sie den Teil {Benutzername} entfernen und den convertAndSendToUser (String-Benutzer, String-Ziel, T-Nachricht) verwenden sollten.
Viktor K.
Danke, aber ich habe es versucht simpMessagingTemplate.convertAndSendToUser(principal.getName(), "/user/reply", reply);und wenn die Nachricht vom Server gesendet wird, java.lang.IllegalArgumentException: Expected destination pattern "/principal/{userId}/**"
löst
@ViktorK. ist richtig, und Sie waren ziemlich nah an der richtigen Lösung. Ihr Abonnement auf der Client-Seite war korrekt, Sie mussten einfach versuchen:convertAndSendToUser(principal.getName(), "/reply", reply);
Tip-Sy

Antworten:

82

Oh, der client side no need to known about current userServer wird das für Sie tun.

Auf der Serverseite können Sie auf folgende Weise Nachrichten an einen Benutzer senden:

simpMessagingTemplate.convertAndSendToUser(username, "/queue/reply", message);

Hinweis: Die Verwendung queue, nicht topic, Frühling immer mit queuemitsendToUser

Auf der Client-Seite

stompClient.subscribe("/user/queue/reply", handler);

Erklären

Wenn eine Websocket-Verbindung geöffnet ist, weist Spring ihr eine zu session id(nicht HttpSessionpro Verbindung zuweisen). Wenn Ihr Client einen Kanal abonniert, beginnen Sie beispielsweise mit /user/: /user/queue/replyIhre Serverinstanz abonniert eine Warteschlange mit dem Namenqueue/reply-user[session id]

Wenn Sie Nachricht an Benutzer senden verwenden, z. B.: Benutzername ist admin Sie werden schreibensimpMessagingTemplate.convertAndSendToUser("admin", "/queue/reply", message);

Die Feder bestimmt, welche session iddem Benutzer zugeordnet ist admin. Beispiel: Es wurden zwei Sitzungen gefunden, wsxedc123und thnujm456Spring übersetzt es in zwei Ziele queue/reply-userwsxedc123und queue/reply-userthnujm456sendet Ihre Nachricht mit zwei Zielen an Ihren Nachrichtenbroker.

Der Nachrichtenbroker empfängt die Nachrichten und gibt sie an Ihre Serverinstanz zurück, die die Sitzung enthält, die jeder Sitzung entspricht (WebSocket-Sitzungen können von einem oder mehreren Servern gehalten werden). Spring übersetzt die Nachricht in destination(zB :) user/queue/replyund session id(zB :) wsxedc123. Dann sendet es die Nachricht an die entsprechendeWebsocket session

Thanh Nguyen Van
quelle
7
Können Sie bitte erklären, woher Sie den Benutzernamen kennen? In Ihrem Beispiel haben Sie gesagt, der Benutzername ist 'admin'. Sie erhalten den Benutzernamen vom Benutzer?
Jorj
1
Der Benutzername wurde erhalten, HttpSessionals eine Websocket-Verbindung hergestellt wurde
Thanh Nguyen Van
1
Könnten Sie bitte weitere Informationen zum Festlegen des Benutzernamens geben?. Gibt es überhaupt eine Möglichkeit, den Benutzernamen per Abonnement zu senden?
Andres
1
@Andres: Sie können DefaultHandshakeHandlerMethode erweitern und überschreibendetermineUser
Thanh Nguyen Van
1
Ihre Erklärung funktioniert hervorragend. Doch wo ist der queue/reply-user[session id]Teil im offiziellen Dokument?
hbrls
35

Ah, ich habe herausgefunden, was mein Problem war. Zuerst habe ich das /userPräfix nicht beim einfachen Broker registriert

<websocket:simple-broker prefix="/topic,/user" />

Dann brauche ich /userbeim Senden kein zusätzliches Präfix:

convertAndSendToUser(principal.getName(), "/reply", reply);

Spring wird "/user/" + principal.getName()dem Ziel automatisch vorangestellt und daher in "/ user / bob / reply" aufgelöst.

Dies bedeutet auch, dass ich in Javascript eine andere Adresse pro Benutzer abonnieren musste

stompClient.subscribe('/user/' + userName + '/reply,...) 
Gerrytan
quelle
3
Mmm ... Gibt es eine Möglichkeit zu vermeiden, den Benutzernamen clientseitig einzurichten? Wenn Sie diesen Wert ändern (z. B. indem Sie einen anderen Benutzernamen verwenden), können Sie Nachrichten anderer anzeigen.
Vdenotaris
2
Siehe meine Lösung: stackoverflow.com/questions/25646671/…
vdenotaris
Wie abonniere ich, um dem Benutzer von Methoden zu antworten, die mit Anmerkungen versehen sind, @RequestMappingund @MessageMappingsiehe auch hier ? Wie abonniere ich mithilfe der Sping Websocket-Integration für einen bestimmten Benutzernamen (Benutzer-ID) + erhalte Benachrichtigungen von Methoden, die mit @RequestMapping versehen sind?
Shantaram Tupe
Hey mein Freund, kannst du mir das sagen? Was ist dieser "Benutzer" überhaupt? Ich verwende Spring Security und meine Benutzer (Benutzername) sind E-Mail-Adressen. Wenn sich jeder Benutzer anmeldet, erhält er sein JWT-Token für Spring Security.
Francisco Souza
Ich hatte dieselben Probleme und suchte stundenlang, bevor ich zu Ihrer Antwort kam. NUR deine Erklärung hat mir geholfen. Ich danke dir sehr!!
Navaneeth
3

Ich habe auch ein Beispiel-Websocket-Projekt mit STOMP erstellt. Was ich bemerke, ist das

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

}}

Es funktioniert unabhängig davon, ob "/ user" in config.enableSimpleBroker enthalten ist (...

Kans
quelle
2

Meine Lösung basiert auf der besten Erklärung von Thanh Nguyen Van, aber zusätzlich habe ich MessageBrokerRegistry konfiguriert:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/queue/", "/topic/");
        ...
    }
    ...
}
Nikolay Shabak
quelle
2

Genau das habe ich auch getan und es funktioniert ohne Benutzer

@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");
    }
}
Sid
quelle
Hey, wo benutzt du das jemals /app? In welchem ​​Fall?
Francisco Souza