Тело сообщения

В STOMP-протоколе сообщение состоит из заголовков и тела. Если заголовки определяют метаданные доставки и обработки, то тело сообщения (message body) содержит полезную нагрузку, передаваемую между клиентом и брокером. В STOMP.js работа с телом сообщения напрямую связана с тем, как данные сериализуются, интерпретируются и декодируются на разных сторонах WebSocket-соединения.


Структура тела сообщения в STOMP

Тело сообщения в STOMP представляет собой непрозрачный байтовый блок, который следует сразу после заголовков и отделяется символом пустой строки. Протокол не накладывает строгих ограничений на формат данных внутри тела, оставляя это на усмотрение разработчика и серверной реализации.

Ключевая особенность:

  • тело сообщения не интерпретируется брокером;
  • содержимое передаётся как есть;
  • ответственность за сериализацию лежит на клиенте и сервере.

В STOMP.js тело сообщения обычно передаётся как строка или бинарный буфер.


Передача текстовых данных

Наиболее распространённый сценарий использования STOMP.js — передача текстовых сообщений. В этом случае тело сообщения представлено строкой, чаще всего в формате JSON.

Пример отправки:

client.publish({
  destination: "/app/chat",
  body: JSON.stringify({
    user: "alex",
    message: "Привет"
  })
});

В данном случае:

  • тело сообщения — строка JSON;
  • кодировка по умолчанию — UTF-8;
  • брокер не разбирает содержимое.

При получении:

client.subscribe("/topic/chat", (message) => {
  const payload = JSON.parse(message.body);
  console.log(payload.user, payload.message);
});

message.body всегда приходит как строка, если не используется бинарный режим.


Кодировка и сериализация

STOMP предполагает использование UTF-8 для текстовых сообщений. Это означает, что:

  • все строки должны быть корректно сериализованы в UTF-8;
  • специальные символы должны учитываться при сериализации;
  • несоответствие кодировки приводит к повреждению данных.

На практике STOMP.js скрывает детали кодировки, но разработчик должен учитывать их при работе с бинарными данными или межъязыковыми системами.


Content-Type и интерпретация тела

Хотя STOMP-протокол не навязывает строгую семантику content-type, многие брокеры и серверные реализации используют этот заголовок для определения способа обработки тела сообщения.

Пример:

client.publish({
  destination: "/app/data",
  body: JSON.stringify({ id: 1 }),
  headers: {
    "content-type": "application/json"
  }
});

Типичные значения:

  • text/plain — простой текст;
  • application/json — JSON-структуры;
  • application/octet-stream — бинарные данные.

Важно, что STOMP.js сам по себе не парсит тело на основе content-type, но сервер может использовать этот заголовок для десериализации.


Получение тела сообщения

При подписке на destination STOMP.js предоставляет объект сообщения, содержащий поле body.

client.subscribe("/topic/updates", (message) => {
  console.log(message.body);
});

Особенности:

  • body всегда строка (в текстовом режиме);
  • преобразование в объекты выполняется вручную;
  • заголовки доступны через message.headers.

Пример работы с заголовками и телом:

client.subscribe("/topic/events", (message) => {
  const type = message.headers["event-type"];
  const data = JSON.parse(message.body);

  if (type === "user_join") {
    console.log("Пользователь подключился:", data.name);
  }
});

Пустое тело сообщения

STOMP допускает сообщения без тела. В этом случае:

  • body может быть пустой строкой;
  • либо отсутствовать содержимое между заголовками и терминатором.

Пример:

client.publish({
  destination: "/app/ping",
  body: ""
});

Использование пустого тела характерно для:

  • ping/heartbeat сообщений;
  • команд управления;
  • событий без полезной нагрузки.

Бинарные данные в STOMP.js

STOMP.js поддерживает передачу бинарных данных, но поведение зависит от транспорта (WebSocket) и версии реализации.

При работе с бинарными payload используются Uint8Array или ArrayBuffer.

Пример отправки бинарных данных:

const bytes = new Uint8Array([1, 2, 3, 4]);

client.publish({
  destination: "/app/binary",
  binaryBody: bytes
});

При получении:

client.subscribe("/topic/binary", (message) => {
  const bytes = message.binaryBody;
  console.log(bytes);
});

Особенности бинарного режима:

  • body заменяется на binaryBody;
  • данные не преобразуются в строку;
  • требуется согласованная обработка на сервере.

Ограничения размера тела сообщения

Размер тела сообщения ограничивается не STOMP.js, а:

  • WebSocket-сервером;
  • брокером сообщений (RabbitMQ, ActiveMQ и др.);
  • настройками промежуточных прокси.

Типичные проблемы при больших payload:

  • разрыв соединения;
  • таймаут доставки;
  • фрагментация кадров WebSocket.

Практика показывает, что большие данные лучше:

  • разбивать на чанки;
  • передавать через внешнее хранилище (S3, CDN);
  • отправлять ссылки вместо контента.

Кодирование JSON как основной паттерн

Наиболее распространённый формат тела в STOMP.js — JSON. Это связано с тем, что:

  • JSON легко сериализуется;
  • поддерживается всеми языками;
  • удобно комбинируется с REST API и WebSocket.

Типичный паттерн:

const payload = {
  type: "message",
  timestamp: Date.now(),
  data: {
    text: "Hello"
  }
};

client.publish({
  destination: "/app/events",
  body: JSON.stringify(payload),
  headers: {
    "content-type": "application/json"
  }
});

На стороне клиента:

client.subscribe("/topic/events", (message) => {
  const event = JSON.parse(message.body);
  handleEvent(event);
});

Десериализация и контроль ошибок

Так как STOMP.js не выполняет автоматический парсинг, ошибки обработки тела сообщения ложатся на прикладной уровень.

Типичные проблемы:

  • невалидный JSON;
  • несовпадение схемы;
  • неожиданный тип данных;
  • повреждение при передаче.

Защищённая обработка:

client.subscribe("/topic/data", (message) => {
  try {
    const data = JSON.parse(message.body);
    process(data);
  } catch (e) {
    console.error("Ошибка парсинга сообщения", e);
  }
});

Комбинация тела и заголовков

Тело сообщения тесно связано с заголовками, которые определяют контекст интерпретации.

Примеры взаимодействия:

  • content-type определяет формат тела;
  • content-length может указывать размер;
  • кастомные заголовки задают тип события.

Пример:

client.publish({
  destination: "/app/upload",
  body: fileData,
  headers: {
    "content-type": "application/octet-stream",
    "file-name": "image.png",
    "content-length": fileData.length
  }
});

Потоковая передача данных через тело сообщения

STOMP не является потоковым протоколом в классическом смысле, но тело сообщения может использоваться для имитации потоков:

  • последовательная отправка чанков;
  • маркировка частей сообщения;
  • сборка на стороне получателя.

Пример:

client.publish({
  destination: "/app/chunk",
  body: JSON.stringify({
    id: "file1",
    index: 1,
    total: 10,
    chunk: "base64data..."
  })
});

На стороне обработки:

  • данные собираются по id;
  • упорядочиваются по index;
  • финализируются при достижении total.

Особенности взаимодействия с серверными реализациями

Поведение тела сообщения может отличаться в зависимости от брокера:

  • RabbitMQ STOMP plugin может ограничивать размер frame;
  • ActiveMQ поддерживает дополнительные заголовки;
  • Spring WebSocket часто автоматически десериализует JSON.

Несмотря на это, STOMP.js остаётся нейтральным слоем, который:

  • передаёт тело без изменений;
  • не интерпретирует содержимое;
  • не накладывает схем.