STOMP est un protocole extrêmement simple et puissant pour l’envoi de messages mis en œuvre par des serveurs populaires tels que RabbitMQ, ActiveMQ et Apollo. L’utilisation de STOMP sur WebSocket est un protocole direct et facile à utiliser, ce qui en fait une solution populaire pour l’envoi de messages depuis un navigateur Web, car des protocoles tels que AMQP sont limités par les navigateurs qui ne permettent pas les connexions TCP.
Pour utiliser STOMP sur WebSocket, vous pouvez utiliser @stomp/stompjs, mais il possède des callbacks très subtilement impliqués et une API compliquée conçue pour des cas d’utilisation plus spécialisés. Heureusement, il existe également le moins connu @stompjs/rx-stomp qui offre une interface agréable grâce à des observables de RxJS. Les observables ne sont pas exclusifs à Angular et s’intègrent assez bien à la manière dont React fonctionne. C’est une interface sympa lorsque vous composez des flux de travail et des pipelines complexes avec de nombreuses sources de messages différentes.
Le tutoriel suit une voie relativement similaire à la version initiale dans Angular, mais la structure du composant et le style du code sont axés sur le style fonctionnel de React.
Note: Ce tutoriel est écrit en TypeScript strict
, mais le code JavaScript est presque identique étant donné que nous n’avons que 5 déclarations de type. Pour la version JS, vous pouvez ignorer les importations et définitions de types.
Table des matières
Objectifs
Ici, nous construirons une application de salon de discussion simplifiée qui montre différents aspects de RxStomp dans différents composants. Globalement, nous voulons avoir :
-
Un frontend React connecté à RxStomp vers un serveur STOMP.
-
Un affichage en direct de l’état de connexion basé sur notre connexion au serveur STOMP.
-
Une logique Pub/Sub pour n’importe quel sujet configurable.
-
Séparer la logique RxStomp sur plusieurs composants pour montrer comment séparer la logique et la responsabilité.
-
Aligner les cycles de connexion/abonnement de RxStomp avec les cycles des composants React pour s’assurer qu’il n’y a pas de fuites ou de watchers non fermés.
Préalables
-
Vous devez avoir un serveur STOMP en cours d’execution afin que l’application React puisse se connecter à celui-ci. Ici, nous utiliserons RabbitMQ avec l’extension
rabbitmq_web_stomp
. -
Dernière version de React. Cet tutoriel utilisera la version 18, bien que les versions plus anciennes fonctionnent probablement également.
-
Une certaine familiarité avec les observables sera également utile.
Serveur STOMP de départ avec RabbitMQ
Si vous souhaitez également utiliser RabbitMQ (non strictement obligatoire), voici les guides d’installation pour différents systèmes d’exploitation. Pour ajouter l’extension, vous devez exécuter :
$ rabbitmq-plugins enable rabbitmq_web_stomp
Si vous pouvez utiliser Docker
, un fichier Docker similaire à ceci configure tout ce qu’il faut pour ce tutoriel :
FROM rabbitmq:3.8.8-alpine
run rabbitmq-plugins enable --offline rabbitmq_web_stomp
EXPOSE 15674
Modèle de démarrage React
Pour ce tutoriel, nous utiliserons le modèle Vite de react-ts
. La partie centrale de notre application se trouvera dans le composant App
, et nous créerez des composants enfants pour d’autres fonctionnalités spécifiques de STOMP.
Comment installer RxStomp
Nous utiliserons le package npm @stomp/rx-stomp
:
$ npm i @stomp/rx-stomp rxjs
Cela installera la version 2.0.0
Note : Ce tutoriel fonctionne encore sans spécifier explicitement rxjs
car c’est une dépendance sœur, mais il est recommandé de le faire explicitement.
Comment gérer la connexion et la déconnexion avec le serveur STOMP
Maintenant, ouvrons App.tsx et initialisons notre client RxStomp
. Comme le client n’est pas un état qui changera pour le rendu, nous l’enveloppons dans l’hook useRef
.
// src/App.tsx
import { useRef } from 'react'
import { RxStomp } from '@stomp/rx-stomp'
import './App.css'
function App() {
const rxStompRef = useRef(new RxStomp())
const rxStomp = rxStompRef.current
return (
<>
<h1>Hello RxStomp!</h1>
</>
)
}
export default App
En supposant les ports et détails d’authentification par défaut, nous définirons ensuite une configuration pour notre connexion.
// src/App.tsx
import { RxStomp } from '@stomp/rx-stomp'
import type { RxStompConfig } from '@stomp/rx-stomp'
...
const rxStompConfig: RxStompConfig = {
brokerURL: 'ws://localhost:15674/ws',
connectHeaders: {
login: 'guest',
passcode: 'guest',
},
debug: (msg) => {
console.log(new Date(), msg)
},
heartbeatIncoming: 0,
heartbeatOutgoing: 20000,
reconnectDelay: 200,
}
function App() {
...
Pour un meilleur expérience de développement, nous avons enregistré toutes les messages avec des horodatages locaux dans la console et mis les fréquences des déclencheurs basses. Votre configuration pourra être très différente pour votre application de production, donc consultez les documents de RxStompConfig pour toutes les options disponibles.
Next, we’ll pass the configuration to rxStomp
inside a useEffect
Hook. This manages the connection’s activation alongside the component lifecycle.
// src/App.tsx
...
function App() {
const rxStompRef = useRef(new RxStomp())
const rxStomp = rxStompRef.current
useEffect(() => {
rxStomp.configure(rxStompConfig)
rxStomp.activate()
return () => {
rxStomp.deactivate()
}
})
...
While there’s no visual change in our app, checking the logs should show connection and ping logs. Here’s an example of what that should look like:
Date ... >>> CONNECT
login:guest
passcode:guest
accept-version:1.2,1.1,1.0
heart-beat:20000,0
Date ... Received data
Date ... <<< CONNECTED
version:1.2
heart-beat:0,20000
session:session-EJqaGQijDXqlfc0eZomOqQ
server:RabbitMQ/4.0.2
content-length:0
Date ... connected to server RabbitMQ/4.0.2
Date ... send PING every 20000ms
Date ... <<< PONG
Date ... >>> PING
Note: Generally, if you see duplicate logs, it may be a sign that a deactivation or unsubscribe functionality wasn’t implemented correctly. React renders each component twice in a dev environment to help people catch these bugs via React.StrictMode
How to Monitor the Connection Status
RxStomp has a RxStompState enum that represents possible connection states with our broker. Our next goal is to display the connection status in our UI.
Let’s create a new component for this called Status.tsx
:
// src/Status.tsx
import { useState } from 'react'
export default function Status() {
const [connectionStatus, setConnectionStatus] = useState('')
return (
<>
<h2>Connection Status: {connectionStatus}</h2>
</>
)
}
We can use the rxStomp.connectionState$
observable to bind to our connectionStatus
string. Similar to how we used useEffect
, we’ll use the unmount action to unsubscribe()
.
// src/Status.tsx
import { RxStompState } from '@stomp/rx-stomp'
import { useEffect, useState } from 'react'
import type { RxStomp } from '@stomp/rx-stomp'
export default function Status(props: { rxStomp: RxStomp }) {
const [connectionStatus, setConnectionStatus] = useState('')
useEffect(() => {
const statusSubscription = props.rxStomp.connectionState$.subscribe((state) => {
setConnectionStatus(RxStompState[state])
})
return () => {
statusSubscription.unsubscribe()
}
}, [])
return (
<>
<h2>Connection Status: {connectionStatus}</h2>
</>
)
}
To view it, we include it in our app:
// src/App.tsx
import Status from './Status'
...
return (
<>
<h1>Hello RxStomp!</h1>
<Status rxStomp={rxStomp}/>
</>
)
A ce stade, vous devriez avoir un indicateur visuel fonctionnel sur l’écran. Essayez de jouer avec la désactivation du serveur STOMP et verifiez si les journaux fonctionnent comme prévu.
Comment Envoyer des Messages
Créons un simple salon de discussion pour montrer un flux de messages simplifié de bout en bout avec le broker.
Nous pouvons placer la fonctionnalité dans un nouveau composant Chatroom
. Premièrement, nous pouvons créer le composant avec un champ personnalisé username
et message
lié aux entrées.
// src/Chatroom.tsx
import { useState } from 'react'
import type { RxStomp } from '@stomp/rx-stomp'
export default function Chatroom(props: {rxStomp: RxStomp}) {
const [message, setMessage] = useState('')
const [userName, setUserName] = useState(`user${Math.floor(Math.random() * 1000)}`)
return (
<>
<h2>Chatroom</h2>
<label htmlFor='username'>Username: </label>
<input
type='text'
name='username'
value={userName}
onChange={(e) => setUserName(e.target.value)}
/>
<label htmlFor='message'>Message: </label>
<input
type='text'
value={message}
onChange={(e) => setMessage(e.target.value)}
name='message'
/>
</>
)
}
Inclutons cela dans notre App avec un bouton pour rejoindre le salon de discussion :
// src/App.tsx
import { useEffect, useState, useRef } from 'react'
import Chatroom from './Chatroom'
...
function App() {
const [joinedChatroom, setJoinedChatroom] = useState(false)
...
return (
<>
<h1>Hello RxStomp!</h1>
<Status rxStomp={rxStomp}/>
{!joinedChatroom && (
<button onClick={() => setJoinedChatroom(true)}>
Join chatroom!
</button>
)}
{joinedChatroom && (
<>
<button onClick={() => setJoinedChatroom(false)}>
Leave chatroom!
</button>
<Chatroom rxStomp={rxStomp}/>
</>
)}
</>
)
Il est temps d’envoyer des messages réellement. STOMP est idéal pour l’envoi de messages texte (le transfert de données binaires est également possible). Nous définirons la structure des données que nous envoyons dans un nouveau fichier types :
// types.ts
interface ChatMessage {
userName: string,
message: string
}
Note: Si vous n’utilisez pas TypeScript, vous pouvez sauter l’ajout de cette définition de type.
Ensuite, utilisons JSON pour sérialiser le message et envoyons des messages à notre serveur STOMP en utilisant .publish
avec un sujet de destination et notre corps JSON body
.
// src/Chatroom.tsx
import type { ChatMessage } from './types'
...
const CHATROOM_NAME = '/topic/test'
export default function Chatroom(props: {rxStomp: RxStomp}) {
...
function sendMessage(chatMessage: ChatMessage) {
const body = JSON.stringify({ ...chatMessage })
props.rxStomp.publish({ destination: CHATROOM_NAME, body })
console.log(`Sent ${body}`)
setMessage('')
}
return (
<>
<h2>Chatroom</h2>
<label htmlFor="username">Username: </label>
<input
type="text"
name="username"
value={userName}
onChange={(e) => setUserName(e.target.value)}
/>
<label htmlFor="message">Message: </label>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
name="message"
/>
<button onClick={() => sendMessage({userName, message})}>Send Message</button>
</>
)
}
Pour l’essayer, essayez de cliquer sur le bouton Envoyer un Message quelques fois et vérifiez si la sérialisation fonctionne bien. Malgré le fait que vous ne verrez pas d’évolutions visuelles pour l’instant, les journaux de console devraient le montrer :
Date ... >>> SEND
destination:/topic/test
content-length:45
Sent {"userName":"user722","message":"1234567890"}
Comment Recevoir des Messages
Nous créerons un nouveau composant pour afficher la liste des messages de tous les utilisateurs. Pour l’instant, nous utiliserons le même type, transmettons le nom du sujet en tant que prop et affichons tout comme une liste. Tout cela se place dans un nouveau composant appelé MessageList
.
// src/MessageDisplay.tsx
import { useEffect, useState } from 'react'
import type { RxStomp } from '@stomp/rx-stomp'
import type { ChatMessage } from './types'
export default function MessageDisplay(props: {rxStomp: RxStomp, topic: string}) {
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([
{userName: 'admin', message: `Welcome to ${props.topic} room!`}
])
return(
<>
<h2>Chat Messages</h2>
<ul>
{chatMessages.map((chatMessage, index) =>
<li key={index}>
<strong>{chatMessage.userName}</strong>: {chatMessage.message}
</li>
)}
</ul>
</>
)
}
C’est le moment de rassembler tout !
Pareil que pour gérer l’abonnement avec le composant Status
, nous établissons l’abonnement au montage et désabonnons à la désinstallation.
En utilisant RxJS pipe
et map
, nous pouvons désérialiser notre JSON en notre ChatMessage
. Le design modulaire permet de configurer une pipeline plus compliquée en utilisant les opérateurs RxJS.
// src/MessageDisplay.tsx
...
import { map } from 'rxjs'
export default function MessageDisplay(props: {rxStomp: RxStomp, topic: string}) {
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([
{userName: 'admin', message: `Welcome to ${props.topic} room!`}
])
useEffect(() => {
const subscription = props.rxStomp
.watch(props.topic)
.pipe(map((message) => JSON.parse(message.body)))
.subscribe((message) => setChatMessages((chatMessages) => [...chatMessages, message]))
return () => {
subscription.unsubscribe()
}
}, [])
...
A ce stade, la JUI du chat devrait afficher les messages correctement, et vous pouvez expérimenter avec l’ouverture de plusieurs onglets en tant que différents utilisateurs.
Une autre chose à essayer ici est d’éteindre le serveur STOMP, d’envoyer quelques messages, et de le rallumer. Les messages devraient être mis en file d’attente localement et être envoyés une fois que le serveur est prêt. Sympa !
Résumé
Dans ce tutoriel, nous avons :
-
Installé
@stomp/rx-stomp
pour une belle expérience de développement. -
Configuré
RxStompConfig
pour configurer notre client avec les détails de connexion, le logging du débuggeur et les réglages de délai. -
Utiliser
rxStomp.activate
etrxStomp.deactivate
pour gérer le cycle de vie principal du client. -
Surveiller l’état de l’abonnement à l’aide de l’observable
rxStomp.connectionState$
. -
Publier des messages à l’aide de
rxStomp.publish
avec des destinations et des corps de messages configurables. -
Créer un observable pour un sujet donné à l’aide de
rxStomp.watch
. -
Utiliser à la fois les journaux de console et les composants React pour observer la bibliothèque en action, et vérifier la fonctionnalité et la tolérance aux panneaux.
Vous pouvez trouver le code final sur Gitlab : https://gitlab.com/harsh183/rxstomp-react-tutorial. N’hésitez pas à l’utiliser comme modèle de départ également et rapportez tous les problèmes qui peuvent survenir.
Source:
https://www.freecodecamp.org/news/build-chat-app-with-stomp-and-react/