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
źródło
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}/**"
convertAndSendToUser(principal.getName(), "/reply", reply);
Odpowiedzi:
Och,
client side no need to known about current user
serwer 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:
queue
Nietopic
używaj Spring zawsze używajqueue
zsendToUser
Po stronie klienta
stompClient.subscribe("/user/queue/reply", handler);
Wyjaśnić
Gdy którekolwiek połączenie Websocket jest otwarte, Spring przypisze mu
session id
(nieHttpSession
przypisz 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 id
mapowany na użytkownikaadmin
. Np .: Znalazł dwie sesjewsxedc123
ithnujm456
Spring przetłumaczy je na 2 miejsca docelowequeue/reply-userwsxedc123
iqueue/reply-userthnujm456
wyś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/reply
isession id
(np:)wsxedc123
. Następnie wysyła wiadomość do odpowiedniegoWebsocket session
źródło
HttpSession
podczas inicjowania połączenia internetowegoDefaultHandshakeHandler
i zastąpić metodędetermineUser
queue/reply-user[session id]
część w oficjalnym dokumencie?Ach, znalazłem mój problem. Najpierw nie zarejestrowałem
/user
prefiksu w prostym brokerze<websocket:simple-broker prefix="/topic,/user" />
Wtedy nie potrzebuję dodatkowego
/user
prefiksu 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,...)
źródło
@RequestMapping
a także@MessageMapping
zobaczyć tutaj. Jak zasubskrybować za pomocą integracji Sping Websocket dla określonej nazwy użytkownika (userId) + otrzymywać powiadomienia z metody anotowanej @RequestMapping?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 (...
źródło
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/"); ... } ... }
źródło
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"); } }
źródło
/app
? W którym to przypadku?