Escribir un chat con Spring Boot y WebSockets





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









En la creación de notificaciones escalables similares a las de Facebook usando eventos enviados por el servidor y Redis, usamos eventos enviados por el servidor para enviar mensajes desde el servidor al cliente. También mencionó WebSocket, una tecnología de comunicación bidireccional entre un servidor y un cliente.



En este artículo, veremos uno de los casos de uso comunes de WebSocket. Escribiremos una aplicación de mensajería privada.



El siguiente video demuestra lo que vamos a hacer.





Introducción a WebSockets y STOMP



WebSocket es un protocolo para la comunicación bidireccional entre el servidor y el cliente.

WebSocket, a diferencia de HTTP, el protocolo de capa de aplicación, es un protocolo de capa de transporte (TCP). Aunque se utiliza HTTP para la conexión inicial, la conexión se "actualiza" a la conexión TCP utilizada en WebSocket.



WebSocket es un protocolo de bajo nivel que no define formatos de mensaje. Por lo tanto, WebSocket RFC define subprotocolos que describen la estructura y los estándares de los mensajes. Usaremos STOMP sobre WebSockets (STOMP sobre WebSockets).



El protocolo STOMP (el Simple / Streaming the Text Oriented the Message Protocol) define las reglas para el intercambio de mensajes entre el servidor y el cliente.



STOMP es similar a HTTP y se ejecuta sobre TCP mediante los siguientes comandos:



  • CONECTAR
  • SUSCRIBIR
  • ANULAR SUSCRIPCIÓN
  • ENVIAR
  • EMPEZAR
  • COMETER
  • ACK


La especificación y la lista completa de comandos STOMP se pueden encontrar aquí .



Arquitectura







  • El Servicio de autenticación es responsable de autenticar y administrar usuarios. No reinventaremos la rueda aquí y usaremos el servicio de autenticación de JWT y Social Authentication usando Spring Boot .
  • El Servicio de chat es responsable de configurar WebSocket, manejar los mensajes STOMP y almacenar y procesar los mensajes de los usuarios.
  • El cliente de chat es una aplicación ReactJS que utiliza un cliente STOMP para conectarse y suscribirse a un chat. También aquí está la interfaz de usuario.


Modelo de mensaje



Lo primero en lo que pensar es en el modelo de mensaje. ChatMessage se ve así:



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 clase es ChatMessagebastante simple, con campos necesarios para identificar al remitente y al destinatario.



También tiene un campo de estado que indica si el mensaje se ha entregado al cliente.



public enum MessageStatus {
    RECEIVED, DELIVERED
}


Cuando el servidor recibe un mensaje de un chat, no envía el mensaje directamente al destinatario, sino que envía una notificación ( ChatNotification ) para notificar al cliente que se ha recibido un nuevo mensaje. Después de eso, el propio cliente puede recibir un nuevo mensaje. Tan pronto como el cliente recibe el mensaje, se marca como ENTREGADO.



La notificación se ve así:



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


La notificación contiene el ID del nuevo mensaje e información sobre el remitente para que el cliente pueda mostrar información sobre el nuevo mensaje o la cantidad de mensajes nuevos, como se muestra a continuación.











Configuración de WebSocket y STOMP en Spring



El primer paso es configurar el punto final y el agente de mensajes de STOMP.



Para hacer esto, creamos una clase WebSocketConfig con anotaciones @Configurationy @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;
    }
}


El primer método configura un intermediario de mensajes en memoria simple con una única dirección prefijada /userpara enviar y recibir mensajes. Las direcciones prefijadas /appson para mensajes procesados ​​por métodos anotados @MessageMapping, que discutiremos en la siguiente sección.



El segundo método registra el punto final STOMP /ws. Los clientes utilizarán este punto final para conectarse al servidor STOMP. Esto también incluye un SockJS alternativo que se utilizará si WebSocket no está disponible.



El último método configura el convertidor JSON que Spring usa para convertir mensajes a / desde JSON.



Controlador para el manejo de mensajes



En esta sección, crearemos un controlador que manejará las solicitudes. Recibirá un mensaje del usuario y se lo enviará al destinatario.



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


Usamos la anotación @MessageMappingpara configurar que cuando se envíe el mensaje a /app/chat, se llame al método processMessage. Tenga en cuenta que el prefijo de la aplicación configurado previamente se agregará a la asignación /app.



Este método almacena el mensaje en MongoDB y luego llama al método convertAndSendToUserpara enviar la notificación al objetivo.



El método de convertAndSendToUseragregar un prefijo /usery recipientIdla dirección /queue/messages. La dirección final se verá así:



/user/{recipientId}/queue/messages


Todos los suscriptores a esta dirección (en nuestro caso, una) recibirán el mensaje.



Generando chatId



Para cada conversación entre dos usuarios, creamos una sala de chat y generamos una única para identificarla chatId.



La clase ChatRoom se ve así:



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


El valor chatIdes igual a la concatenación senderId_recipientId. Para cada conversación, mantenemos dos entidades con lo mismo chatId: una entre el remitente y el destinatario, y la otra entre el destinatario y el remitente, para que ambos usuarios obtengan lo mismo chatId.



Cliente JavaScript



En esta sección, crearemos un cliente de JavaScript que enviará y recibirá mensajes de un servidor WebSocket / STOMP.



Usaremos SockJS y Stomp.js para comunicarnos con el servidor usando STOMP sobre 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);
  };


El método connect()establece una conexión a /ws, donde nuestro servidor está esperando conexiones, y también define una función de devolución de llamada a la onConnectedque se llamará después de una conexión exitosa y se onErrorllamará si ocurre un error al conectarse al servidor.



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

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


El método se onConnected()suscribe a una dirección específica y recibe todos los mensajes enviados allí.



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


Al final del método, sendMessage()se envía un mensaje a la dirección /app/chatque se especifica en nuestro controlador.



Conclusión



En este artículo, hemos cubierto todos los puntos importantes de la creación de un chat usando Spring Boot y STOMP sobre WebSocket.



También creamos un cliente de JavaScript utilizando las bibliotecas SockJs y Stomp.js .



El código fuente de muestra se puede encontrar aquí .






Obtenga más información sobre el curso.











All Articles