Фрейм SEND используется в протоколе STOMP для передачи
сообщений от клиента брокеру сообщений. Через него отправляются данные в
очереди, топики, пользовательские каналы, обработчики серверных
приложений и промежуточные маршрутизаторы.
В библиотеке STOMP.js фрейм SEND формируется
автоматически при вызове метода publish(). Несмотря на
автоматизацию, понимание внутренней структуры фрейма имеет
принципиальное значение при разработке высоконагруженных
realtime-приложений, систем уведомлений, чатов, брокеров событий и
интеграционных шлюзов.
STOMP-фрейм представляет собой текстовую структуру, состоящую из:
\0Базовый вид:
SEND
destination:/queue/messages
content-type:text/plain
Hello World^@
Символ ^@ условно обозначает NULL-терминатор.
В современных версиях STOMP.js отправка сообщения выполняется через
метод publish().
Простейший пример:
client.publish({
destination: '/queue/chat',
body: 'Привет'
});
Внутри библиотеки будет сформирован полноценный STOMP-фрейм:
SEND
destination:/queue/chat
content-length:12
Привет^@
Метод publish() принимает объект конфигурации.
Основные параметры:
client.publish({
destination: '/topic/news',
body: 'Новость',
headers: {
priority: '9'
},
binaryBody: binaryData,
skipContentLengthHeader: false
});
Заголовок destination является обязательным.
Он определяет конечную точку маршрутизации сообщения.
Пример:
client.publish({
destination: '/queue/orders',
body: 'Заказ создан'
});
Формируемый фрейм:
SEND
destination:/queue/orders
Заказ создан^@
Разные брокеры используют разные соглашения маршрутизации.
Наиболее распространённые варианты:
/queue/tasks
/topic/events
/exchange/logs
/app/messages
/user/queue/private
Пример:
client.publish({
destination: '/topic/system',
body: 'Сервер запущен'
});
Поле body содержит полезную нагрузку.
STOMP.js преобразует строку в текстовую часть фрейма.
Пример:
client.publish({
destination: '/queue/logs',
body: 'INFO: Application started'
});
Наиболее распространённый сценарий — отправка JSON.
const payload = {
id: 15,
status: 'created',
amount: 1200
};
client.publish({
destination: '/topic/orders',
body: JSON.stringify(payload)
});
Фрейм:
SEND
destination:/topic/orders
{"id":15,"status":"created","amount":1200}^@
При отправке структурированных данных желательно явно указывать MIME-тип.
client.publish({
destination: '/topic/orders',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
id: 10
})
});
Формируемый фрейм:
SEND
destination:/topic/orders
content-type:application/json
{"id":10}^@
STOMP.js может автоматически добавлять заголовок
content-length.
Пример:
client.publish({
destination: '/queue/test',
body: 'Hello'
});
Результат:
SEND
destination:/queue/test
content-length:5
Hello^@
Некоторые брокеры работают корректнее без
content-length.
Для этого используется параметр
skipContentLengthHeader.
client.publish({
destination: '/queue/test',
body: 'Hello',
skipContentLengthHeader: true
});
Фрейм:
SEND
destination:/queue/test
Hello^@
SEND-фрейм поддерживает произвольные заголовки.
client.publish({
destination: '/queue/payments',
headers: {
priority: '10',
type: 'invoice',
source: 'crm'
},
body: 'Оплата подтверждена'
});
Фрейм:
SEND
destination:/queue/payments
priority:10
type:invoice
source:crm
Оплата подтверждена^@
Дополнительные заголовки используются для:
Часто передаются correlation-id и request-id.
client.publish({
destination: '/queue/rpc',
headers: {
'correlation-id': 'req-1001',
'reply-to': '/queue/replies'
},
body: 'Запрос'
});
Некоторые брокеры поддерживают приоритеты.
client.publish({
destination: '/queue/tasks',
headers: {
priority: '9'
},
body: 'Критическая задача'
});
Заголовок receipt позволяет запросить подтверждение от
брокера.
client.publish({
destination: '/queue/orders',
headers: {
receipt: 'msg-001'
},
body: 'Создать заказ'
});
После обработки брокер вернёт фрейм RECEIPT.
STOMP.js поддерживает бинарные сообщения через
binaryBody.
Пример с Uint8Array:
const bytes = new Uint8Array([10, 20, 30, 40]);
client.publish({
destination: '/queue/binary',
binaryBody: bytes
});
body:
binaryBody:
Пример отправки файла:
const file = input.files[0];
const arrayBuffer = await file.arrayBuffer();
client.publish({
destination: '/queue/files',
binaryBody: new Uint8Array(arrayBuffer),
headers: {
filename: file.name
}
});
По умолчанию используется UTF-8.
Пример Unicode-сообщения:
client.publish({
destination: '/topic/chat',
body: 'Привет мир'
});
STOMP.js корректно рассчитывает размер UTF-8 строки при формировании
content-length.
В STOMP специальные символы внутри заголовков экранируются автоматически.
Экранируются:
\r
\n
:
\
Пример:
client.publish({
destination: '/queue/test',
headers: {
description: 'line1\nline2'
},
body: 'data'
});
Внутри библиотеки формирование фрейма происходит через сериализацию объекта команды.
Упрощённая схема:
const frame = [
'SEND',
'destination:/queue/test',
'',
'Hello'
].join('\n');
Реальная реализация дополнительно:
Процесс передачи включает:
После формирования фрейм передаётся через WebSocket-соединение.
webSocket.send(serializedFrame);
STOMP.js выступает надстройкой над WebSocket и реализует протокольный уровень обмена.
client.publish({
destination: '/topic/notifications',
headers: {
'content-type': 'application/json',
priority: '5',
receipt: 'notify-77'
},
body: JSON.stringify({
type: 'email',
userId: 15,
message: 'Подтверждение'
})
});
Результирующий фрейм:
SEND
destination:/topic/notifications
content-type:application/json
priority:5
receipt:notify-77
content-length:62
{"type":"email","userId":15,"message":"Подтверждение"}^@
client.publish({
body: 'Test'
});
Проблема:
client.publish({
body: 'text',
binaryBody: bytes
});
Подобная конструкция считается ошибочной архитектурой.
body: "{name:'test'}"
Ошибка:
headers: {
'content-type': 'text/plain'
}
при фактической передаче JSON приводит к:
В высоконагруженных системах важно:
receipt;Пример серии SEND-фреймов:
for (let i = 0; i < 1000; i++) {
client.publish({
destination: '/queue/bulk',
body: `Message ${i}`
});
}
При высокой нагрузке это создаёт:
Иногда эффективнее передавать массив объектов:
client.publish({
destination: '/queue/bulk',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify([
{ id: 1 },
{ id: 2 },
{ id: 3 }
])
});
Фрейм SEND может быть частью транзакции.
client.publish({
destination: '/queue/orders',
headers: {
transaction: 'tx-001'
},
body: 'Создание заказа'
});
Сообщение будет окончательно обработано только после
COMMIT.
Некоторые брокеры поддерживают долговременное хранение сообщений.
Пример:
client.publish({
destination: '/queue/tasks',
headers: {
persistent: 'true'
},
body: 'Важная задача'
});
Для анализа полезно включать debug-режим.
client.debug = (message) => {
console.log(message);
};
Это позволяет видеть:
STOMP-фреймы можно исследовать через:
При передаче данных необходимо учитывать:
Heartbeat не влияет напрямую на структуру SEND-фрейма, однако поддерживает стабильность транспортного соединения во время активного обмена сообщениями.
Разные брокеры по-разному интерпретируют SEND-фреймы.
Особенности могут касаться:
content-length;Формирование SEND может отличаться в версиях:
Наиболее заметные изменения:
Последовательность внутренних операций библиотеки:
const payload = {
event: 'payment.created',
paymentId: 501,
amount: 1200,
currency: 'USD',
createdAt: Date.now()
};
client.publish({
destination: '/topic/payments',
headers: {
'content-type': 'application/json',
priority: '8',
persistent: 'true',
receipt: 'payment-501',
source: 'billing-service'
},
body: JSON.stringify(payload)
});
Формируемый STOMP-фрейм:
SEND
destination:/topic/payments
content-type:application/json
priority:8
persistent:true
receipt:payment-501
source:billing-service
content-length:97
{"event":"payment.created","paymentId":501,"amount":1200,"currency":"USD","createdAt":1710000000}^@