STOMP (Simple Text Oriented Messaging Protocol) поверх WebSocket представляет обмен данными в виде текстовых фреймов. Каждый фрейм — это атомарная единица коммуникации между клиентом и брокером сообщений. Вся логика протокола строится вокруг строго определённой структуры, в которой выделяются три ключевых компонента: команда, заголовки и тело сообщения.
Фрейм всегда интерпретируется последовательно: сначала читается строка команды, затем набор заголовков, затем тело до нулевого байта. Такая линейная модель делает протокол простым для парсинга и реализации в JavaScript.
Фрейм STOMP в текстовом представлении имеет следующий формат:
COMMAND
header1:value1
header2:value2
body^@
Где:
COMMAND — управляющая инструкция (например, CONNECT,
SEND, SUBSCRIBE)headers — набор пар ключ-значениеbody — полезная нагрузка сообщения^@ — нулевой символ (NULL, \x00),
обозначающий конец фреймаНесмотря на текстовую природу, протокол требует строгого соблюдения структуры, иначе брокер не сможет корректно разобрать сообщение.
Команда — первый элемент фрейма. Она определяет тип операции, которую необходимо выполнить.
На уровне STOMP.js команды можно разделить на несколько категорий:
CONNECT — установление соединенияSEND — отправка сообщения в destinationSUBSCRIBE — подписка на каналUNSUBSCRIBE — отмена подпискиDISCONNECT — закрытие соединенияCONNECTED — подтверждение соединенияMESSAGE — доставка сообщения подписчикуRECEIPT — подтверждение выполнения операцииERROR — сообщение об ошибкеКоманда всегда находится в первой строке и не содержит дополнительных символов или префиксов.
Заголовки представляют собой набор метаданных, влияющих на поведение доставки сообщений и обработки фрейма.
Формат заголовка:
key:value
Каждый заголовок занимает отдельную строку. Порядок заголовков не имеет семантического значения.
destination — маршрут сообщения (очередь или
топик)content-type — тип содержимого (например,
application/json)content-length — длина тела сообщенияid — идентификатор подпискиreceipt — запрос подтверждения выполнения операцииauthorization — токен авторизации (в некоторых
реализациях)destination:/topic/chat
content-type:application/json
Заголовки часто используются брокерами (RabbitMQ, ActiveMQ, Spring STOMP) для маршрутизации и контроля доставки.
Тело фрейма содержит полезную нагрузку. Это может быть:
В STOMP.js тело передаётся как строка, поэтому любые сложные структуры сериализуются вручную.
Пример JSON-данных:
{"user":"alex","message":"hello"}
Ключевой момент: STOMP не накладывает ограничений на формат тела, но вся ответственность за сериализацию и десериализацию лежит на прикладном уровне.
Каждый фрейм обязательно завершается нулевым символом:
\x00
Он служит маркером конца сообщения и позволяет парсеру точно определить границы фрейма даже при потоковой передаче данных через WebSocket.
Без этого символа невозможно корректно разделить последовательность фреймов в одном TCP-потоке.
SEND
destination:/topic/chat
content-type:application/json
{"user":"alex","message":"hello"}\x00
Этот фрейм означает отправку сообщения в топик
/topic/chat.
При установлении соединения используется специальный набор фреймов, определяющих параметры сессии.
CONNECT
accept-version:1.2
host:localhost
login:user
passcode:password
\x00
Заголовки:
accept-version — поддерживаемая версия протоколаhost — виртуальный хост брокераlogin и passcode — аутентификацияCONNECTED
version:1.2
session:session-id-123
\x00
Этот фрейм подтверждает успешное установление соединения и создание сессии.
Подписка на канал осуществляется через SUBSCRIBE:
SUBSCRIBE
id:sub-1
destination:/topic/news
\x00
Здесь:
id используется для управления подпискойdestination определяет канал сообщенийОтветные сообщения приходят в виде MESSAGE-фреймов.
Когда брокер доставляет данные подписчику, используется структура:
MESSAGE
subscription:sub-1
message-id:msg-001
destination:/topic/news
content-type:application/json
{"title":"update","body":"text"}\x00
Заголовки включают идентификаторы доставки, позволяющие отслеживать маршрут сообщения и подтверждать получение.
Отправка данных через STOMP.js реализуется через SEND-фреймы:
SEND
destination:/queue/task
content-type:text/plain
task payload\x00
Маршрутизация определяется заголовком destination.
Брокер интерпретирует его как очередь или topic в зависимости от
конфигурации.
В режиме ручного подтверждения доставки используются ACK-фреймы:
ACK
id:msg-001
\x00
И отрицательное подтверждение:
NACK
id:msg-001
\x00
Эти механизмы обеспечивают контроль доставки сообщений в надёжных системах.
При возникновении ошибок сервер отправляет:
ERROR
message:Invalid subscription
The subscription id was not found\x00
Особенности:
message содержит краткое описаниеSTOMP-фреймы передаются поверх WebSocket как текстовые строки. Важные нюансы:
\n)\x00) обязателенЛюбое отклонение от формата может привести к некорректному разбору фрейма на стороне брокера.
В STOMP.js фрейм обрабатывается как поток символов. Общая логика:
\n\x00Упрощённая модель объекта:
{
command: "MESSAGE",
headers: {
subscription: "sub-1",
destination: "/topic/news"
},
body: "{\"title\":\"update\"}"
}
Парсер должен корректно обрабатывать потоковый характер данных, где один WebSocket-пакет может содержать несколько фреймов или их части.
STOMP не гарантирует, что один WebSocket message
соответствует одному фрейму. Возможны три ситуации:
Поэтому STOMP.js использует буферизацию входящего потока и анализ на
наличие \x00.
Некоторые символы требуют специальной обработки:
\n — конец строки\r — может игнорироваться: — разделитель заголовков\x00 — конец фреймаВ теле сообщения допустим любой контент, но бинарные данные обычно сериализуются в Base64, поскольку STOMP изначально текстовый.
Фреймы являются базовым уровнем абстракции, на котором строится вся логика STOMP.js:
Каждый фрейм представляет независимое событие, что упрощает масштабирование и интеграцию с брокерами сообщений.
При интенсивной передаче сообщений структура фрейма остаётся неизменной, но увеличивается частота обработки:
Эффективность STOMP-системы напрямую зависит от оптимизации размера фреймов и частоты их отправки.