Conversando com Spring Boot e WebSockets





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









Ao construir notificações escalonáveis ​​como o Facebook usando eventos enviados pelo servidor e Redis, usamos eventos enviados pelo servidor para enviar mensagens do servidor para o cliente. Também mencionou o WebSocket, uma tecnologia de comunicação bidirecional entre um servidor e um cliente.



Neste artigo, vamos dar uma olhada em um dos casos de uso comuns para WebSocket. Vamos escrever um aplicativo de mensagens privadas.



O vídeo abaixo demonstra o que vamos fazer.





Introdução a WebSockets e STOMP



WebSocket é um protocolo para comunicação bidirecional entre servidor e cliente.

O WebSocket, ao contrário do HTTP, o protocolo da camada de aplicativo, é um protocolo da camada de transporte (TCP). Embora HTTP seja usado para a conexão inicial, a conexão é então "atualizada" para a conexão TCP usada no WebSocket.



WebSocket é um protocolo de baixo nível que não define formatos de mensagem. Portanto, o RFC WebSocket define subprotocolos que descrevem a estrutura e os padrões das mensagens. Estaremos usando STOMP sobre WebSockets (STOMP sobre WebSockets).



O protocolo STOMP (o Protocolo Simples / Streaming de Texto Orientado a Mensagem) define as regras para a troca de mensagens entre o servidor e o cliente.



STOMP é semelhante ao HTTP e é executado em cima do TCP usando os seguintes comandos:



  • CONECTAR
  • SE INSCREVER
  • CANCELAR SUBSCRIÇÃO
  • MANDAR
  • INÍCIO
  • COMPROMETE
  • ACK


A especificação e a lista completa dos comandos STOMP podem ser encontradas aqui .



Arquitetura







  • O serviço Auth é responsável por autenticar e gerenciar usuários. Não reinventaremos a roda aqui e usaremos o serviço de autenticação do JWT e autenticação social usando Spring Boot .
  • O serviço de chat é responsável por configurar o WebSocket, manipular mensagens STOMP e armazenar e processar mensagens do usuário.
  • O Chat Client é um aplicativo ReactJS que usa um cliente STOMP para se conectar e se inscrever em um chat. A interface do usuário também está localizada aqui.


Modelo de mensagem



A primeira coisa a se pensar é o modelo de mensagem. ChatMessage se parece com isto:



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


A classe é ChatMessagebem simples, com campos necessários para identificar o remetente e o destinatário.



Também possui um campo de status que indica se a mensagem foi entregue ao cliente.



public enum MessageStatus {
    RECEIVED, DELIVERED
}


Quando o servidor recebe uma mensagem de um chat, ele não envia a mensagem diretamente ao destinatário, mas envia uma notificação ( ChatNotification ) para notificar o cliente de que uma nova mensagem foi recebida. Depois disso, o próprio cliente pode receber uma nova mensagem. Assim que o cliente recebe a mensagem, ela é marcada como ENTREGUE.



A notificação tem a seguinte aparência:



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


A notificação contém o ID da nova mensagem e informações sobre o remetente para que o cliente possa mostrar informações sobre a nova mensagem ou a quantidade de novas mensagens, conforme mostrado a seguir.











Configurando WebSocket e STOMP no Spring



A primeira etapa é configurar o terminal STOMP e o intermediário de mensagem.



Para fazer isso, criamos uma classe WebSocketConfig com anotações @Configuratione @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;
    }
}


O primeiro método configura um intermediário de mensagem simples na memória com um único endereço prefixado /userpara enviar e receber mensagens. Os endereços prefixados /appsão para mensagens processadas por métodos anotados @MessageMapping, que discutiremos na próxima seção.



O segundo método registra o ponto de extremidade STOMP /ws. Este ponto final será usado por clientes para se conectar ao servidor STOMP. Isso também inclui um SockJS substituto que será usado se o WebSocket não estiver disponível.



O último método configura o conversor JSON que Spring usa para converter mensagens de / para JSON.



Controlador para tratamento de mensagens



Nesta seção, criaremos um controlador que tratará das solicitações. Ele receberá uma mensagem do usuário e a enviará ao destinatário.



@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 anotação @MessageMappingpara configurar que quando a mensagem é enviada para /app/chat, o método é chamado processMessage. Observe que o prefixo do aplicativo configurado anteriormente será adicionado ao mapeamento /app.



Este método armazena a mensagem no MongoDB e, em seguida, chama o método convertAndSendToUserpara enviar a notificação ao destino.



O método de convertAndSendToUseradicionar um prefixo /usere recipientIdao endereço /queue/messages. O endereço final será semelhante a este:



/user/{recipientId}/queue/messages


Todos os assinantes deste endereço (no nosso caso, um) receberão a mensagem.



Gerando chatId



Para cada conversa entre dois usuários, criamos uma sala de chat e geramos uma única para identificá-la chatId.



A classe ChatRoom se parece com isto:



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


O valor chatIdé igual à concatenação senderId_recipientId. Para cada conversa, mantemos duas entidades com o mesmo chatId: uma entre o remetente e o destinatário e a outra entre o destinatário e o remetente, para que ambos os usuários recebam o mesmo chatId.



Cliente JavaScript



Nesta seção, criaremos um cliente JavaScript que enviará mensagens e receberá o servidor WebSocket / STOMP de lá.



Estaremos usando SockJS e Stomp.js para nos comunicarmos com o 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);
  };


O método connect()estabelece uma conexão para /ws, onde nosso servidor está esperando por conexões, e também define uma função de retorno de chamada onConnectedque será chamada em uma conexão bem-sucedida e onErrorchamada se ocorrer um erro durante a conexão com o servidor.



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

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


O método se onConnected()inscreve em um endereço específico e recebe todas as mensagens enviadas para lá.



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


No final do método, uma sendMessage()mensagem é enviada para o endereço /app/chatque está especificado em nosso controlador.



Conclusão



Neste artigo, cobrimos todos os pontos importantes da criação de um bate-papo usando Spring Boot e STOMP sobre WebSocket.



Também construímos um cliente JavaScript usando as bibliotecas SockJs e Stomp.js .



O código-fonte de amostra pode ser encontrado aqui .






Saiba mais sobre o curso.











All Articles