Установка заголовков

Заголовки (headers) в протоколе STOMP используются для передачи дополнительной служебной информации вместе с кадрами (FRAME). Они играют ключевую роль в аутентификации, маршрутизации, управлении сообщениями, настройке подписок и взаимодействии с брокером сообщений.

В библиотеке STOMP.js заголовки передаются практически во всех операциях:

  • подключение (CONNECT)
  • отправка сообщений (SEND)
  • подписка (SUBSCRIBE)
  • подтверждение сообщений (ACK)
  • транзакции (BEGIN, COMMIT, ABORT)
  • отключение (DISCONNECT)

Каждый заголовок представляет собой пару ключ: значение.

Пример STOMP-кадра:

SEND
destination:/queue/chat
content-type:application/json
priority:9

{"message":"Hello"}

Внутри STOMP.js заголовки обычно задаются в виде объекта JavaScript.


Общий формат установки заголовков

Базовая структура выглядит следующим образом:

{
    headerName: 'value',
    anotherHeader: 'anotherValue'
}

Пример:

client.publish({
    destination: '/queue/test',
    headers: {
        priority: '9',
        persistent: 'true'
    },
    body: 'Test message'
});

STOMP.js автоматически преобразует объект headers в STOMP-заголовки кадра.


Заголовки при подключении

Во время подключения заголовки используются для:

  • авторизации
  • передачи токенов
  • выбора виртуального хоста
  • настройки heartbeat
  • согласования версии протокола

Передача логина и пароля

const client = new Client({
    brokerURL: 'ws://localhost:15674/ws',
    connectHeaders: {
        login: 'admin',
        passcode: 'admin'
    }
});

В результате формируется кадр:

CONNECT
login:admin
passcode:admin

Заголовок host

Некоторые брокеры требуют указания виртуального хоста.

const client = new Client({
    brokerURL: 'ws://localhost:15674/ws',
    connectHeaders: {
        host: '/'
    }
});

Особенно часто это используется в:

  • RabbitMQ
  • ActiveMQ
  • Apollo

Передача JWT-токена

Современные приложения часто используют токены авторизации.

const client = new Client({
    brokerURL: 'ws://localhost:8080/ws',
    connectHeaders: {
        Authorization: 'Bearer eyJhbGciOiJIUzI1Ni...'
    }
});

На сервере заголовок может быть обработан middleware или interceptor-механизмом.


Пользовательские заголовки подключения

STOMP не ограничивает набор пользовательских заголовков.

connectHeaders: {
    clientId: 'frontend-app',
    appVersion: '2.5.1',
    region: 'kz'
}

Сервер может использовать их:

  • для логирования
  • маршрутизации
  • аудита
  • аналитики
  • идентификации клиента

Установка заголовков при отправке сообщения

Наиболее распространённое место использования заголовков — операция SEND.

Базовый пример

client.publish({
    destination: '/queue/chat',
    headers: {
        type: 'notification'
    },
    body: 'New message'
});

Заголовок content-type

Определяет тип содержимого сообщения.

JSON

client.publish({
    destination: '/queue/events',
    headers: {
        'content-type': 'application/json'
    },
    body: JSON.stringify({
        id: 15,
        status: 'created'
    })
});

Plain text

headers: {
    'content-type': 'text/plain'
}

XML

headers: {
    'content-type': 'application/xml'
}

Заголовок content-length

Некоторые брокеры автоматически вычисляют длину содержимого, однако иногда требуется установить её вручную.

const body = JSON.stringify(data);

client.publish({
    destination: '/queue/test',
    headers: {
        'content-length': body.length.toString()
    },
    body
});

Обычно STOMP.js сам управляет этим заголовком.

Ручная установка используется редко.


Заголовок persistent

Определяет, должно ли сообщение сохраняться брокером.

client.publish({
    destination: '/queue/orders',
    headers: {
        persistent: 'true'
    },
    body: 'Order created'
});

Если брокер поддерживает persistent-сообщения:

  • сообщение переживёт перезапуск брокера
  • данные будут записаны на диск
  • повышается надёжность доставки

Заголовок priority

Позволяет задавать приоритет сообщения.

client.publish({
    destination: '/queue/tasks',
    headers: {
        priority: '9'
    },
    body: 'Critical task'
});

Чаще всего диапазон:

0–9

Где:

  • 0 — минимальный приоритет
  • 9 — максимальный

Поддержка зависит от брокера.


Заголовок expires

Устанавливает время жизни сообщения.

client.publish({
    destination: '/queue/cache',
    headers: {
        expires: (Date.now() + 60000).toString()
    },
    body: 'Temporary data'
});

После истечения времени брокер может:

  • удалить сообщение
  • переместить его в dead-letter queue
  • проигнорировать

Заголовок receipt

Используется для подтверждения выполнения операции брокером.

Отправка сообщения с receipt

client.publish({
    destination: '/queue/test',
    headers: {
        receipt: 'msg-001'
    },
    body: 'Test'
});

Обработка RECEIPT

client.onRece ipt = (frame) => {
    console.log('Receipt received:', frame.headers['receipt-id']);
};

Брокер вернёт:

RECEIPT
receipt-id:msg-001

Заголовки подписки

Подписки (SUBSCRIBE) активно используют заголовки.


Заголовок id

Каждая подписка должна иметь уникальный идентификатор.

client.subscribe('/topic/news', callback, {
    id: 'news-subscription'
});

Без уникального id некоторые брокеры отклоняют подписку.


Заголовок ack

Управляет режимом подтверждения сообщений.

auto

ack: 'auto'

Сообщение считается подтверждённым автоматически.


client

ack: 'client'

Подтверждение выполняется вручную:

message.ack();

client-individual

ack: 'client-individual'

Каждое сообщение подтверждается отдельно.


Пользовательские заголовки подписки

client.subscribe('/topic/orders', callback, {
    region: 'eu',
    service: 'billing'
});

Сервер может фильтровать подписчиков по этим параметрам.


Заголовки подтверждения ACK

При ручном подтверждении сообщений STOMP.js формирует специальные заголовки автоматически.

Пример:

message.ack({
    transaction: 'tx-1'
});

STOMP.js добавит:

ACK
id:message-id
subscription:sub-0
transaction:tx-1

Заголовки NACK

Некоторые брокеры поддерживают отрицательное подтверждение.

message.nack();

Можно передавать дополнительные заголовки:

message.nack({
    requeue: 'true'
});

Заголовки транзакций

STOMP поддерживает транзакционный режим работы.


BEGIN

client.begin('tx-1');

Или:

client.begin('tx-1', {
    priority: '5'
});

COMMIT

client.commit('tx-1');

ABORT

client.abort('tx-1');

Автоматические заголовки STOMP.js

Часть заголовков библиотека генерирует самостоятельно.

Примеры:

accept-version
heart-beat
content-length
destination
subscription
message-id

Обычно переопределять их не требуется.


Доступ к заголовкам входящего сообщения

Все входящие заголовки доступны через объект message.headers.

client.subscribe('/queue/chat', (message) => {
    console.log(message.headers);
});

Получение конкретного заголовка

const type = message.headers.type;

Или:

const contentType = message.headers['content-type'];

Пример входящего сообщения

MESSAGE
destination:/queue/chat
content-type:application/json
message-id:007
priority:8

{"text":"Hello"}

Извлечение:

client.subscribe('/queue/chat', (message) => {
    console.log(message.headers['message-id']);
    console.log(message.headers.priority);
});

Регистрозависимость заголовков

STOMP-заголовки чувствительны к регистру.

Корректно:

'content-type'

Некорректно:

'Content-Type'

Некоторые брокеры могут игнорировать неправильный регистр.


Экранирование специальных символов

STOMP 1.1+ поддерживает экранирование:

Символ Экранирование
\n перевод строки
\c двоеточие
\\ обратный слэш

Пример:

custom-header:line1\nline2

STOMP.js обычно выполняет экранирование автоматически.


Перезапись заголовков

При совпадении имён побеждает последнее значение.

headers: {
    priority: '1',
    priority: '9'
}

Результат:

priority:9

Динамическое формирование заголовков

Часто заголовки создаются во время выполнения.

const headers = {
    Authorization: `Bearer ${token}`,
    requestId: crypto.randomUUID(),
    timestamp: Date.now().toString()
};

client.publish({
    destination: '/queue/api',
    headers,
    body: payload
});

Использование spread-оператора

const commonHeaders = {
    app: 'frontend',
    version: '1.0'
};

client.publish({
    destination: '/queue/test',
    headers: {
        ...commonHeaders,
        priority: '5'
    },
    body: 'Test'
});

Централизация заголовков

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

export function createHeaders(token) {
    return {
        Authorization: `Bearer ${token}`,
        client: 'web-app'
    };
}

Использование:

client.publish({
    destination: '/queue/orders',
    headers: createHeaders(token),
    body: JSON.stringify(order)
});

Проверка заголовков на сервере

На серверной стороне заголовки часто используются для:

  • проверки прав доступа
  • tenant-routing
  • correlation-id
  • trace-id
  • user-id
  • фильтрации сообщений
  • observability

Пример correlation-id:

headers: {
    'correlation-id': crypto.randomUUID()
}

Типичные ошибки при работе с заголовками

Использование неверного регистра

'Content-Type'

Вместо:

'content-type'

Передача числа вместо строки

Некоторые брокеры ожидают строковые значения.

Плохо:

priority: 5

Лучше:

priority: '5'

Отсутствие обязательного id подписки

client.subscribe('/topic/test', callback);

Некоторые брокеры могут отклонить такую подписку.


Конфликт пользовательских и системных заголовков

Опасно переопределять:

  • destination
  • content-length
  • subscription
  • message-id

Это может привести к ошибкам протокола.


Практический пример полного набора заголовков

const client = new Client({
    brokerURL: 'ws://localhost:15674/ws',
    connectHeaders: {
        login: 'admin',
        passcode: 'admin',
        Authorization: `Bearer ${token}`,
        clientId: 'frontend-app'
    }
});

client.onConn ect = () => {
    client.subscribe('/queue/orders', (message) => {
        console.log(message.body);

        message.ack({
            transaction: 'tx-order'
        });

    }, {
        id: 'orders-sub',
        ack: 'client'
    });

    client.publish({
        destination: '/queue/orders',
        headers: {
            'content-type': 'application/json',
            persistent: 'true',
            priority: '8',
            receipt: 'order-001',
            'correlation-id': crypto.randomUUID()
        },
        body: JSON.stringify({
            orderId: 15,
            amount: 250
        })
    });
};

client.activate();