Como enviar mensagem de websocket do servidor apenas para um usuário específico?
Meu webapp tem configuração de segurança Spring e usa websocket. Estou encontrando um problema complicado ao tentar enviar mensagem do servidor apenas para um usuário específico .
Meu entendimento ao ler o manual é do servidor que podemos fazer
simpMessagingTemplate.convertAndSend("/user/{username}/reply", reply);
E do lado do cliente:
stompClient.subscribe('/user/reply', handler);
Mas eu nunca poderia obter o retorno de chamada da assinatura. Eu tentei muitos caminhos diferentes, mas sem sorte.
Se eu enviar para / topic / reply, ele funciona, mas todos os outros usuários conectados também o receberão.
Para ilustrar o problema, criei este pequeno projeto no github: https://github.com/gerrytan/wsproblem
Passos para reproduzir:
1) Clone e construa o projeto (certifique-se de usar o jdk 1.7 e o maven 3.1)
$ git clone https://github.com/gerrytan/wsproblem.git
$ cd wsproblem
$ mvn jetty:run
2) Acesse http://localhost:8080
, faça login usando bob / test ou jim / test
3) Clique em "Solicitar mensagem específica do usuário". Esperado: uma mensagem "olá {nome de usuário}" é exibida ao lado de "Mensagem recebida apenas para mim" apenas para este usuário. Real: nada foi recebido
fonte
simpMessagingTemplate.convertAndSendToUser(principal.getName(), "/user/reply", reply);
e quando a mensagem é enviada do servidor ele lança esta exceçãojava.lang.IllegalArgumentException: Expected destination pattern "/principal/{userId}/**"
convertAndSendToUser(principal.getName(), "/reply", reply);
Respostas:
Oh, o
client side no need to known about current user
servidor fará isso por você.No lado do servidor, usando a seguinte maneira de enviar mensagem a um usuário:
simpMessagingTemplate.convertAndSendToUser(username, "/queue/reply", message);
Nota: Usando
queue
, nãotopic
, Spring sempre usandoqueue
comsendToUser
Do lado do cliente
stompClient.subscribe("/user/queue/reply", handler);
Explicar
Quando qualquer conexão de websocket é aberta, Spring irá atribuir a ela um
session id
(nãoHttpSession
, atribuir por conexão). E quando seu cliente se inscreve em um canal começa com/user/
, por/user/queue/reply
exemplo:, sua instância de servidor se inscreve em uma fila chamadaqueue/reply-user[session id]
Quando usar enviar mensagem ao usuário, por exemplo: nome de usuário é
admin
Você escreverásimpMessagingTemplate.convertAndSendToUser("admin", "/queue/reply", message);
O Spring determinará qual
session id
mapeado para o usuárioadmin
. Ex: Encontrou duas sessõeswsxedc123
ethnujm456
, o Spring irá traduzir para 2 destinosqueue/reply-userwsxedc123
equeue/reply-userthnujm456
, e enviará sua mensagem com 2 destinos para o seu corretor de mensagens.O agente de mensagens recebe as mensagens e as fornece de volta para a instância do servidor que mantém a sessão correspondente a cada sessão (as sessões do WebSocket podem ser mantidas por um ou mais servidores). O Spring irá traduzir a mensagem para
destination
(por exemplouser/queue/reply
:) esession id
(por exemplowsxedc123
:). Em seguida, ele envia a mensagem para o correspondenteWebsocket session
fonte
HttpSession
ao iniciar uma conexão de websocketDefaultHandshakeHandler
e substituir o métododetermineUser
queue/reply-user[session id]
parte no documento oficial?Ah eu descobri qual era o meu problema. Primeiro eu não registrei o
/user
prefixo no corretor simples<websocket:simple-broker prefix="/topic,/user" />
Então, não preciso do
/user
prefixo extra ao enviar:convertAndSendToUser(principal.getName(), "/reply", reply);
O Spring irá anexar automaticamente
"/user/" + principal.getName()
ao destino, portanto, ele é resolvido em "/ user / bob / reply".Isso também significa que em javascript eu tive que me inscrever em endereços diferentes por usuário
stompClient.subscribe('/user/' + userName + '/reply,...)
fonte
@RequestMapping
e também@MessageMapping
veja aqui Como se inscrever usando a integração Sping Websocket em userName específico (userId) + obter notificações de método anotado com @RequestMapping?Eu criei um projeto de websocket de amostra usando STOMP também. O que estou percebendo é que
@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(); }
}
funciona independentemente de "/ user" estar ou não incluído em config.enableSimpleBroker (...
fonte
Minha solução disso é baseada na melhor explicação de Thanh Nguyen Van, mas, além disso, configurei MessageBrokerRegistry:
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/queue/", "/topic/"); ... } ... }
fonte
Fiz exatamente o mesmo e está funcionando sem usar o usuário
@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"); } }
fonte
/app
? Nesse caso?