Протокол STOMP изначально ориентирован на передачу текстовых сообщений. Большинство примеров используют JSON, строки или текстовые команды. Однако современные приложения регулярно работают с бинарными форматами:
Библиотека STOMP.js
поддерживает передачу бинарных данных через свойство
binaryBody.
В STOMP.js существуют два основных варианта отправки содержимого:
client.publish({
destination: '/topic/messages',
body: 'Текстовое сообщение'
});
и бинарный вариант:
client.publish({
destination: '/topic/files',
binaryBody: binaryData
});
Ключевое различие:
| Свойство | Тип |
|---|---|
body |
строка |
binaryBody |
Uint8Array |
Для бинарной передачи используется исключительно
Uint8Array.
Uint8Array представляет массив беззнаковых 8-битных
целых чисел.
Пример создания:
const bytes = new Uint8Array([72, 101, 108, 108, 111]);
Каждый элемент массива — отдельный байт.
Такой формат идеально подходит для:
Базовый пример:
const data = new Uint8Array([1, 2, 3, 4, 5]);
client.publish({
destination: '/topic/binary',
binaryBody: data
});
После отправки STOMP.js формирует бинарный WebSocket-фрейм.
Для кодирования текста используется TextEncoder.
const encoder = new TextEncoder();
const binaryData = encoder.encode('Привет мир');
client.publish({
destination: '/topic/chat',
binaryBody: binaryData
});
encode() возвращает Uint8Array.
На стороне подписчика выполняется обратное преобразование через
TextDecoder.
client.subscribe('/topic/chat', (message) => {
const decoder = new TextDecoder();
const text = decoder.decode(message.binaryBody);
console.log(text);
});
При получении бинарного сообщения STOMP.js предоставляет:
message.binaryBody
Тип:
Uint8Array
Текстовое поле message.body в бинарных сообщениях обычно
не используется.
Иногда JSON передают не как строку, а как бинарный поток.
Пример:
const payload = {
id: 15,
name: 'Alex',
online: true
};
const encoder = new TextEncoder();
const binaryData = encoder.encode(
JSON.stringify(payload)
);
client.publish({
destination: '/topic/users',
binaryBody: binaryData
});
Получение:
client.subscribe('/topic/users', (message) => {
const decoder = new TextDecoder();
const json = decoder.decode(message.binaryBody);
const data = JSON.parse(json);
console.log(data);
});
<input type="file" id="fileInput">
const input = document.getElementById('fileInput');
input.addEventListener('change', async () => {
const file = input.files[0];
const arrayBuffer = await file.arrayBuffer();
const binaryData = new Uint8Array(arrayBuffer);
client.publish({
destination: '/topic/upload',
binaryBody: binaryData
});
});
Между этими структурами существует важное различие.
Содержит сырые бинарные данные.
Предоставляет интерфейс доступа к байтам.
Пример:
const buffer = new ArrayBuffer(8);
const bytes = new Uint8Array(buffer);
STOMP.js ожидает именно Uint8Array.
const response = await fetch('/image.png');
const buffer = await response.arrayBuffer();
client.publish({
destination: '/topic/images',
binaryBody: new Uint8Array(buffer)
});
const response = await fetch('/audio.mp3');
const buffer = await response.arrayBuffer();
client.publish({
destination: '/topic/audio',
binaryBody: new Uint8Array(buffer)
});
Многие высоконагруженные системы используют Protocol Buffers вместо JSON.
Пример:
const encodedMessage = Message.encode(payload).finish();
client.publish({
destination: '/topic/protobuf',
binaryBody: encodedMessage
});
Метод finish() возвращает Uint8Array.
При бинарной отправке желательно явно задавать MIME-тип.
Пример:
client.publish({
destination: '/topic/files',
binaryBody: fileData,
headers: {
'content-type': 'application/octet-stream'
}
});
| Тип данных | MIME |
|---|---|
| Бинарный поток | application/octet-stream |
| JSON | application/json |
| PNG | image/png |
| JPEG | image/jpeg |
| MP3 | audio/mpeg |
| application/pdf | |
| ZIP | application/zip |
client.publish({
destination: '/topic/images',
binaryBody: imageBytes,
headers: {
'content-type': 'image/png'
}
});
client.publish({
destination: '/topic/docs',
binaryBody: pdfBytes,
headers: {
'content-type': 'application/pdf'
}
});
Для бинарных сообщений заголовок content-length особенно
важен.
Он позволяет:
STOMP.js обычно рассчитывает его автоматически.
client.publish({
destination: '/topic/data',
binaryBody: binaryData,
headers: {
'content-length': binaryData.length
}
});
Иногда брокеры работают нестандартно.
client.publish({
destination: '/topic/data',
binaryBody: binaryData,
skipContentLengthHeader: true
});
STOMP не является идеальным протоколом для очень крупных файлов.
Проблемы:
Многие брокеры ограничивают размер сообщений.
Например:
При превышении лимита сообщение может:
Крупные файлы часто разбиваются на части.
function splitBytes(bytes, chunkSize) {
const chunks = [];
for (let i = 0; i < bytes.length; i += chunkSize) {
chunks.push(
bytes.slice(i, i + chunkSize)
);
}
return chunks;
}
const chunks = splitBytes(fileBytes, 64 * 1024);
chunks.forEach((chunk, index) => {
client.publish({
destination: '/topic/chunks',
binaryBody: chunk,
headers: {
chunkIndex: index,
totalChunks: chunks.length
}
});
});
const receivedChunks = [];
client.subscribe('/topic/chunks', (message) => {
const index = Number(
message.headers.chunkIndex
);
receivedChunks[index] = message.binaryBody;
});
function mergeChunks(chunks) {
const totalLength = chunks.reduce(
(sum, chunk) => sum + chunk.length,
0
);
const result = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result;
}
Иногда бинарные данные кодируют в Base64.
const base64 = btoa(binaryString);
client.publish({
destination: '/topic/base64',
body: base64
});
Передача через binaryBody:
client.subscribe('/topic/data', (message) => {
const contentType =
message.headers['content-type'];
console.log(contentType);
});
client.subscribe('/topic/data', (message) => {
const type =
message.headers['content-type'];
if (type === 'application/json') {
const text = new TextDecoder()
.decode(message.binaryBody);
console.log(JSON.parse(text));
}
});
В браузерах часто используется Blob.
Преобразование:
const blob = new Blob([binaryData]);
const buffer = await blob.arrayBuffer();
const bytes = new Uint8Array(buffer);
client.subscribe('/topic/files', (message) => {
const blob = new Blob([
message.binaryBody
]);
console.log(blob);
});
client.subscribe('/topic/download', (message) => {
const blob = new Blob([
message.binaryBody
]);
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'file.bin';
link.click();
});
console.log(binaryData.length);
Размер измеряется в байтах.
Перед отправкой возможно использование:
Пример заголовка:
headers: {
'content-encoding': 'gzip'
}
Передача крупных данных может влиять на heartbeat-механизм STOMP.
Возможные проблемы:
Иногда требуется увеличение:
heartbeatIncoming
heartbeatOutgoing
Для анализа удобно выводить массив байтов:
console.log(message.binaryBody);
или:
console.log(
Array.from(message.binaryBody)
);
const hex = Array.from(binaryData)
.map(byte => byte.toString(16))
.join(' ');
console.log(hex);
Распространённый способ — вычисление хеша.
Например:
Отправитель:
headers: {
checksum: hash
}
Получатель:
if (receivedHash !== calculatedHash) {
console.error('Файл повреждён');
}
Неправильно:
client.publish({
destination: '/topic/data',
body: binaryData
});
Правильно:
client.publish({
destination: '/topic/data',
binaryBody: binaryData
});
Неправильно:
binaryBody: arrayBuffer
Правильно:
binaryBody: new Uint8Array(arrayBuffer)
Без MIME-типа обработка на сервере может быть затруднена.
Огромные бинарные пакеты способны:
Бинарная передача через STOMP.js активно применяется: