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


Вы смотрели 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,...) 

3
Ммм ... Есть ли способ избежать настройки клиентской стороны userName? Изменив это значение (например, используя другое имя пользователя), вы сможете видеть сообщения других пользователей.
vdenotaris 03



Привет, мой друг, ты можешь мне это сказать? И вообще, что это за «пользователь»? Я использую 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? В таком случае?
Франсиско Соуза,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.