Invio di un messaggio a un utente specifico su Spring Websocket


87

Come inviare un messaggio websocket dal server solo a un utente specifico?

La mia webapp ha una configurazione di sicurezza primaverile e utilizza websocket. Sto riscontrando un problema complicato nel tentativo di inviare un messaggio dal server solo a un utente specifico .

La mia comprensione dalla lettura del manuale proviene dal server che possiamo fare

simpMessagingTemplate.convertAndSend("/user/{username}/reply", reply);

E dal lato client:

stompClient.subscribe('/user/reply', handler);

Ma non sono mai riuscito a richiamare la richiamata dell'abbonamento. Ho provato molti percorsi diversi ma senza fortuna.

Se lo invio a / topic / reply funziona ma anche tutti gli altri utenti collegati lo riceveranno.

Per illustrare il problema ho creato questo piccolo progetto su github: https://github.com/gerrytan/wsproblem

Passaggi per riprodurre:

1) Clona e compila il progetto (assicurati di usare jdk 1.7 e maven 3.1)

$ git clone https://github.com/gerrytan/wsproblem.git
$ cd wsproblem
$ mvn jetty:run

2) Vai a http://localhost:8080, accedi utilizzando bob / test o jim / test

3) Fare clic su "Richiedi messaggio specifico utente". Previsto: un messaggio "ciao {nome utente}" viene visualizzato accanto a "Messaggio ricevuto solo a me" solo per questo utente, Attuale: non viene ricevuto nulla


Hai guardato convertAndSendToUser (utente stringa, destinazione stringa, messaggio T)? docs.spring.io/spring/docs/4.0.0.M3/javadoc-api/org/…
Viktor K.

Sì,
ho

Ho lavorato a progetti privati ​​e questo è il metodo che usiamo e sta funzionando per noi. Penso che un potenziale problema potrebbe essere che ti iscrivi a "/ user / reply" e stai inviando messaggi a "/ user / {username} / reply". Penso che dovresti rimuovere la parte {nomeutente} e utilizzare convertAndSendToUser (utente stringa, destinazione stringa, messaggio T).
Viktor K.

Grazie ma ho provato simpMessagingTemplate.convertAndSendToUser(principal.getName(), "/user/reply", reply);e quando il messaggio viene inviato dal server genera questa eccezionejava.lang.IllegalArgumentException: Expected destination pattern "/principal/{userId}/**"
gerrytan

@ViktorK. è giusto, ed eri abbastanza vicino alla soluzione giusta. Il tuo abbonamento sul lato client era corretto, dovevi semplicemente provare:convertAndSendToUser(principal.getName(), "/reply", reply);
Tip-Sy

Risposte:


85

Oh, client side no need to known about current user server lo farà per te.

Sul lato server, utilizzando il seguente modo per inviare un messaggio a un utente:

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

Nota: utilizzando queue, non topic, la primavera utilizzando sempre queueconsendToUser

Dalla parte del cliente

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

Spiegare

Quando una qualsiasi connessione websocket è aperta, Spring gli assegnerà un session id(non HttpSession, assegnazione per connessione). E quando il tuo cliente si iscrive a un canale inizia con /user/, ad esempio:/user/queue/reply esempio:, l'istanza del tuo server si iscriverà a una coda denominataqueue/reply-user[session id]

Quando si utilizza invia un messaggio all'utente, ad esempio: nome utente è admin ScriveraisimpMessagingTemplate.convertAndSendToUser("admin", "/queue/reply", message);

La primavera determinerà quale session idmappato all'utente admin. Ad esempio: ha trovato due sessioni wsxedc123e thnujm456, Spring lo tradurrà in 2 destinazioni queue/reply-userwsxedc123e queue/reply-userthnujm456, e invierà il tuo messaggio con 2 destinazioni al tuo broker di messaggi.

Il broker di messaggi riceve i messaggi e li restituisce all'istanza del server che tiene la sessione corrispondente a ciascuna sessione (le sessioni WebSocket possono essere trattenute da uno o più server). Spring tradurrà il messaggio in destination(es . user/queue/reply:) e session id(es wsxedc123.:). Quindi, invia il messaggio al corrispondenteWebsocket session


7
Puoi spiegare come conosci il nome utente? nel tuo esempio hai detto che il nome utente è "admin", ricevi il nome utente dall'utente?
Jorj

1
Il nome utente è stato ottenuto da HttpSessionquando si avvia una connessione websocket
Thanh Nguyen Van

1
per favore potresti fornire maggiori informazioni su come impostare il nome utente ?. c'è comunque che posso inviare il nome utente sul messaggio di iscrizione?
Andres

1
@Andres: puoi estendere DefaultHandshakeHandlere ignorare il metododetermineUser
Thanh Nguyen Van

1
La tua spiegazione funziona alla grande. Tuttavia dov'è la queue/reply-user[session id]parte nel documento ufficiale?
hbrls

35

Ah, ho scoperto qual era il mio problema. Per prima cosa non ho registrato il /userprefisso sul broker semplice

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

Quindi non ho bisogno del /userprefisso extra durante l'invio:

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

La primavera verrà anteposta automaticamente "/user/" + principal.getName()alla destinazione, quindi si risolverà in "/ user / bob / reply".

Ciò significa anche che in javascript dovevo iscrivermi a un indirizzo diverso per utente

stompClient.subscribe('/user/' + userName + '/reply,...) 

3
Mmm ... C'è un modo per evitare di impostare userName lato client? Modificando quel valore (ad esempio utilizzando un altro nome utente), sarai in grado di vedere i messaggi degli altri.
vdenotaris


come iscriversi per rispondere all'utente da metodi annotati con @RequestMappinge @MessageMappingvedere anche qui Come iscriversi utilizzando l'integrazione di Sping Websocket su userName specifico (userId) + ricevere notifiche dal metodo annotato con @RequestMapping?
Shantaram Tupe

Hey amico mio, puoi dirmi questo? Che cos'è questo "utente" comunque? Utilizzo Spring Security ei miei utenti (nome utente) sono indirizzi e-mail. Quando ogni utente accede ottiene il suo token JWT su Spring Security.
Francisco Souza

Ho affrontato gli stessi problemi, cercato per ore prima di arrivare alla tua risposta. SOLO la tua esplorazione mi ha aiutato. Grazie mille!!
Navaneeth

3

Ho creato anche un progetto websocket di esempio utilizzando STOMP. Quello che sto notando è questo

@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();
}

}

funziona indipendentemente dal fatto che "/ user" sia incluso o meno in config.enableSimpleBroker (...


2

La mia soluzione si basa sulla migliore spiegazione di Thanh Nguyen Van, ma in aggiunta ho configurato MessageBrokerRegistry:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/queue/", "/topic/");
        ...
    }
    ...
}

2

Esattamente ho fatto lo stesso e funziona senza utilizzare l'utente

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

Ehi, dove lo usi mai /app? In quale caso?
Francisco Souza
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.