STOMP-протокол основан на текстовых командах, которые передаются поверх транспортного уровня (обычно WebSocket). Каждая команда представляет собой отдельный фрейм с фиксированной структурой: строка команды, набор заголовков, пустая строка-разделитель и тело сообщения. В STOMP.js эти команды абстрагируются методами клиента, но понимание их внутреннего устройства критично для отладки, проектирования маршрутизации сообщений и настройки брокеров.
Основной набор команд делится на управляющие (инициализация соединения, завершение сессии), команды обмена сообщениями (отправка и подписка), а также команды транзакций и подтверждений доставки.
Команда CONNECT инициирует установление логического соединения с брокером сообщений. Несмотря на то что транспорт уже может быть установлен (WebSocket открыт), STOMP требует отдельного протокольного рукопожатия.
Фрейм CONNECT включает заголовки:
accept-version — список поддерживаемых версий
протоколаhost — виртуальный хост брокераlogin и passcode — учетные данные (если
включена аутентификация)heart-beat — настройка heartbeat-механизмаПример логической структуры:
CONNECT
accept-version:1.2
host:example.com
login:user
passcode:pass
\0
В STOMP.js это соответствует созданию клиента и вызову
client.activate(), после чего библиотека автоматически
формирует CONNECT-фрейм.
После успешной аутентификации брокер отвечает командой CONNECTED. Она завершает фазу рукопожатия и фиксирует параметры сессии.
Ключевые заголовки:
version — согласованная версия протоколаsession — идентификатор сессииserver — информация о сервере STOMPheart-beat — согласованный heartbeatФрейм не имеет тела или содержит минимальные данные.
В STOMP.js это событие отображается через callback подключения, где становится доступен активный клиент.
Команда SEND используется для публикации сообщения в брокер.
Структура:
destination — очередь или топикcontent-type,
priority)Пример:
SEND
destination:/queue/orders
content-type:application/json
{"id":42,"status":"created"}\0
В STOMP.js это соответствует:
client.publish({
destination: "/queue/orders",
body: JSON.stringify({ id: 42, status: "created" })
});
Важно, что STOMP не определяет формат маршрутизации — это полностью зависит от брокера (RabbitMQ, ActiveMQ, Apollo и др.).
Команда SUBSCRIBE открывает канал получения сообщений из указанного destination.
Основные заголовки:
destination — адрес очереди или топикаid — уникальный идентификатор подпискиack — режим подтверждения (auto,
client, client-individual)Пример:
SUBSCRIBE
id:sub-001
destination:/topic/news
ack:auto
\0
В STOMP.js:
const subscription = client.subscribe("/topic/news", (message) => {
console.log(message.body);
});
Каждая подписка должна иметь уникальный id, особенно при
ручном управлении ACK.
Команда UNSUBSCRIBE завершает ранее созданную подписку.
Заголовки:
id — идентификатор подписки, полученный в
SUBSCRIBEUNSUBSCRIBE
id:sub-001
\0
В STOMP.js:
subscription.unsubscribe();
После выполнения брокер перестает отправлять сообщения в этот канал.
Команда MESSAGE используется брокером для передачи данных клиенту.
Структура включает:
destination — канал доставкиmessage-id — уникальный идентификатор сообщенияsubscription — id подпискиcontent-typeПример:
MESSAGE
subscription:sub-001
message-id:001-123
destination:/topic/news
content-type:text/plain
Hello world\0
В STOMP.js это представлено объектом Message, где
доступны:
bodyheadersack()nack()При режиме client или client-individual
клиент обязан подтверждать обработку сообщений.
Команда ACK подтверждает успешную обработку:
ACK
id:message-id-123
\0
В STOMP.js:
message.ack();
Команда NACK сообщает о неуспешной обработке, позволяя брокеру переотправить сообщение:
NACK
id:message-id-123
\0
В STOMP.js:
message.nack();
Режим подтверждения влияет на гарантию доставки: auto не
требует ACK, client требует явного подтверждения,
client-individual подтверждает каждое сообщение
отдельно.
STOMP поддерживает транзакции для группировки операций SEND и ACK.
Инициализация транзакции:
BEGIN
transaction:tx-001
\0
Подтверждение всех операций внутри транзакции:
COMMIT
transaction:tx-001
\0
Отмена всех операций:
ABORT
transaction:tx-001
\0
В STOMP.js:
const tx = client.begin();
client.publish({
destination: "/queue/orders",
body: "data",
transaction: tx
});
tx.commit();
или
tx.abort();
Транзакции особенно важны при атомарной обработке нескольких сообщений или групповой отправке событий.
Команда DISCONNECT корректно закрывает STOMP-сессию.
DISCONNECT
receipt:77
\0
Опционально может содержать receipt, чтобы подтвердить
завершение.
В STOMP.js:
client.deactivate();
После DISCONNECT брокер освобождает ресурсы, связанные с клиентом, включая подписки и транзакции.
Если клиент отправляет заголовок receipt, брокер обязан
вернуть команду RECEIPT.
Пример запроса:
DISCONNECT
receipt:close-1
\0
Ответ:
RECEIPT
receipt-id:close-1
Это механизм гарантии выполнения управляющих команд.
В STOMP.js можно обрабатывать через:
client.watchForReceipt("close-1", callback);
Команда ERROR отправляется брокером при нарушении протокола или логики запроса.
Структура:
message — краткое описание ошибкиcontent-typeПример:
ERROR
message:Malformed frame received
The message body is missing required headers
В STOMP.js ошибки доступны через обработчики:
client.onStompEr ror = (frame) => {
console.error(frame.headers["message"]);
};
Также могут возникать транспортные ошибки WebSocket, не связанные напрямую с STOMP-фреймами.
Хотя heartbeat не является отдельной командой, он тесно связан с CONNECT и CONNECTED. Он реализуется как периодическая отправка символов newline для проверки живости соединения.
Параметр heart-beat: cx,cy задает:
Несоответствие heartbeat приводит к закрытию соединения без явного DISCONNECT.
STOMP.js не работает с “сырыми” фреймами напрямую в большинстве сценариев. Вместо этого команды инкапсулируются в API:
client.publish() → SENDclient.subscribe() → SUBSCRIBEmessage.ack() → ACKclient.begin() → BEGINclient.deactivate() → DISCONNECTОднако библиотека сохраняет соответствие спецификации, что позволяет точно сопоставлять поведение с протокольным уровнем.
Особенность реализации — буферизация фреймов и асинхронная обработка. Команды могут отправляться до завершения CONNECT, но фактически уходят в сеть только после получения CONNECTED.
Типичный цикл взаимодействия выглядит как последовательность:
Каждая команда в этом цикле имеет строго определенную роль и влияет на состояние брокера и клиента, формируя управляемый поток событий поверх WebSocket-транспорта.