Проблемы с кодировкой в приложениях на базе STOMP.js возникают при передаче текстовых сообщений между браузером, WebSocket-сервером и брокером сообщений. Наиболее распространённые симптомы:
STOMP.js работает поверх WebSocket и использует текстовые фреймы STOMP-протокола. Внутри этих фреймов данные передаются как строки или бинарные массивы. Любое несовпадение кодировок между клиентом и сервером приводит к повреждению содержимого.
Внутри STOMP-фрейма тело сообщения представляет собой строку:
SEND
destination:/topic/chat
content-type:text/plain
Привет мир
^@
По умолчанию STOMP.js предполагает использование UTF-8. Однако проблемы начинаются в нескольких случаях:
content-type;Наиболее распространённый случай — повреждение русского текста.
Вместо:
Привет
клиент получает:
Привет
Причина — UTF-8 интерпретируется как Latin-1.
Например, Java-сервер отправляет сообщение:
message.getBytes("CP1251")
а браузер ожидает UTF-8.
В результате STOMP.js получает повреждённую строку.
client.publish({
destination: '/topic/chat',
body: JSON.stringify(data),
headers: {
'content-type': 'application/json;charset=UTF-8'
}
});
mappingJackson2MessageConverter.setDefaultCharset(StandardCharsets.UTF_8);
ws.send(JSON.stringify(data));
Node.js по умолчанию использует UTF-8, поэтому ручная перекодировка обычно не требуется.
STOMP.js ориентируется на заголовки сообщения.
Если сервер отправляет:
content-type:text/plain
кодировка считается неопределённой.
Правильный вариант:
content-type:text/plain;charset=UTF-8
Для JSON:
content-type:application/json;charset=UTF-8
Если кодировка нарушена, JSON становится невалидным.
Пример:
{
"message":"Привет"
}
После неправильной декодировки:
{
"message":"Привет"
}
Иногда повреждаются кавычки и escape-последовательности, что приводит к:
JSON.parse(message.body)
ошибке:
Unexpected token
client.subscribe('/topic/messages', message => {
try {
const data = JSON.parse(message.body);
console.log(data);
} catch (e) {
console.error('Ошибка декодирования', e);
console.log(message.body);
}
});
STOMP.js поддерживает бинарные payload.
Ошибка возникает, когда бинарные данные пытаются читать как текст.
client.publish({
destination: '/topic/file',
body: binaryData
});
Если binaryData — ArrayBuffer, сервер может
интерпретировать его как строку.
client.publish({
destination: '/topic/file',
binaryBody: uint8Array,
headers: {
'content-type': 'application/octet-stream'
}
});
Для контроля кодировки применяются встроенные API браузера.
const encoder = new TextEncoder();
const bytes = encoder.encode('Привет');
const decoder = new TextDecoder('utf-8');
const text = decoder.decode(bytes);
const decoder = new TextDecoder('windows-1251');
Но браузерная поддержка зависит от платформы.
Emoji занимают несколько байтов в UTF-8.
Например:
?
может занимать 4 байта.
Если сервер режет payload по символам вместо байтов, сообщение повреждается.
Некоторые backend-приложения вычисляют:
body.length()
вместо:
body.getBytes(StandardCharsets.UTF_8).length
Для кириллицы и emoji значения различаются.
STOMP использует заголовок:
content-length
Если длина указана неверно, брокер неправильно считывает тело сообщения.
Текст:
Привет
Содержит:
Если сервер отправит:
content-length:6
фрейм будет обрезан.
Buffer.byteLength(body, 'utf8')
body.getBytes(StandardCharsets.UTF_8).length
SockJS иногда меняет формат передачи данных.
В старых конфигурациях возможны:
Исходный текст:
Привет
После промежуточной сериализации:
\u041f\u0440\u0438\u0432\u0435\u0442
После некорректного повторного декодирования:
\\u041f\\u0440...
Для диагностики удобно использовать DevTools браузера.
Раздел:
Network → WS → Frames
Позволяет увидеть:
Одна из самых опасных ошибок — путать:
const text = 'Привет';
text.length
Результат:
6
new TextEncoder().encode(text).length
Результат:
12
Иногда кодировка ломается при ручной сериализации.
body: data.toString()
Если data — объект:
[object Object]
body: JSON.stringify(data)
Иногда бинарные данные передаются как Base64.
btoa('Привет')
Вызывает:
InvalidCharacterError
Потому что btoa работает только с Latin-1.
const bytes = new TextEncoder().encode('Привет');
const binary = Array.from(bytes)
.map(b => String.fromCharCode(b))
.join('');
const base64 = btoa(binary);
console.log(message.headers);
Важно анализировать:
content-type
content-length
console.log(message.binaryBody);
const bytes = new Uint8Array(message.binaryBody);
console.log(bytes);
Разные брокеры по-разному работают с кодировкой.
Обычно корректно использует UTF-8, но проблемы возможны при:
Может автоматически конвертировать текстовые сообщения.
Иногда это ломает бинарные payload.
В старых версиях встречались проблемы с UTF-8 и content-length.
Иногда данные кодируются дважды.
Исходный текст:
Привет
После первого UTF-8:
Пр
После повторного UTF-8:
ßÃÂ...
Иногда payload проходит через:
encodeURIComponent()
а затем:
decodeURIComponent()
в неправильном порядке.
%D0%9F%D1%80...
может остаться нераскодированным.
SockJS может использовать:
Некоторые транспорты исторически хуже работали с Unicode.
Особенно это проявлялось в старых браузерах.
const client = new Client({
brokerURL: 'ws://localhost:15674/ws'
});
client.publish({
destination: '/topic/chat',
body: JSON.stringify(data),
headers: {
'content-type': 'application/json;charset=UTF-8'
}
});
client.subscribe('/topic/chat', message => {
const data = JSON.parse(message.body);
console.log(data);
});
Другие кодировки в WebSocket/STOMP создают большое количество несовместимостей.
charset=UTF-8
должен присутствовать в content-type.
Только байтовая длина.
Для файлов использовать:
binaryBody
Избегать:
Некоторые reverse proxy меняют заголовки и payload.
Особенно:
function isUtf8(bytes) {
try {
new TextDecoder('utf-8', { fatal: true }).decode(bytes);
return true;
} catch {
return false;
}
}
Иногда возможно восстановление.
function fixUtf8(str) {
return decodeURIComponent(escape(str));
}
Но метод считается устаревшим и работает не всегда.
Для высоконагруженных систем JSON иногда передают как UTF-8 байты.
const bytes = new TextEncoder()
.encode(JSON.stringify(data));
client.publish({
destination: '/topic/data',
binaryBody: bytes
});
client.subscribe('/topic/data', message => {
const text = new TextDecoder()
.decode(message.binaryBody);
const data = JSON.parse(text);
console.log(data);
});
Привет
Unexpected token
Прив�
��
Malformed UTF-8 data
const bytes = new Uint8Array(message.binaryBody);
console.log(
Array.from(bytes)
.map(v => v.toString(16))
);
Позволяет увидеть реальное содержимое UTF-8 пакета.
Старые браузеры:
могут иметь:
Наиболее опасны системы, где присутствуют:
Каждый промежуточный слой способен повредить UTF-8.
На практике наиболее стабильной считается схема: