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


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


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,...) 

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


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 (...


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/");
        ...
    }
    ...
}

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

Hej, gdzie tego używasz /app? W którym to przypadku?
— Francisco Souza
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.