Отправка сообщения конкретному пользователю в Spring Websocket

Как отправить сообщение веб-сокета с сервера только определенному пользователю?

Мое веб-приложение имеет настройку безопасности Spring и использует веб-сокет. У меня возникла сложная проблема при попытке отправить сообщение с сервера только конкретному пользователю.

Насколько я понимаю, прочитав руководство это с сервера, который мы можем сделать

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" только для этого пользователя, Фактически: ничего не получено


person gerrytan    schedule 13.03.2014    source источник
comment
Вы смотрели convertAndSendToUser (String user, String destination, T message)? docs.spring.io/spring/docs/4.0.0.M3/javadoc-api/org/   -  person Viktor K.    schedule 13.03.2014
comment
Да, тоже пробовал, но не повезло   -  person gerrytan    schedule 13.03.2014
comment
Я работал над частным проектом, и это метод, который мы используем, и он работает для нас. Я думаю, что одна из потенциальных проблем может заключаться в том, что вы подписываетесь на / user / reply и отправляете сообщения / user / {username} / reply. Я думаю, что вам следует удалить часть {username} и использовать convertAndSendToUser (String user, String destination, T message).   -  person Viktor K.    schedule 13.03.2014
comment
Спасибо, но я попробовал simpMessagingTemplate.convertAndSendToUser(principal.getName(), "/user/reply", reply);, и когда сообщение отправляется с сервера, он выдает это исключение java.lang.IllegalArgumentException: Expected destination pattern "/principal/{userId}/**"   -  person gerrytan    schedule 13.03.2014
comment
@ViktorK. правильно, и вы были довольно близки к правильному решению. Ваша подписка на стороне клиента была правильной, вам просто нужно было попробовать: convertAndSendToUser(principal.getName(), "/reply", reply);   -  person Tip-Sy    schedule 15.10.2014


Ответы (5)


О, client side no need to known about current user, сервер сделает это за вас.

На стороне сервера, используя следующий способ отправки сообщения пользователю:

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

Примечание. Используя queue, а не topic, Spring всегда использует queue с sendToUser

На стороне клиента

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

Объясните

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

При использовании отправки сообщения пользователю, например: имя пользователя admin Вы должны написать simpMessagingTemplate.convertAndSendToUser("admin", "/queue/reply", message);

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

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

person Thanh Nguyen Van    schedule 23.07.2015
comment
Не могли бы вы объяснить, откуда вы знаете имя пользователя? в вашем примере вы сказали, что имя пользователя - «admin», вы получаете имя пользователя от пользователя? - person Jorj; 23.12.2016
comment
Имя пользователя было получено от HttpSession при инициации подключения к веб-сокету. - person Thanh Nguyen Van; 25.12.2016
comment
пожалуйста, не могли бы вы дать больше информации о том, как установить имя пользователя ?. Могу ли я отправить имя пользователя в сообщении о подписке? - person Andres; 03.05.2017
comment
@Andres: вы можете расширить DefaultHandshakeHandler и переопределить метод determineUser - person Thanh Nguyen Van; 03.05.2017
comment
@Andres: Отправка имени пользователя по подписке возможна, но очень небезопасно. Пожалуйста, не надо! - person Thanh Nguyen Van; 03.05.2017
comment
Подробно об использовании determineUser & DefaultHandshakeHandler здесь: stackoverflow.com/questions/37853727/ - person Siddharth Garg; 24.12.2017
comment
Ваше объяснение отлично работает. Однако где находится queue/reply-user[session id] часть в официальном документе? - person hbrls; 27.12.2017
comment
@ThanhNguyenVan Я слежу за вашим ответом, не хотели бы вы взглянуть на мой вопрос? Благодарю . stackoverflow .com / questions / 49748468 / - person naila naseem; 11.04.2018

Ах, я нашел, в чем была моя проблема. Сначала не прописал префикс /user на простом брокере

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

Тогда мне не нужен лишний префикс /user при отправке:

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

Spring автоматически добавит "/user/" + principal.getName() к месту назначения, поэтому он преобразуется в "/ user / bob / reply".

Это также означает, что в javascript мне приходилось подписываться на разные адреса для каждого пользователя.

stompClient.subscribe('/user/' + userName + '/reply,...) 
person gerrytan    schedule 13.03.2014
comment
Ммм ... Есть ли способ избежать настройки клиентской стороны userName? Изменив это значение (например, используя другое имя пользователя), вы сможете видеть сообщения других пользователей. - person vdenotaris; 03.09.2014
comment
См. Мое решение: stackoverflow.com/questions/25646671/ - person vdenotaris; 03.09.2014
comment
как подписаться, чтобы отвечать пользователю из методов, помеченных @RequestMapping, а также @MessageMapping см. здесь Как подписаться с помощью Sping Websocket интеграция с конкретным userName (userId) + получать уведомления от метода, помеченного @RequestMapping? - person Shantaram Tupe; 04.12.2017
comment
Привет, мой друг, ты можешь мне это сказать? И вообще, что это за пользователь? Я использую Spring Security, и мои пользователи (имя пользователя) являются адресами электронной почты. Когда каждый пользователь входит в систему, он получает свой токен JWT в Spring Security. - person Francisco Souza; 14.10.2019
comment
Я столкнулся с такими же проблемами, искал часами, прежде чем пришел к вашему ответу. ТОЛЬКО ваше объяснение мне помогло. Большое спасибо!! - person Navaneeth; 15.07.2020

Я также создал образец проекта веб-сокета, используя 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 (...

person Kans    schedule 02.04.2016

Мое решение этого вопроса основано на лучшем объяснении Тхань Нгуен Вана, но, кроме того, я настроил MessageBrokerRegistry:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

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

Точно я сделал то же самое, и он работает без использования пользователя

@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");
    }
}
person Sid    schedule 31.10.2017
comment
Эй, а где ты это /app использовал? В таком случае? - person Francisco Souza; 15.10.2019