Публикация сообщений — центральный механизм взаимодействия клиента с
брокером сообщений в протоколе STOMP. В библиотеке STOMP.js отправка
данных выполняется через метод publish(), который формирует
STOMP-фрейм SEND и передаёт его брокеру через активное
WebSocket-соединение.
Публикация используется для:
Базовая публикация сообщения выглядит следующим образом:
client.publish({
destination: '/topic/chat',
body: 'Привет'
});
После вызова метода библиотека:
SEND.Метод publish() принимает объект конфигурации.
Основные параметры:
| Параметр | Назначение |
|---|---|
destination |
Адрес канала |
body |
Тело сообщения |
headers |
STOMP-заголовки |
binaryBody |
Бинарные данные |
skipContentLengthHeader |
Отключение заголовка content-length |
Поле destination определяет адрес назначения
сообщения.
Примеры:
destination: '/topic/chat'
destination: '/queue/tasks'
destination: '/app/sendMessage'
Тип адреса зависит от брокера:
| Префикс | Назначение |
|---|---|
/topic/ |
Публикация для группы подписчиков |
/queue/ |
Очередь сообщений |
/app/ |
Серверный endpoint |
/exchange/ |
RabbitMQ exchange |
/user/ |
Персональные сообщения |
Наиболее распространённый вариант:
client.publish({
destination: '/topic/news',
body: 'Новое сообщение'
});
Тело автоматически отправляется как строка.
Чаще всего STOMP используется для обмена JSON-структурами.
const message = {
id: 15,
text: 'Привет',
author: 'Alex'
};
client.publish({
destination: '/topic/chat',
body: JSON.stringify(message)
});
На сервере данные обычно десериализуются обратно в объект.
При отправке JSON рекомендуется указывать MIME-тип.
client.publish({
destination: '/topic/chat',
body: JSON.stringify({
text: 'Сообщение'
}),
headers: {
'content-type': 'application/json'
}
});
Популярные типы:
| MIME | Назначение |
|---|---|
text/plain |
Обычный текст |
application/json |
JSON |
application/xml |
XML |
application/octet-stream |
Бинарные данные |
Публикация может содержать произвольные заголовки.
client.publish({
destination: '/topic/events',
body: 'event',
headers: {
priority: 'high',
type: 'notification'
}
});
Заголовки используются для:
Нередко клиент самостоятельно назначает идентификатор:
client.publish({
destination: '/topic/chat',
body: 'Сообщение',
headers: {
messageId: crypto.randomUUID()
}
});
Это полезно для:
client.publish({
destination: '/topic/logs',
body: JSON.stringify({
message: 'System started',
timestamp: Date.now()
})
});
В распределённых системах временные метки особенно важны для:
Отправка сообщений возможна только после успешного соединения.
Корректный вариант:
client.onConn ect = () => {
client.publish({
destination: '/topic/chat',
body: 'Подключение успешно'
});
};
client.activate();
Некорректный вариант:
client.activate();
client.publish({
destination: '/topic/chat',
body: 'Ошибка'
});
Во втором случае соединение может ещё не успеть установиться.
if (client.connected) {
client.publish({
destination: '/topic/chat',
body: 'Сообщение'
});
}
Свойство connected показывает текущее состояние
клиента.
button.addEventListener('click', () => {
client.publish({
destination: '/app/message',
body: JSON.stringify({
text: input.value
})
});
});
Такой подход используется в:
for (let i = 0; i < 100; i++) {
client.publish({
destination: '/queue/tasks',
body: JSON.stringify({
taskId: i
})
});
}
При высокой нагрузке необходимо учитывать:
STOMP поддерживает подтверждение обработки команды брокером.
client.publish({
destination: '/topic/chat',
body: 'Сообщение',
headers: {
receipt: 'msg-001'
}
});
Обработка receipt:
client.onRece ipt = (frame) => {
console.log('Получено подтверждение');
console.log(frame.headers['receipt-id']);
};
Receipt подтверждает:
Receipt не гарантирует доставку подписчику.
client.publish({
destination: '/queue/payments',
body: JSON.stringify({
amount: 100
}),
headers: {
receipt: 'payment-100'
}
});
Подобный механизм используется в:
STOMP.js поддерживает передачу бинарных данных через
binaryBody.
const bytes = new Uint8Array([1, 2, 3, 4]);
client.publish({
destination: '/topic/binary',
binaryBody: bytes,
headers: {
'content-type': 'application/octet-stream'
}
});
Поддерживаются:
Uint8Array;ArrayBuffer;Пример отправки файла:
fileInput.addEventListener('change', async (event) => {
const file = event.target.files[0];
const arrayBuffer = await file.arrayBuffer();
client.publish({
destination: '/topic/files',
binaryBody: new Uint8Array(arrayBuffer),
headers: {
filename: file.name,
'content-type': file.type
}
});
});
Брокеры обычно ограничивают максимальный размер пакета.
Типичные ограничения:
| Брокер | Ограничение |
|---|---|
| RabbitMQ | configurable |
| ActiveMQ | configurable |
| Apollo | configurable |
Слишком большие сообщения могут:
Вместо одного огромного сообщения часто используют разбиение на части.
client.publish({
destination: '/topic/upload',
body: JSON.stringify({
fileId: 'abc',
chunk: 1,
total: 10,
data: chunkData
})
});
По умолчанию STOMP.js добавляет заголовок
content-length.
Иногда его требуется отключить.
client.publish({
destination: '/topic/chat',
body: 'Hello',
skipContentLengthHeader: true
});
Это может понадобиться при несовместимости со старым брокером.
client.publish({
destination: '/topic/xml',
body: `
<message>
<text>Hello</text>
</message>
`,
headers: {
'content-type': 'application/xml'
}
});
Иногда токен передаётся в заголовках сообщения.
client.publish({
destination: '/app/private',
body: 'secret',
headers: {
Authorization: 'Bearer token'
}
});
Однако чаще авторизация выполняется на уровне CONNECT-фрейма.
При интеграции с Spring Framework часто используются endpoint’ы:
client.publish({
destination: '/app/chat.send',
body: JSON.stringify({
text: 'Hello'
})
});
Серверный обработчик:
@MessageMapping("/chat.send")
public void send(ChatMessage message) {
// обработка
}
При использовании RabbitMQ возможны специальные адреса:
client.publish({
destination: '/exchange/chat/messages',
body: 'Hello RabbitMQ'
});
Также поддерживаются:
/queue/
/topic/
/exchange/
/amq/queue/
Сам вызов publish() обычно не выбрасывает исключение
доставки.
Но возможны ошибки:
Пример проверки:
try {
if (!client.connected) {
throw new Error('Нет соединения');
}
client.publish({
destination: '/topic/chat',
body: 'Hello'
});
} catch (error) {
console.error(error);
}
Некоторые приложения временно сохраняют сообщения до подключения.
const pendingMessages = [];
function send(message) {
if (client.connected) {
client.publish({
destination: '/topic/chat',
body: message
});
} else {
pendingMessages.push(message);
}
}
После подключения:
client.onConn ect = () => {
pendingMessages.forEach(message => {
client.publish({
destination: '/topic/chat',
body: message
});
});
pendingMessages.length = 0;
};
При сетевых сбоях используется retry-механизм.
function publishWithRetry(message, retries = 3) {
try {
client.publish({
destination: '/topic/chat',
body: message
});
} catch (error) {
if (retries > 0) {
setTimeout(() => {
publishWithRetry(message, retries - 1);
}, 1000);
}
}
}
На производительность влияют:
Для оптимизации применяются:
const events = [];
for (let i = 0; i < 100; i++) {
events.push({
id: i
});
}
client.publish({
destination: '/topic/batch',
body: JSON.stringify(events)
});
Такой подход снижает:
Некоторые приложения публикуют собственные heartbeat-сообщения.
setInterval(() => {
client.publish({
destination: '/topic/heartbeat',
body: JSON.stringify({
timestamp: Date.now()
})
});
}, 5000);
STOMP часто используется как event bus.
client.publish({
destination: '/topic/events',
body: JSON.stringify({
type: 'USER_CREATED',
payload: {
id: 10
}
})
});
Стандартная структура событий обычно включает:
| Поле | Назначение |
|---|---|
type |
Тип события |
payload |
Данные |
timestamp |
Время |
version |
Версия схемы |
source |
Источник события |
При повторной доставке брокером важно избегать дублирования операций.
Для этого используются:
client.publish({
destination: '/queue/orders',
body: JSON.stringify({
operationId: crypto.randomUUID(),
amount: 500
})
});
Идемпотентность особенно важна для:
function publish(destination, body) {
console.log('SEND', destination, body);
client.publish({
destination,
body: JSON.stringify(body)
});
}
Логирование помогает:
В крупных проектах публикацию часто инкапсулируют.
class MessageBus {
constructor(client) {
this.client = client;
}
send(destination, payload) {
this.client.publish({
destination,
body: JSON.stringify(payload),
headers: {
'content-type': 'application/json'
}
});
}
}
Преимущества:
interface ChatMessage {
id: number;
text: string;
}
function publishChatMessage(message: ChatMessage) {
client.publish({
destination: '/topic/chat',
body: JSON.stringify(message)
});
}
Типизация уменьшает количество ошибок при сериализации и обработке событий.
client.publish({
destination: '/topic/chat',
body: 'Ошибка'
});
Неправильно:
body: {
text: 'Hello'
}
Правильно:
body: JSON.stringify({
text: 'Hello'
})
destination: 'chat'
Корректный вариант:
destination: '/topic/chat'
headers: {
'content-type': 'application/json'
}
В больших системах публикация сообщений обычно строится вокруг:
STOMP.js при этом выступает клиентским уровнем транспорта между браузером и брокером сообщений.