Enviando mensagem para usuário específico no Spring Websocket


87

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


Você esteve olhando para convertAndSendToUser (String user, String destination, T message)? docs.spring.io/spring/docs/4.0.0.M3/javadoc-api/org/…
Viktor K.

Sim, tentei isso também, mas sem sorte
gerrytan de

Tenho trabalhado em um projeto privado e esse é o método que usamos e está funcionando para nós. Acho que um problema potencial pode ser que você se inscreveu em "/ usuário / resposta" e está enviando mensagens para "/ usuário / {nome de usuário} / resposta". Eu acho que você deve remover a parte {username} e usar o convertAndSendToUser (String usuário, String destino, mensagem T).
Viktor K.

Obrigado, mas eu tentei 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}/**"
gerrytan

@ViktorK. está certo e você estava bem perto da solução certa. Sua assinatura do lado do cliente estava correta, você simplesmente tinha que tentar:convertAndSendToUser(principal.getName(), "/reply", reply);
Tip-Sy

Respostas:


85

Oh, o client side no need to known about current userservidor 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ão topic, Spring sempre usando queuecomsendToUser

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ão HttpSession, atribuir por conexão). E quando seu cliente se inscreve em um canal começa com /user/, por /user/queue/replyexemplo:, 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 idmapeado para o usuário admin. Ex: Encontrou duas sessões wsxedc123e thnujm456, o Spring irá traduzir para 2 destinos queue/reply-userwsxedc123e queue/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 exemplo user/queue/reply:) e session id(por exemplo wsxedc123:). Em seguida, ele envia a mensagem para o correspondenteWebsocket session


7
Você pode explicar como sabe o nome de usuário? no seu exemplo você disse que o nome de usuário é 'admin', você recebe o nome de usuário do usuário?
Jorj

1
O nome de usuário foi obtido HttpSessionao iniciar uma conexão de websocket
Thanh Nguyen Van

1
por favor, você poderia fornecer mais informações sobre como definir o nome de usuário ?. Existe alguma maneira de enviar o nome de usuário na mensagem de inscrição?
Andres

1
@Andres: Você pode estender DefaultHandshakeHandlere substituir o métododetermineUser
Thanh Nguyen Van

1
Sua explicação funciona muito bem. Porém, onde está a queue/reply-user[session id]parte no documento oficial?
hbrls

35

Ah eu descobri qual era o meu problema. Primeiro eu não registrei o /userprefixo no corretor simples

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

Então, não preciso do /userprefixo 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,...) 

3
Mmm ... Existe uma maneira de evitar configurar o userName do lado do cliente? Alterando esse valor (por exemplo, usando outro nome de usuário), você poderá ver as mensagens de outras pessoas.
vdenotaris,


como se inscrever para responder ao usuário a partir de métodos anotados com @RequestMappinge também @MessageMappingveja aqui Como se inscrever usando a integração Sping Websocket em userName específico (userId) + obter notificações de método anotado com @RequestMapping?
Shantaram Tupe

Ei meu amigo você pode me dizer isso? Afinal, o que é esse "usuário"? Estou usando Spring Security e meus usuários (nome de usuário) são endereços de e-mail. Quando cada usuário efetua login, ele obtém seu token JWT no Spring Security.
Francisco Souza

Enfrentei os mesmos problemas, pesquisei horas antes de chegar à sua resposta. SÓ a sua explicação me ajudou. Muito obrigado!!
Navaneeth

3

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 (...


2

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

2

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

Ei, onde você usa isso /app? Nesse caso?
Francisco Souza
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.