Надсилання повідомлення конкретному користувачу на Spring Websocket


85

Як надсилати повідомлення з веб-сервера з сервера лише певному користувачеві?

Мій веб-додаток налаштовано на весняну безпеку та використовує веб-сокет. Я стикаюся з хитрою проблемою, намагаючись надіслати повідомлення з сервера лише певному користувачеві .

Я зрозумів, що читаю посібник , з сервера, який ми можемо зробити

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

А з боку клієнта:

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

Але я ніколи не міг отримати зворотний виклик передплати. Я пробував багато різних шляхів, але не везе.

Якщо я надішлю його в / topic / reply, він працює, але всі інші підключені користувачі також отримають його.

Для ілюстрації проблеми я створив цей невеликий проект на github: https://github.com/gerrytan/wsproblem

Етапи відтворення:

1) Клонуйте та побудуйте проект (переконайтеся, що ви використовуєте jdk 1.7 та maven 3.1)

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

2) Перейдіть до http://localhost:8080, увійдіть за допомогою bob / test або jim / test

3) Клацніть "Запитувати повідомлення користувача". Очікується: повідомлення "hello {username}" відображається поруч із "Received Message To Me Only" лише для цього користувача, фактично: нічого не отримано


Ви розглядали convertAndSendToUser (користувач рядка, призначення рядка, повідомлення T)? docs.spring.io/spring/docs/4.0.0.M3/javadoc-api/org/…
Віктор К.

Так, спробував і це, але не пощастило
gerrytan

Я працював над приватним проектом, і цей метод ми використовуємо, і він працює для нас. Я думаю, що однією з можливих проблем може бути те, що ви підписалися на "/ user / reply" і ви надсилаєте повідомлення на "/ user / {username} / reply". Я думаю, що вам слід видалити частину {username} та використовувати convertAndSendToUser (користувач рядка, призначення рядка, повідомлення T).
Віктор К.

Дякую, але я спробував, simpMessagingTemplate.convertAndSendToUser(principal.getName(), "/user/reply", reply);і коли повідомлення відправляється з сервера, воно java.lang.IllegalArgumentException: Expected destination pattern "/principal/{userId}/**"
видає

@ViktorK. правильно, і ви були дуже близькі до правильного рішення. Ваша підписка на стороні клієнта була правильною, вам просто довелося спробувати:convertAndSendToUser(principal.getName(), "/reply", reply);
Tip-Sy

Відповіді:


81

О, client side no need to known about current userсервер зробить це за вас.

На стороні сервера, використовуючи такий спосіб відправити повідомлення користувачеві:

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

Примітка: Використовуючи queue, ні topic, Spring завжди використовуючи queuewithsendToUser

На стороні клієнта

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

Поясніть

Коли будь-яке з’єднання з веб-розеткою відкрите, Spring присвоює йому session id(не HttpSession, присвоювати для кожного з’єднання). І коли ваш клієнт підписується на канал, починаючи з /user/, наприклад,: /user/queue/reply, ваш екземпляр сервера підписується на чергу з іменемqueue/reply-user[session id]

При використанні надіслати повідомлення користувачеві, наприклад: username is admin Ви напишетеsimpMessagingTemplate.convertAndSendToUser("admin", "/queue/reply", message);

Весна визначатиме, яке session idзіставлено з користувачем admin. Напр .: він знайшов два сеанси, wsxedc123і thnujm456Spring переведе його на 2 пункти призначення queue/reply-userwsxedc123і queue/reply-userthnujm456, і він надішле ваше повідомлення з 2 адресами вашому брокеру повідомлень.

Брокер повідомлень отримує повідомлення та передає його екземпляру вашого сервера, що проведення сеансу, що відповідає кожному сеансу (сеанси WebSocket можуть утримуватися одним або кількома серверами). Spring переведе повідомлення в destination(наприклад:) user/queue/replyта session id(наприклад:) wsxedc123. Потім воно надсилає повідомлення відповідномуWebsocket session


7
Чи можете ви пояснити, як ви знаєте ім’я користувача? у вашому прикладі ви сказали ім'я користувача "admin", ви отримуєте ім'я користувача від користувача?
Jorj

1
Ім'я користувача було отримано HttpSessionпід час встановлення з'єднання через Інтернет
Thanh Nguyen Van

1
будь ласка, не могли б Ви надати більше інформації про те, як встановити ім'я користувача ?. чи можу я все-таки надіслати ім'я користувача на повідомлення про припинення?
Андрес

1
@Andres: Ви можете розширити DefaultHandshakeHandlerі перевизначити методdetermineUser
Тхань Нгуєн Ван,

1
Ваше пояснення чудово працює. Однак де ця queue/reply-user[session id]частина в офіційному документі?
hbrls

35

Ах, я знайшов, у чому моя проблема. По-перше, я не зареєстрував /userпрефікс у простого брокера

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

Тоді мені не потрібен додатковий /userпрефікс під час надсилання:

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

Весна автоматично додається "/user/" + principal.getName()до пункту призначення, отже, вона переходить у "/ user / bob / reply".

Це також означає, що в javascript мені довелося підписатися на іншу адресу для кожного користувача

stompClient.subscribe('/user/' + userName + '/reply,...) 

3
Ммм ... Чи є спосіб уникнути налаштування userName на стороні клієнта? Змінивши це значення (наприклад, використовуючи інше ім’я користувача), ви зможете бачити повідомлення інших.
vdenotaris

2
Дивіться моє рішення: stackoverflow.com/questions/25646671 / ...
vdenotaris

як підписатись, щоб відповісти користувачеві з методів, анотованих, @RequestMappingа також @MessageMappingдив. тут Як підписатись за допомогою інтеграції Sping Websocket на конкретне userName (userId) + отримувати сповіщення від методу, позначеного @RequestMapping?
Шантарам Тупе

Гей, друже, ти можеш сказати мені це? Що взагалі цей "користувач"? Я використовую Spring Security, а мої користувачі (ім’я користувача) - це адреси електронної пошти. Коли кожен користувач входить в систему, він отримує свій маркер JWT на Spring Security.
Франциско Соуза,

Я стикався з тими ж проблемами, шукав їх годинами, перш ніж прийти до вашої відповіді. ТІЛЬКИ ваше пояснення допомогло мені. Дуже дякую!!
Наванеет

3

Я також створив зразок проекту веб-розетки за допомогою STOMP. Що я помічаю, це те

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

}

він працює незалежно від того, включений "/ user" у config.enableSimpleBroker (...


2

Моє рішення на основі найкращого пояснення Тхань Нгуєна Вана, але крім того я налаштував MessageBrokerRegistry:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

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

2

Точно я зробив те саме, і він працює без використання користувача

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

Гей, де ти коли-небудь використовуєш це /app? У якому випадку?
Франсіско Суза
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.