. « 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
ChatMessage
bastante 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
@Configuration
y @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
/user
para enviar y recibir mensajes. Las direcciones prefijadas /app
son 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
@MessageMapping
para 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
convertAndSendToUser
para enviar la notificación al objetivo.
El método de
convertAndSendToUser
agregar un prefijo /user
y recipientId
la 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
chatId
es 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 onConnected
que se llamará después de una conexión exitosa y se onError
llamará 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/chat
que 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.