Chatten mit Spring Boot und WebSockets





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









Beim Erstellen einer skalierbaren Facebook-ähnlichen Benachrichtigung mithilfe von vom Server gesendeten Ereignissen und Redis haben wir vom Server gesendete Ereignisse verwendet, um Nachrichten vom Server an den Client zu senden. Außerdem wurde WebSocket erwähnt - eine Technologie für die bidirektionale Kommunikation zwischen einem Server und einem Client.



In diesem Artikel werfen wir einen Blick auf einen der häufigsten Anwendungsfälle für WebSocket. Wir werden eine private Messaging-Anwendung schreiben.



Das Video unten zeigt, was wir tun werden.





Einführung in WebSockets und STOMP



WebSocket ist ein Protokoll für die bidirektionale Kommunikation zwischen Server und Client.

WebSocket ist im Gegensatz zu HTTP, dem Anwendungsschichtprotokoll, ein Transportschichtprotokoll (TCP). Obwohl HTTP für die anfängliche Verbindung verwendet wird, wird die Verbindung dann auf die in WebSocket verwendete TCP-Verbindung "aktualisiert".



WebSocket ist ein Low-Level-Protokoll, das keine Nachrichtenformate definiert. Daher definiert der WebSocket RFC Unterprotokolle, die die Struktur und die Standards von Nachrichten beschreiben. Wir werden STOMP über WebSockets (STOMP über WebSockets) verwenden.



Protokoll STOMP (das Simple / das Streaming des textorientierten Nachrichtenprotokolls) definiert die Regeln für den Austausch von Nachrichten zwischen dem Server und dem Client.



STOMP ähnelt HTTP und wird mit den folgenden Befehlen auf TCP ausgeführt:



  • VERBINDEN
  • ABONNIEREN
  • ABMELDEN
  • SENDEN
  • START
  • VERPFLICHTEN
  • ACK


Die Spezifikation und die vollständige Liste der STOMP-Befehle finden Sie hier .



Die Architektur







  • Der Auth-Dienst ist für die Authentifizierung und Verwaltung der Benutzer verantwortlich. Wir werden das Rad hier nicht neu erfinden und den Authentifizierungsdienst von JWT und Social Authentication mit Spring Boot verwenden .
  • Der Chat-Dienst ist für die Konfiguration von WebSocket, die Verarbeitung von STOMP-Nachrichten sowie die Speicherung und Verarbeitung von Benutzernachrichten verantwortlich.
  • Der Chat-Client ist eine ReactJS-Anwendung, die einen STOMP-Client verwendet, um eine Verbindung herzustellen und einen Chat zu abonnieren. Hier befindet sich auch die Benutzeroberfläche.


Nachrichtenmodell



Das erste, woran Sie denken müssen, ist das Nachrichtenmodell. ChatMessage sieht folgendermaßen aus:



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


Die Klasse ist ChatMessageziemlich einfach, mit Feldern, die benötigt werden, um den Absender und den Empfänger zu identifizieren.



Es gibt auch ein Statusfeld, das angibt, ob die Nachricht an den Client übermittelt wurde.



public enum MessageStatus {
    RECEIVED, DELIVERED
}


Wenn der Server eine Nachricht von einem Chat empfängt, sendet er die Nachricht nicht direkt an den Empfänger, sondern sendet eine Benachrichtigung ( ChatNotification ), um den Client über den Empfang einer neuen Nachricht zu informieren. Danach kann der Kunde selbst eine neue Nachricht erhalten. Sobald der Client die Nachricht erhält, wird sie als LIEFERBAR markiert.



Die Benachrichtigung sieht folgendermaßen aus:



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


Die Benachrichtigung enthält die ID der neuen Nachricht und Informationen zum Absender, sodass der Client Informationen zur neuen Nachricht oder zur Anzahl der neuen Nachrichten anzeigen kann, wie unten gezeigt.











WebSocket und STOMP im Frühjahr konfigurieren



Der erste Schritt besteht darin, den STOMP-Endpunkt und den Nachrichtenbroker zu konfigurieren.



Dazu erstellen wir eine WebSocketConfig- Klasse mit Anmerkungen @Configurationund @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;
    }
}


Die erste Methode konfiguriert einen einfachen In-Memory-Nachrichtenbroker mit einer einzelnen Präfixadresse /userzum Senden und Empfangen von Nachrichten. Präfixadressen /appgelten für Nachrichten, die mit kommentierten Methoden verarbeitet werden. Dies @MessageMappingwird im nächsten Abschnitt erläutert.



Die zweite Methode registriert den STOMP-Endpunkt /ws. Dieser Endpunkt wird von Clients verwendet, um eine Verbindung zum STOMP-Server herzustellen. Dies beinhaltet auch ein Fallback-SockJS, das verwendet wird, wenn das WebSocket nicht verfügbar ist.



Die letzte Methode konfiguriert den JSON-Konverter, mit dem Spring Nachrichten in / von JSON konvertiert.



Controller zur Verarbeitung von Nachrichten



In diesem Abschnitt erstellen wir einen Controller , der Anforderungen verarbeitet. Es erhält eine Nachricht vom Benutzer und sendet sie an den Empfänger.



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


Wir verwenden Annotation, um @MessageMappingzu konfigurieren, dass /app/chatdie Methode aufgerufen wird , wenn die Nachricht an gesendet wird processMessage. Bitte beachten Sie, dass das zuvor konfigurierte Anwendungspräfix zur Zuordnung hinzugefügt wird /app.



Diese Methode speichert die Nachricht in MongoDB und ruft dann die Methode convertAndSendToUserauf, um die Benachrichtigung an das Ziel zu senden.



Die Methode zum convertAndSendToUserHinzufügen eines Präfixes /userund recipientIdder Adresse /queue/messages. Die endgültige Adresse sieht folgendermaßen aus:



/user/{recipientId}/queue/messages


Alle Abonnenten dieser Adresse (in unserem Fall einer) erhalten die Nachricht.



ChatId generieren



Für jede Konversation zwischen zwei Benutzern erstellen wir einen Chatraum und generieren einen eindeutigen, um ihn zu identifizieren chatId.



Die ChatRoom- Klasse sieht folgendermaßen aus:



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


Der Wert chatIdentspricht der Verkettung senderId_recipientId. Für jede Konversation behalten wir zwei Entitäten mit derselben bei chatId: eine zwischen dem Absender und dem Empfänger und die andere zwischen dem Empfänger und dem Absender, sodass beide Benutzer dasselbe erhalten chatId.



JavaScript-Client



In diesem Abschnitt erstellen wir einen JavaScript-Client, der Nachrichten an den WebSocket / STOMP-Server sendet und von dort empfängt.



Wir werden SockJS und Stomp.js verwenden , um mit dem Server über STOMP über WebSocket zu kommunizieren.



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


Die Methode connect()stellt eine Verbindung zu her /ws, bei der unser Server auf Verbindungen wartet, und definiert außerdem eine Rückruffunktion onConnected, die bei erfolgreicher Verbindung onErroraufgerufen und aufgerufen wird, wenn beim Herstellen einer Verbindung zum Server ein Fehler aufgetreten ist.



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

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


Die Methode onConnected()abonniert eine bestimmte Adresse und empfängt alle dort gesendeten Nachrichten.



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


Am Ende der Methode wird eine sendMessage()Nachricht an die /app/chatin unserem Controller angegebene Adresse gesendet .



Fazit



In diesem Artikel haben wir alle wichtigen Punkte beim Erstellen eines Chats mit Spring Boot und STOMP über WebSocket behandelt.



Wir haben auch einen JavaScript-Client mit den Bibliotheken SockJs und Stomp.js erstellt .



Beispielquellcode finden Sie hier .






Erfahren Sie mehr über den Kurs.











All Articles