Wysyłanie wiadomości do konkretnego użytkownika w Spring Websocket

85

Jak wysłać wiadomość WebSocket z serwera tylko do określonego użytkownika?

Moja aplikacja internetowa ma ustawienia zabezpieczeń sprężynowych i korzysta z gniazda sieciowego. Mam trudny problem podczas próby wysłania wiadomości z serwera tylko do określonego użytkownika .

Moje zrozumienie z przeczytania instrukcji pochodzi z serwera, który możemy zrobić

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

A po stronie klienta:

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

Ale nigdy nie mogłem wywołać wywołania zwrotnego subskrypcji. Próbowałem wielu różnych ścieżek, ale bez powodzenia.

Jeśli wyślę to na / topic / odpowiedz, to działa, ale wszyscy inni podłączeni użytkownicy też to otrzymają.

Aby zilustrować problem, stworzyłem ten mały projekt na github: https://github.com/gerrytan/wsproblem

Kroki ku reprodukcji:

1) Sklonuj i skompiluj projekt (upewnij się, że używasz jdk 1.7 i maven 3.1)

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

2) Przejdź do http://localhost:8080, zaloguj się za pomocą bob / test lub jim / test

3) Kliknij „Poproś o wiad. Użytkownika”. Oczekiwano: wiadomość „cześć {nazwa użytkownika}” jest wyświetlana obok pozycji „Tylko otrzymana wiadomość do mnie” tylko dla tego użytkownika. Rzeczywiste: nic nie zostało odebrane

gerrytan
źródło
Czy patrzyłeś na convertAndSendToUser (użytkownik ciągu, miejsce docelowe ciągu, wiadomość T)? docs.spring.io/spring/docs/4.0.0.M3/javadoc-api/org/…
Viktor K.
Tak też próbowałem, ale bez powodzenia
gerrytan
Pracowałem nad projektem prywatnym i to jest metoda, którą stosujemy i to działa na nas. Myślę, że potencjalnym problemem może być to, że subskrybujesz „/ user / response” i wysyłasz wiadomości do „/ user / {nazwa_użytkownika} / odpowiedz”. Myślę, że powinieneś usunąć część {username} i użyć funkcji convertAndSendToUser (użytkownik ciągu, miejsce docelowe ciągu, wiadomość T).
Viktor K.
Dzięki, ale próbowałem simpMessagingTemplate.convertAndSendToUser(principal.getName(), "/user/reply", reply);i kiedy wiadomość jest wysyłana z serwera, rzuca ten wyjątekjava.lang.IllegalArgumentException: Expected destination pattern "/principal/{userId}/**"
gerrytan
@ViktorK. ma rację i byłeś dość blisko właściwego rozwiązania. Twoja subskrypcja po stronie klienta była poprawna, po prostu musiałeś spróbować:convertAndSendToUser(principal.getName(), "/reply", reply);
Tip-Sy

Odpowiedzi:

81

Och, client side no need to known about current userserwer zrobi to za Ciebie.

Po stronie serwera, wysyłając wiadomość do użytkownika w następujący sposób:

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

Uwaga: queueNie topicużywaj Spring zawsze używaj queuezsendToUser

Po stronie klienta

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

Wyjaśnić

Gdy którekolwiek połączenie Websocket jest otwarte, Spring przypisze mu session id(nie HttpSessionprzypisz na połączenie). A kiedy twój klient zasubskrybuje kanał, zaczynając /user/np .: /user/queue/reply, twoja instancja serwera zasubskrybuje kolejkę o nazwiequeue/reply-user[session id]

Przy użyciu wyślij wiadomość do użytkownika np .: nazwa użytkownika to admin Ty napiszeszsimpMessagingTemplate.convertAndSendToUser("admin", "/queue/reply", message);

Spring określi, który session idmapowany na użytkownika admin. Np .: Znalazł dwie sesje wsxedc123i thnujm456Spring przetłumaczy je na 2 miejsca docelowe queue/reply-userwsxedc123i queue/reply-userthnujm456wyśle ​​Twoją wiadomość z 2 miejscami docelowymi do Twojego brokera wiadomości.

Broker komunikatów odbiera komunikaty i przekazuje je z powrotem do instancji serwera, która utrzymuje sesję odpowiadającą każdej sesji (sesje WebSocket mogą być utrzymywane przez jeden lub więcej serwerów). Spring przetłumaczy wiadomość na destination(np:) user/queue/replyi session id(np:) wsxedc123. Następnie wysyła wiadomość do odpowiedniegoWebsocket session

Thanh Nguyen Van
źródło
7
Czy możesz wyjaśnić, skąd znasz nazwę użytkownika? w swoim przykładzie powiedziałeś, że nazwa użytkownika to „admin”, czy otrzymałeś nazwę użytkownika od użytkownika?
Jorj,
1
Nazwa użytkownika została uzyskana HttpSessionpodczas inicjowania połączenia internetowego
Thanh Nguyen Van
1
czy możesz podać więcej informacji na temat ustawiania nazwy użytkownika ?. czy w ogóle mogę wysłać nazwę użytkownika w wiadomości o subskrypcji?
Andres
1
@Andres: Możesz rozszerzyć DefaultHandshakeHandleri zastąpić metodędetermineUser
Thanh Nguyen Van
1
Twoje wyjaśnienie działa świetnie. Jednak gdzie jest ta queue/reply-user[session id]część w oficjalnym dokumencie?
hbrls
35

Ach, znalazłem mój problem. Najpierw nie zarejestrowałem /userprefiksu w prostym brokerze

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

Wtedy nie potrzebuję dodatkowego /userprefiksu podczas wysyłania:

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

Wiosna automatycznie doda "/user/" + principal.getName()do miejsca docelowego, dlatego zamienia się na „/ user / bob / Reply”.

Oznacza to również, że w javascript musiałem subskrybować inny adres dla każdego użytkownika

stompClient.subscribe('/user/' + userName + '/reply,...) 
gerrytan
źródło
3
Mmm ... Czy jest sposób, aby uniknąć konfigurowania nazwy użytkownika po stronie klienta? Zmieniając tę ​​wartość (na przykład używając innej nazwy użytkownika), będziesz mógł zobaczyć wiadomości innych osób.
vdenotaris
2
Zobacz moje rozwiązanie: stackoverflow.com/questions/25646671/…
vdenotaris
jak zasubskrybować, aby odpowiedzieć użytkownikowi z metod oznaczonych adnotacjami, @RequestMappinga także @MessageMappingzobaczyć tutaj. Jak zasubskrybować za pomocą integracji Sping Websocket dla określonej nazwy użytkownika (userId) + otrzymywać powiadomienia z metody anotowanej @RequestMapping?
Shantaram Tupe
Hej przyjacielu, możesz mi to powiedzieć? Czym właściwie jest ten „użytkownik”? Używam Spring Security, a moi użytkownicy (nazwa użytkownika) to adresy e-mail. Kiedy każdy użytkownik loguje się, otrzymuje swój token JWT na Spring Security.
Francisco Souza,
Napotkałem te same problemy, przeszukiwane godzinami przed uzyskaniem odpowiedzi. TYLKO twoja eksploracja mi pomogła. Dziękuję bardzo!!
Navaneeth
3

Stworzyłem przykładowy projekt websocket przy użyciu STOMP. To, co zauważam, to to

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

}

działa niezależnie od tego, czy „/ user” znajduje się w config.enableSimpleBroker (...

Kans
źródło
2

Moje rozwiązanie w oparciu o najlepsze wyjaśnienie Thanha Nguyena Van'a, ale dodatkowo skonfigurowałem MessageBrokerRegistry:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/queue/", "/topic/");
        ...
    }
    ...
}
Nikolay Shabak
źródło
2

Dokładnie zrobiłem to samo i działa bez użycia użytkownika

@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
źródło
Hej, gdzie tego używasz /app? W którym to przypadku?
Francisco Souza