. « Spring Framework» . , , : « Spring», .
Dans la création d'une notification évolutive de type Facebook à l'aide d'un événement envoyé par le serveur et de Redis, nous avons utilisé des événements envoyés par le serveur pour envoyer des messages du serveur au client. Il a également mentionné WebSocket - une technologie de communication bidirectionnelle entre un serveur et un client.
Dans cet article, nous examinerons l'un des cas d'utilisation courants de WebSocket. Nous écrirons une application de messagerie privée.
La vidéo ci-dessous montre ce que nous allons faire.
Introduction aux WebSockets et STOMP
WebSocket est un protocole de communication bidirectionnelle entre le serveur et le client.
WebSocket, contrairement à HTTP, le protocole de couche application, est un protocole de couche de transport (TCP). Bien que HTTP soit utilisé pour la connexion initiale, la connexion est alors "mise à niveau" vers la connexion TCP utilisée dans WebSocket.
WebSocket est un protocole de bas niveau qui ne définit pas les formats de message. Par conséquent, la RFC WebSocket définit des sous-protocoles qui décrivent la structure et les normes des messages. Nous utiliserons STOMP sur WebSockets (STOMP sur WebSockets).
Le protocole STOMP (le protocole Simple / Streaming the Text Oriented the Message Protocol) définit les règles d'échange de messages entre le serveur et le client.
STOMP est similaire à HTTP et s'exécute au-dessus de TCP à l'aide des commandes suivantes:
- RELIER
- SOUSCRIRE
- SE DÉSABONNER
- ENVOYER
- COMMENCER
- COMMETTRE
- ACK
La spécification et la liste complète des commandes STOMP peuvent être trouvées ici .
Architecture
- Le service d'authentification est responsable de l'authentification et de la gestion des utilisateurs. Nous ne réinventerons pas la roue ici et utiliserons le service d'authentification de JWT et d'authentification sociale à l'aide de Spring Boot .
- Le service de conversation est responsable de la configuration de WebSocket, de la gestion des messages STOMP et du stockage et du traitement des messages des utilisateurs.
- Le client de discussion est une application ReactJS qui utilise un client STOMP pour se connecter et s'abonner à une discussion. Voici également l'interface utilisateur.
Modèle de message
La première chose à laquelle il faut penser est le modèle de message. ChatMessage ressemble à ceci:
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 classe est
ChatMessage
assez simple, avec des champs nécessaires pour identifier l'expéditeur et le destinataire.
Il comporte également un champ d'état indiquant si le message a été remis au client.
public enum MessageStatus {
RECEIVED, DELIVERED
}
Lorsque le serveur reçoit un message d'une conversation, il n'envoie pas le message directement au destinataire, mais envoie une notification ( ChatNotification ) pour informer le client qu'un nouveau message a été reçu. Après cela, le client lui-même peut recevoir un nouveau message. Dès que le client reçoit le message, il est marqué comme DELIVERED.
La notification ressemble à ceci:
public class ChatNotification {
private String id;
private String senderId;
private String senderName;
}
La notification contient l'ID du nouveau message et des informations sur l'expéditeur afin que le client puisse afficher des informations sur le nouveau message ou le nombre de nouveaux messages, comme indiqué ci-dessous.
Configurer WebSocket et STOMP au printemps
La première étape consiste à configurer le point de terminaison STOMP et le courtier de messages.
Pour ce faire, nous créons une classe WebSocketConfig avec des annotations
@Configuration
et @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;
}
}
La première méthode configure un courtier de messages en mémoire simple avec une seule adresse préfixée
/user
pour envoyer et recevoir des messages. Les adresses préfixées /app
concernent les messages traités par des méthodes annotées @MessageMapping
, dont nous parlerons dans la section suivante.
La deuxième méthode enregistre le point de terminaison STOMP
/ws
. Ce point de terminaison sera utilisé par les clients pour se connecter au serveur STOMP. Cela inclut également un SockJS de secours qui sera utilisé si le WebSocket n'est pas disponible.
La dernière méthode configure le convertisseur JSON que Spring utilise pour convertir des messages vers / depuis JSON.
Contrôleur de traitement des messages
Dans cette section, nous allons créer un contrôleur qui gérera les demandes. Il recevra un message de l'utilisateur et l'enverra au destinataire.
@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()));
}
}
Avec l'aide de l'annotation,
@MessageMapping
nous configurons que lorsque le message est envoyé à la /app/chat
méthode est appelée processMessage
. Veuillez noter que le préfixe d'application précédemment configuré sera ajouté au mappage /app
.
Cette méthode stocke le message dans MongoDB, puis appelle la méthode
convertAndSendToUser
pour envoyer la notification à la cible.
La méthode d'
convertAndSendToUser
ajout d'un préfixe /user
et recipientId
à l'adresse /queue/messages
. L'adresse finale ressemblera à ceci:
/user/{recipientId}/queue/messages
Tous les abonnés de cette adresse (dans notre cas, un) recevront le message.
Génération de chatId
Pour chaque conversation entre deux utilisateurs, nous créons une salle de chat et en générons une unique pour l'identifier
chatId
.
La classe ChatRoom ressemble à ceci:
public class ChatRoom {
private String id;
private String chatId;
private String senderId;
private String recipientId;
}
La valeur
chatId
est égale à la concaténation senderId_recipientId
. Pour chaque conversation, nous gardons deux entités identiques chatId
: l'une entre l'expéditeur et le destinataire, et l'autre entre le destinataire et l'expéditeur, afin que les deux utilisateurs reçoivent la même chose chatId
.
Client JavaScript
Dans cette section, nous allons créer un client JavaScript qui enverra des messages et en recevra le serveur WebSocket / STOMP.
Nous utiliserons SockJS et Stomp.js pour communiquer avec le serveur en utilisant STOMP sur 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);
};
La méthode
connect()
établit une connexion à /ws
, où notre serveur attend les connexions, et définit également une fonction de rappel onConnected
qui sera appelée en cas de connexion réussie et onError
appelée si une erreur s'est produite lors de la connexion au serveur.
const onConnected = () => {
console.log("connected");
stompClient.subscribe(
"/user/" + currentUser.id + "/queue/messages",
onMessageReceived
);
};
La méthode
onConnected()
s'abonne à une adresse spécifique et reçoit tous les messages qui y sont envoyés.
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));
}
};
À la fin de la méthode, un
sendMessage()
message est envoyé à l'adresse /app/chat
qui est spécifiée dans notre contrôleur.
Conclusion
Dans cet article, nous avons couvert tous les points importants de la création d'une discussion en utilisant Spring Boot et STOMP sur WebSocket.
Nous avons également construit un client JavaScript en utilisant les bibliothèques SockJs et Stomp.js .
Un exemple de code source peut être trouvé ici .
En savoir plus sur le cours.