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.