Spring WebSockets: Construire un chat utilisateur

Spring WebSockets: Construire un chat utilisateur

1. introduction

Dans ce didacticiel, nous allons décrire commentuse Spring WebSockets to send STOMP messages to a single user. C'est important car nous ne voulons parfois pas diffuser chaque message à chaque utilisateur. En outre, nous vous montrerons comment envoyer ces messages de manière sécurisée.

Pour une introduction aux WebSockets, consultez l'excellent tutoriel dethis pour savoir comment être opérationnel. Et, pour une plongée plus approfondie dans la sécurité, consultez l'article dethis pour sécuriser votre implémentation WebSockets.

2. Files d'attente, sujets et points de terminaison

Il y a desthree main ways to say where messages are sent and how they are subscribed to utilisant Spring WebSockets et STOMP:

  1. Topics - conversations ou sujets de discussion courants ouverts à tout client ou utilisateur

  2. Queues - réservé à des utilisateurs spécifiques et à leurs sessions actuelles

  3. Endpoints - points de terminaison génériques

Maintenant, jetons un coup d'œil à un exemple de chemin de contexte pour chacun:

  • “/topic/movies”

  • “/user/queue/specific-user”

  • “/secured/chat”

Il est important de noter que we must use queues to send messages to specific users, as topics and endpoints don’t support this functionality.

3. Configuration

Voyons maintenant comment configurer notre application afin que nous puissions envoyer des messages à un utilisateur spécifique:

public class SocketBrokerConfig extends
  AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/secured/user/queue/specific-user");
        config.setApplicationDestinationPrefixes("/spring-security-mvc-socket");
        config.setUserDestinationPrefix("/secured/user");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/secured/room").withSockJS();
    }
}

Veillons à inclure une destination utilisateur, car cela détermine les points de terminaison réservés à des utilisateurs uniques.

Nous préfixons également toutes nos files d'attente et destinations utilisateur avec“/secured” pour qu'elles nécessitent une authentification. Pour les points de terminaison non protégés, nous pouvons supprimer le préfixe“/secured” (en raison de nos autres paramètres de sécurité).

Du point de vue depom.xml, aucune dépendance supplémentaire n'est requise.

4. Mappages d'URL

Nous souhaitons que notre client s'abonne à une file d'attente à l'aide d'un mappage d'URL conforme au modèle suivant:

"/user/queue/updates"

Ce mappage sera automatiquement transformé parUserDestinationMessageHandler en adresse spécifique à la session utilisateur.

Par exemple, si nous avons un utilisateur nommé“user123”, l'adresse correspondante serait:

"/queue/updates-user123"

Côté serveur, nous enverrons notre réponse spécifique à l'utilisateur à l'aide du modèle de mappage d'URL suivant:

"/user/{username}/queue/updates"

Cela aussi sera transformé en le mappage d'URL correct que nous avons déjà souscrit au côté client.

Ainsi, on voit quethe essential ingredients here are two-fold:

  1. Ajoutez notre préfixe de destination utilisateur spécifié (configuré enAbstractWebSocketMessageBrokerConfigurer).

  2. Utilisez“/queue” quelque part dans le mappage.

Dans la section suivante, nous verrons exactement comment procéder.

5. Appel deconvertAndSendToUser()

Nous pouvons invoquer de manière non statiqueconvertAndSendToUser() depuisSimpMessagingTemplate ouSimpMessageSendingOperations:

@Autowired
private SimpMessagingTemplate simpMessagingTemplate;

@MessageMapping("/secured/room")
public void sendSpecific(
  @Payload Message msg,
  Principal user,
  @Header("simpSessionId") String sessionId) throws Exception {
    OutputMessage out = new OutputMessage(
      msg.getFrom(),
      msg.getText(),
      new SimpleDateFormat("HH:mm").format(new Date()));
    simpMessagingTemplate.convertAndSendToUser(
      msg.getTo(), "/secured/user/queue/specific-user", out);
}

Vous avez peut-être remarqué:

@Header("simpSessionId") String sessionId

The @Header annotation allows access to headers exposed by the inbound message. Par exemple, nous pouvons saisir lessessionId actuels sans avoir besoin d'intercepteurs compliqués. De même,we can access the current user via Principal.

Il est important de noter que l'approche que nous adoptons dans cet article offre une meilleure personnalisation de l'annotation@sendToUser en ce qui concerne les mappages d'URL. Pour en savoir plus sur cette annotation, consultez l'excellent article dethis.

Côté client, nous utiliseronsconnect() en JavaScript pourinitialize a SockJS instance and connect to our WebSocket server using STOMP:

var socket = new SockJS('/secured/room');
var stompClient = Stomp.over(socket);
var sessionId = "";

stompClient.connect({}, function (frame) {
    var url = stompClient.ws._transport.url;
    url = url.replace(
      "ws://localhost:8080/spring-security-mvc-socket/secured/room/",  "");
    url = url.replace("/websocket", "");
    url = url.replace(/^[0-9]+\//, "");
    console.log("Your current session is: " + url);
    sessionId = url;
}

Nous accédons également auxsessionId fournis et les ajoutons au mappage d'URL de «secured/room . This gives us the ability to dynamically and manually supply a user-specific subscription queue:

stompClient.subscribe('secured/user/queue/specific-user'
  + '-user' + that.sessionId, function (msgOut) {
     //handle messages
}

Une fois que tout est configuré, nous devrions voir:

 

image

Et dans notre console de serveur:

image

6. Conclusion

Consultez le Springblog officiel et leofficial documentation pour plus d'informations sur ce sujet.

Comme toujours, les exemples de code utilisés dans cet article sont disponiblesover on GitHub.