
. « 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 é
ChatMessage
bem 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
@Configuration
e @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
/user
para enviar e receber mensagens. Os endereços prefixados /app
sã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
@MessageMapping
para 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
convertAndSendToUser
para enviar a notificação ao destino.
O método de
convertAndSendToUser
adicionar um prefixo /user
e recipientId
ao 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 onConnected
que será chamada em uma conexão bem-sucedida e onError
chamada 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/chat
que 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.