Discuter avec Spring Boot et WebSockets





. « Spring Framework» . , , : « Spring», .









Dans la création d'une notification évolutive de type Facebook à l'aide d'un événement envoyé par le serveur et de Redis, nous avons utilisé des événements envoyés par le serveur pour envoyer des messages du serveur au client. Il a également mentionné WebSocket - une technologie de communication bidirectionnelle entre un serveur et un client.



Dans cet article, nous examinerons l'un des cas d'utilisation courants de WebSocket. Nous écrirons une application de messagerie privée.



La vidéo ci-dessous montre ce que nous allons faire.





Introduction aux WebSockets et STOMP



WebSocket est un protocole de communication bidirectionnelle entre le serveur et le client.

WebSocket, contrairement à HTTP, le protocole de couche application, est un protocole de couche de transport (TCP). Bien que HTTP soit utilisé pour la connexion initiale, la connexion est alors "mise à niveau" vers la connexion TCP utilisée dans WebSocket.



WebSocket est un protocole de bas niveau qui ne définit pas les formats de message. Par conséquent, la RFC WebSocket définit des sous-protocoles qui décrivent la structure et les normes des messages. Nous utiliserons STOMP sur WebSockets (STOMP sur WebSockets).



Le protocole STOMP (le protocole Simple / Streaming the Text Oriented the Message Protocol) définit les règles d'échange de messages entre le serveur et le client.



STOMP est similaire à HTTP et s'exécute au-dessus de TCP à l'aide des commandes suivantes:



  • RELIER
  • SOUSCRIRE
  • SE DÉSABONNER
  • ENVOYER
  • COMMENCER
  • COMMETTRE
  • ACK


La spécification et la liste complète des commandes STOMP peuvent être trouvées ici .



Architecture







  • Le service d'authentification est responsable de l'authentification et de la gestion des utilisateurs. Nous ne réinventerons pas la roue ici et utiliserons le service d'authentification de JWT et d'authentification sociale à l'aide de Spring Boot .
  • Le service de conversation est responsable de la configuration de WebSocket, de la gestion des messages STOMP et du stockage et du traitement des messages des utilisateurs.
  • Le client de discussion est une application ReactJS qui utilise un client STOMP pour se connecter et s'abonner à une discussion. Voici également l'interface utilisateur.


Modèle de message



La première chose à laquelle il faut penser est le modèle de message. ChatMessage ressemble à ceci:



public class ChatMessage {
   @Id
   private String id;
   private String chatId;
   private String senderId;
   private String recipientId;
   private String senderName;
   private String recipientName;
   private String content;
   private Date timestamp;
   private MessageStatus status;
}


La classe est ChatMessageassez simple, avec des champs nécessaires pour identifier l'expéditeur et le destinataire.



Il comporte également un champ d'état indiquant si le message a été remis au client.



public enum MessageStatus {
    RECEIVED, DELIVERED
}


Lorsque le serveur reçoit un message d'une conversation, il n'envoie pas le message directement au destinataire, mais envoie une notification ( ChatNotification ) pour informer le client qu'un nouveau message a été reçu. Après cela, le client lui-même peut recevoir un nouveau message. Dès que le client reçoit le message, il est marqué comme DELIVERED.



La notification ressemble à ceci:



public class ChatNotification {
    private String id;
    private String senderId;
    private String senderName;
}


La notification contient l'ID du nouveau message et des informations sur l'expéditeur afin que le client puisse afficher des informations sur le nouveau message ou le nombre de nouveaux messages, comme indiqué ci-dessous.











Configurer WebSocket et STOMP au printemps



La première étape consiste à configurer le point de terminaison STOMP et le courtier de messages.



Pour ce faire, nous créons une classe WebSocketConfig avec des annotations @Configurationet @EnableWebSocketMessageBroker.



@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker( "/user");
        config.setApplicationDestinationPrefixes("/app");
        config.setUserDestinationPrefix("/user");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry
                .addEndpoint("/ws")
                .setAllowedOrigins("*")
                .withSockJS();
    }

    @Override
    public boolean configureMessageConverters(List<MessageConverter> messageConverters) {
        DefaultContentTypeResolver resolver = new DefaultContentTypeResolver();
        resolver.setDefaultMimeType(MimeTypeUtils.APPLICATION_JSON);
        MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
        converter.setObjectMapper(new ObjectMapper());
        converter.setContentTypeResolver(resolver);
        messageConverters.add(converter);
        return false;
    }
}


La première méthode configure un courtier de messages en mémoire simple avec une seule adresse préfixée /userpour envoyer et recevoir des messages. Les adresses préfixées /appconcernent les messages traités par des méthodes annotées @MessageMapping, dont nous parlerons dans la section suivante.



La deuxième méthode enregistre le point de terminaison STOMP /ws. Ce point de terminaison sera utilisé par les clients pour se connecter au serveur STOMP. Cela inclut également un SockJS de secours qui sera utilisé si le WebSocket n'est pas disponible.



La dernière méthode configure le convertisseur JSON que Spring utilise pour convertir des messages vers / depuis JSON.



Contrôleur de traitement des messages



Dans cette section, nous allons créer un contrôleur qui gérera les demandes. Il recevra un message de l'utilisateur et l'enverra au destinataire.



@Controller
public class ChatController {

    @Autowired private SimpMessagingTemplate messagingTemplate;
    @Autowired private ChatMessageService chatMessageService;
    @Autowired private ChatRoomService chatRoomService;

    @MessageMapping("/chat")
    public void processMessage(@Payload ChatMessage chatMessage) {
        var chatId = chatRoomService
                .getChatId(chatMessage.getSenderId(), chatMessage.getRecipientId(), true);
        chatMessage.setChatId(chatId.get());

        ChatMessage saved = chatMessageService.save(chatMessage);
        
        messagingTemplate.convertAndSendToUser(
                chatMessage.getRecipientId(),"/queue/messages",
                new ChatNotification(
                        saved.getId(),
                        saved.getSenderId(),
                        saved.getSenderName()));
    }
}


Avec l'aide de l'annotation, @MessageMappingnous configurons que lorsque le message est envoyé à la /app/chatméthode est appelée processMessage. Veuillez noter que le préfixe d'application précédemment configuré sera ajouté au mappage /app.



Cette méthode stocke le message dans MongoDB, puis appelle la méthode convertAndSendToUserpour envoyer la notification à la cible.



La méthode d' convertAndSendToUserajout d'un préfixe /useret recipientIdà l'adresse /queue/messages. L'adresse finale ressemblera à ceci:



/user/{recipientId}/queue/messages


Tous les abonnés de cette adresse (dans notre cas, un) recevront le message.



Génération de chatId



Pour chaque conversation entre deux utilisateurs, nous créons une salle de chat et en générons une unique pour l'identifier chatId.



La classe ChatRoom ressemble à ceci:



public class ChatRoom {
    private String id;
    private String chatId;
    private String senderId;
    private String recipientId;
}


La valeur chatIdest égale à la concaténation senderId_recipientId. Pour chaque conversation, nous gardons deux entités identiques chatId: l'une entre l'expéditeur et le destinataire, et l'autre entre le destinataire et l'expéditeur, afin que les deux utilisateurs reçoivent la même chose chatId.



Client JavaScript



Dans cette section, nous allons créer un client JavaScript qui enverra des messages et en recevra le serveur WebSocket / STOMP.



Nous utiliserons SockJS et Stomp.js pour communiquer avec le serveur en utilisant STOMP sur WebSocket.



const connect = () => {
    const Stomp = require("stompjs");
    var SockJS = require("sockjs-client");
    SockJS = new SockJS("http://localhost:8080/ws");
    stompClient = Stomp.over(SockJS);
    stompClient.connect({}, onConnected, onError);
  };


La méthode connect()établit une connexion à /ws, où notre serveur attend les connexions, et définit également une fonction de rappel onConnectedqui sera appelée en cas de connexion réussie et onErrorappelée si une erreur s'est produite lors de la connexion au serveur.



const onConnected = () => {
    console.log("connected");

    stompClient.subscribe(
      "/user/" + currentUser.id + "/queue/messages",
      onMessageReceived
    );
  };


La méthode onConnected()s'abonne à une adresse spécifique et reçoit tous les messages qui y sont envoyés.



const sendMessage = (msg) => {
    if (msg.trim() !== "") {
      const message = {
        senderId: currentUser.id,
        recipientId: activeContact.id,
        senderName: currentUser.name,
        recipientName: activeContact.name,
        content: msg,
        timestamp: new Date(),
      };
        
      stompClient.send("/app/chat", {}, JSON.stringify(message));
    }
  };


À la fin de la méthode, un sendMessage()message est envoyé à l'adresse /app/chatqui est spécifiée dans notre contrôleur.



Conclusion



Dans cet article, nous avons couvert tous les points importants de la création d'une discussion en utilisant Spring Boot et STOMP sur WebSocket.



Nous avons également construit un client JavaScript en utilisant les bibliothèques SockJs et Stomp.js .



Un exemple de code source peut être trouvé ici .






En savoir plus sur le cours.











All Articles