Команды протокола

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 — подтверждение соединения

После успешной аутентификации брокер отвечает командой CONNECTED. Она завершает фазу рукопожатия и фиксирует параметры сессии.

Ключевые заголовки:

  • version — согласованная версия протокола
  • session — идентификатор сессии
  • server — информация о сервере STOMP
  • heart-beat — согласованный heartbeat

Фрейм не имеет тела или содержит минимальные данные.

В STOMP.js это событие отображается через callback подключения, где становится доступен активный клиент.


SEND — отправка сообщений

Команда 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 — подписка на поток сообщений

Команда 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 — отмена подписки

Команда UNSUBSCRIBE завершает ранее созданную подписку.

Заголовки:

  • id — идентификатор подписки, полученный в SUBSCRIBE
UNSUBSCRIBE
id:sub-001

\0

В STOMP.js:

subscription.unsubscribe();

После выполнения брокер перестает отправлять сообщения в этот канал.


MESSAGE — доставка сообщений от брокера

Команда 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, где доступны:

  • body
  • headers
  • ack()
  • nack()

ACK и NACK — подтверждение доставки

При режиме client или client-individual клиент обязан подтверждать обработку сообщений.

ACK

Команда ACK подтверждает успешную обработку:

ACK
id:message-id-123

\0

В STOMP.js:

message.ack();

NACK

Команда NACK сообщает о неуспешной обработке, позволяя брокеру переотправить сообщение:

NACK
id:message-id-123

\0

В STOMP.js:

message.nack();

Режим подтверждения влияет на гарантию доставки: auto не требует ACK, client требует явного подтверждения, client-individual подтверждает каждое сообщение отдельно.


BEGIN, COMMIT и ABORT — транзакции

STOMP поддерживает транзакции для группировки операций SEND и ACK.

BEGIN

Инициализация транзакции:

BEGIN
transaction:tx-001

\0

COMMIT

Подтверждение всех операций внутри транзакции:

COMMIT
transaction:tx-001

\0

ABORT

Отмена всех операций:

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 — завершение сессии

Команда DISCONNECT корректно закрывает STOMP-сессию.

DISCONNECT
receipt:77

\0

Опционально может содержать receipt, чтобы подтвердить завершение.

В STOMP.js:

client.deactivate();

После DISCONNECT брокер освобождает ресурсы, связанные с клиентом, включая подписки и транзакции.


RECEIPT — подтверждение выполнения команды

Если клиент отправляет заголовок receipt, брокер обязан вернуть команду RECEIPT.

Пример запроса:

DISCONNECT
receipt:close-1

\0

Ответ:

RECEIPT
receipt-id:close-1

Это механизм гарантии выполнения управляющих команд.

В STOMP.js можно обрабатывать через:

client.watchForReceipt("close-1", callback);

ERROR — обработка ошибок протокола

Команда 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 как часть управляющего обмена

Хотя heartbeat не является отдельной командой, он тесно связан с CONNECT и CONNECTED. Он реализуется как периодическая отправка символов newline для проверки живости соединения.

Параметр heart-beat: cx,cy задает:

  • cx — интервал отправки клиентом
  • cy — интервал ожидания от сервера

Несоответствие heartbeat приводит к закрытию соединения без явного DISCONNECT.


Особенности обработки команд в STOMP.js

STOMP.js не работает с “сырыми” фреймами напрямую в большинстве сценариев. Вместо этого команды инкапсулируются в API:

  • client.publish() → SEND
  • client.subscribe() → SUBSCRIBE
  • message.ack() → ACK
  • client.begin() → BEGIN
  • client.deactivate() → DISCONNECT

Однако библиотека сохраняет соответствие спецификации, что позволяет точно сопоставлять поведение с протокольным уровнем.

Особенность реализации — буферизация фреймов и асинхронная обработка. Команды могут отправляться до завершения CONNECT, но фактически уходят в сеть только после получения CONNECTED.


Взаимодействие команд в жизненном цикле сессии

Типичный цикл взаимодействия выглядит как последовательность:

  1. CONNECT — инициирование соединения
  2. CONNECTED — подтверждение
  3. SUBSCRIBE — регистрация интереса к каналам
  4. SEND — публикация сообщений
  5. MESSAGE — получение данных
  6. ACK/NACK — управление доставкой
  7. BEGIN/COMMIT — транзакционная обработка (опционально)
  8. UNSUBSCRIBE — завершение подписки
  9. DISCONNECT — завершение сессии

Каждая команда в этом цикле имеет строго определенную роль и влияет на состояние брокера и клиента, формируя управляемый поток событий поверх WebSocket-транспорта.