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

87

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

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

Ответы:

85

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

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

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

Примечание. Использование Spring queue, а не topicиспользование всегда 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весна будет перевести его на 2 пункта назначения queue/reply-userwsxedc123и queue/reply-userthnujm456, и отправить сообщение с 2 направления к вашему сообщению брокера.

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

Тхань Нгуен Ван
источник
7
Не могли бы вы объяснить, откуда вы знаете имя пользователя? в вашем примере вы сказали, что имя пользователя - «admin», вы получаете имя пользователя от пользователя?
Jorj
1
Имя пользователя было получено HttpSessionпри инициировании подключения к веб-сокету
Тхань Нгуен Ван
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,...) 
Gerrytan
источник
3
Ммм ... Есть ли способ избежать настройки клиентской стороны userName? Изменив это значение (например, используя другое имя пользователя), вы сможете видеть сообщения других пользователей.
vdenotaris 03
2
См. Мое решение: stackoverflow.com/questions/25646671/…
vdenotaris 03
как подписаться, чтобы отвечать пользователю из методов, аннотированных с помощью, @RequestMappingа также @MessageMappingсм. здесь Как подписаться с использованием интеграции Sping Websocket для определенного имени пользователя (userId) + получать уведомления от метода, аннотированного с помощью @RequestMapping?
Шантарам Тупе,
Привет, мой друг, ты можешь мне это сказать? И вообще, что это за «пользователь»? Я использую Spring Security, и мои пользователи (имя пользователя) являются адресами электронной почты. Когда каждый пользователь входит в систему, он получает свой токен JWT в Spring Security.
Франсиско Соуза,
Я столкнулся с такими же проблемами, искал часами, прежде чем пришел к вашему ответу. ТОЛЬКО ваше объяснение мне помогло. Огромное спасибо!!
Navaneeth
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? В таком случае?
Франсиско Соуза,