STOMP – это удивительно простая, но мощная протокол для отправки сообщений, реализованный популярными серверами, такими как RabbitMQ, ActiveMQ и Apollo. Utilizando STOMP через WebSocket, вы используете простой протокол, что делает его популярным выбором для отправки сообщений из веб-браузера, поскольку протоколы, такие как AMQP, ограничены браузерами, которые не допускают TCP-соединения.
Для использования STOMP через WebSocket вы можете использовать @stomp/stompjs, но он имеет сложные вызовыallback и сложную API, которая соответствует специфическим сценариям использования. К счастью, также существует менее известный @stompjs/rx-stomp, который предоставляет приятный интерфейс через наблюдаемые RxJS. Observables не специфичны для Angular, и они хорошо соответствуют тому, как работает React. Это замечательный интерфейс, когда вы составляете сложные рабочие процессы и потоки с множеством различных источников сообщений.
Руководство следует схожему пути, как и первая версия в Angular, но структура компонентов и стиль кода направлены на функциональный стиль React.
Примечание: Это руководство написано с использованием strict
TypeScript, но JavaScript-код практически идентичен, поскольку у нас только 5 деклараций типов. Чтобы JS-версия, вы можете пропустить импорты и определения типов.
Contents
Цели
В этом разделе мы создадим упрощенное приложение чата, которое показывает различные аспекты RxStomp в различных компонентах. Общая цель состоит в том, чтобы иметь:
-
React-формунд, соединенный с RxStomp и STOMP сервером.
-
Действующее отображение состояния соединения на основе нашего соединения с STOMP сервером.
-
Логика публикации/подписки на любой конфигурируемый топик.
-
Разделение логики RxStomp между множеством компонентов для показа того, как разделить логику и ответственность.
-
Согласование жизненных циклов подключения/подписки RxStomp с жизненными циклами компонентов React, чтобы избежать утечек или незакрытых наблюдателей.
Предварительные требования
-
У вас должен быть запущен сервер STOMP, чтобы приложение React могло к нему подключиться. Здесь мы будем использовать RabbitMQ с расширением
rabbitmq_web_stomp
. -
Последняя версия React. В этом учебнике будет использоваться версия v18, хотя, вероятно, подойдут и более старые версии.
-
Некоторое знакомство с observables также будет полезным.
Начальный сервер STOMP с RabbitMQ
Если вы тоже хотите использовать RabbitMQ (это не строго обязательно), вот руководства по установке для разных операционных систем. Чтобы добавить расширение, вам нужно будет запустить:
$ rabbitmq-plugins enable rabbitmq_web_stomp
Если вы можете использовать Docker
, Docker-файл, похожий на этот, настроит все необходимое для учебника:
FROM rabbitmq:3.8.8-alpine
run rabbitmq-plugins enable --offline rabbitmq_web_stomp
EXPOSE 15674
Демонстрационный React шаблон
Для этого учебника мы будем использовать шаблон Vite react-ts
. Сердцем нашего приложения будет компонент App
, и мы создадим дочерние компоненты для других специфических функций STOMP.
Как установить RxStomp
Мы будем использовать npm пакет @stomp/rx-stomp
:
$ npm i @stomp/rx-stomp rxjs
Это установит версию 2.0.0
Обратите внимание: Этот учебник работает и без явного указания rxjs
, так как он является сестрой зависимости, но хорошей практикой является явное указание.
Как управлять соединением и отключением с сервером STOMP
Теперь откройте App.tsx и инициализируйте наш клиент RxStomp
. Так как клиент не является состоянием, которое изменится для отображения, мы заключим его в кук 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
Предполагая, что используются стандартные порты и данные аутентификации, мы определим несколько настроек для нашей связи.
// 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() {
...
Чтобы улучшитьDev опыт, мы вывели все сообщения с временем戳 в локальный консоль и установили низкие частоты таймера. Ваша конфигурация для вашего производственного приложения должна быть совершенно другая, так что посмотрите на документацию RxStompConfig для всех доступных настроек.
Далее мы передадим конфигурацию в rxStomp
внутри useEffect
Hook. Это управляет активацией соединения вместе с циклом жизни компонента.
// src/App.tsx
...
function App() {
const rxStompRef = useRef(new RxStomp())
const rxStomp = rxStompRef.current
useEffect(() => {
rxStomp.configure(rxStompConfig)
rxStomp.activate()
return () => {
rxStomp.deactivate()
}
})
...
尽管在我们的应用程序中没有视觉变化,但在检查日志时应该会显示连接和心跳日志。以下是一个例子,展示了这应该是什么样子:
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
注意:通常,如果你看到重复的日志,这可能是未正确实现去激活或取消订阅功能的迹象。在开发环境中,React会渲染每个组件两次以帮助人们通过React.StrictMode
捕获这些错误
如何监控连接状态
RxStomp有一个RxStompState枚举,表示与我们的代理可能存在的连接状态。我们的下一个目标是将连接状态显示在UI中。
让我们为此创建一个新组件,称为Status.tsx
:
// src/Status.tsx
import { useState } from 'react'
export default function Status() {
const [connectionStatus, setConnectionStatus] = useState('')
return (
<>
<h2>Connection Status: {connectionStatus}</h2>
</>
)
}
我们可以使用rxStomp.connectionState$
可观察对象将我们的connectionStatus
字符串绑定。与使用useEffect
相似,我们将使用卸载操作来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>
</>
)
}
为了查看它,我们在应用程序中包含它:
// src/App.tsx
import Status from './Status'
...
return (
<>
<h1>Hello RxStomp!</h1>
<Status rxStomp={rxStomp}/>
</>
)
На этой стадии у вас должен быть работающий визуальный индикатор на экране. Попробуйте играться, отключив STOMP-сервер и увидеть, работают ли логи как ожидалось.
Как отправлять сообщения
Давайте создадим простой чат-комнату, чтобы продемонстрировать упрощенный полный цикл отправки сообщений с помощью брокера.
Мы можем разместить функциональность в новом компоненте Chatroom
. Сначала мы можем создать компонент с пользовательским полем username
и message
, связанным с входными данными.
// 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'
/>
</>
)
}
Подключим это в наш App с переключателем для присоединения к чат-комнате:
// 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}/>
</>
)}
</>
)
Время отправлять сообщения на самом деле. STOMP лучший для отправки текстовых сообщений (binрные данные также возможны). Мы будем определять структуру данных, которую мы отправляем, в новом файле types:
// types.ts
interface ChatMessage {
userName: string,
message: string
}
Примечание: Если вы не используете TypeScript, можете пропустить добавление этого определения типа.
Далее мы используем JSON для сериализации сообщения и отправляем сообщения на наш STOMP-сервер с использованием .publish
с топиком назначения и нашей 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>
</>
)
}
Для проверки попробуйте нажать кнопку Отправить сообщение несколько раз и увидеть, работает ли сериализация хорошо.尽管在视觉上还看不出任何变化,控制台日志应该会显示它:
Date ... >>> SEND
destination:/topic/test
content-length:45
Sent {"userName":"user722","message":"1234567890"}
Как получать сообщения
Мы создадим новый компонент для отображения списка сообщений от всех пользователей. Пока мы будем использовать тот же тип, передавать название темы в качестве пропса и отображать все в виде списка. Все это будет помещено в новый компонент под названием 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>
</>
)
}
Пришло время собрать все вместе!
Аналогично управлению подпиской в компоненте Status
, мы настраиваем подписку при монтировании и отписываемся при размонтировании.
Используя pipe
и map
из RxJS, мы можем десериализовать наш JSON обратно в ChatMessage
. Модульный дизайн позволяет при необходимости настроить более сложный конвейер, используя операторы 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()
}
}, [])
...
На этом этапе интерфейс чата должен корректно отображать сообщения, и вы можете поэкспериментировать, открыв несколько вкладок от имени разных пользователей.
Еще один интересный эксперимент – выключить STOMP-сервер, отправить несколько сообщений и снова включить его. Сообщения должны локально поставиться в очередь и отправиться, как только сервер будет готов. Здорово!
Итоги
В этом уроке мы:
-
Установили
@stomp/rx-stomp
для удобной разработки. -
Настроили
RxStompConfig
для конфигурации нашего клиента с параметрами подключения, логированием отладки и настройками таймера. -
Используйте
rxStomp.activate
иrxStomp.deactivate
для управления основным жизненным циклом клиента. -
Отслеживайте состояние подписки с помощью observable
rxStomp.connectionState$
. -
Публикуйте сообщения с помощью
rxStomp.publish
с настраиваемыми пунктами назначения и текстом сообщений. -
Создайте observable для заданной темы с помощью
rxStomp.watch
. -
Используйте как журналы консоли, так и компоненты React, чтобы увидеть библиотеку в действии и проверить функциональность и отказоустойчивость.
Вы можете найти окончательный код на Gitlab: https://gitlab.com/harsh183/rxstomp-react-tutorial. Не стесняйтесь использовать его в качестве стартового шаблона и сообщать о любых возникающих проблемах.
Source:
https://www.freecodecamp.org/news/build-chat-app-with-stomp-and-react/