Подписка в STOMP.js управляется методом subscribe().
Помимо адреса назначения и callback-функции, метод принимает объект
параметров, позволяющий управлять поведением подписки на уровне
STOMP-протокола и брокера сообщений.
Базовая форма подписки:
client.subscribe(destination, callback, headers);
Пример:
client.subscribe('/topic/news', message => {
console.log(message.body);
}, {
id: 'news-subscription'
});
Третий аргумент содержит параметры подписки, которые преобразуются в
STOMP-заголовки кадра SUBSCRIBE.
Объект параметров представляет собой набор заголовков:
{
id: 'sub-1',
ack: 'client',
durable: 'true'
}
STOMP.js не ограничивает список доступных параметров. Библиотека передаёт их брокеру без дополнительной обработки.
Это позволяет:
idЗаголовок id задаёт уникальный идентификатор подписки
внутри STOMP-соединения.
Пример:
client.subscribe('/queue/tasks', handler, {
id: 'tasks-subscription'
});
Идентификатор используется:
Если id не указан, STOMP.js создаёт его
автоматически.
client.subscribe('/topic/chat', message => {
console.log(message.body);
});
Внутренне библиотека формирует уникальное значение:
sub-0
sub-1
sub-2
idРучное задание идентификатора особенно важно при:
Пример:
client.subscribe('/topic/orders', onOrderMessage, {
id: 'orders-stream'
});
Два одинаковых id внутри одного соединения приводят к
ошибкам.
Некоторые брокеры:
ERROR frame.Проблемный пример:
client.subscribe('/topic/a', callbackA, {
id: 'same-id'
});
client.subscribe('/topic/b', callbackB, {
id: 'same-id'
});
ackПараметр ack определяет механизм подтверждения доставки
сообщений.
Доступные режимы:
| Значение | Описание |
|---|---|
auto |
Автоматическое подтверждение |
client |
Ручное подтверждение |
client-individual |
Индивидуальное подтверждение |
autoВ режиме auto брокер считает сообщение доставленным
сразу после отправки клиенту.
client.subscribe('/queue/jobs', message => {
console.log(message.body);
}, {
ack: 'auto'
});
Особенности:
autoСхема обработки:
Если приложение аварийно завершится после получения сообщения, брокер уже удалит его из очереди.
clientРежим client требует ручного подтверждения.
client.subscribe('/queue/orders', message => {
processOrder(message.body);
message.ack();
}, {
ack: 'client'
});
Сообщение считается обработанным только после вызова:
message.ack();
В режиме client подтверждение может распространяться
сразу на несколько сообщений.
Например:
client.subscribe('/queue/events', message => {
console.log(message.body);
message.ack();
}, {
ack: 'client'
});
Если до текущего сообщения были неподтверждённые сообщения, брокер может подтвердить их одновременно.
Поведение зависит от реализации брокера.
clientРучное подтверждение позволяет:
clientОсновные проблемы:
client-individualВ этом режиме каждое сообщение подтверждается отдельно.
client.subscribe('/queue/payments', message => {
processPayment(message.body);
message.ack();
}, {
ack: 'client-individual'
});
Подтверждение одного сообщения не влияет на остальные.
clientСравнение режимов:
| Режим | Подтверждение |
|---|---|
client |
Может подтверждать группу сообщений |
client-individual |
Подтверждает только одно сообщение |
client-individualРежим особенно полезен:
message.nack()При ручных режимах подтверждения возможен отказ от обработки сообщения.
client.subscribe('/queue/tasks', message => {
try {
executeTask(message.body);
message.ack();
} catch (error) {
message.nack();
}
}, {
ack: 'client-individual'
});
nack() сообщает брокеру о неудачной обработке.
Возможные последствия:
Некоторые брокеры поддерживают долговременные подписки.
Пример:
client.subscribe('/topic/notifications', callback, {
id: 'notifications-sub',
durable: 'true'
});
Такая подписка сохраняется на стороне брокера даже после отключения клиента.
Для ActiveMQ часто используются дополнительные параметры:
client.subscribe('/topic/news', callback, {
id: 'news-subscription',
durable: 'true',
'activemq.subscriptionName': 'news-storage'
});
Брокер начинает сохранять сообщения для офлайн-клиента.
Некоторые брокеры поддерживают распределённые подписки между несколькими клиентами.
Пример для RabbitMQ:
client.subscribe('/exchange/events', callback, {
'x-queue-name': 'shared-workers'
});
Несколько клиентов могут обрабатывать сообщения из одной очереди.
Брокеры JMS-совместимого типа поддерживают message selectors.
Пример:
client.subscribe('/topic/orders', callback, {
selector: "priority = 'high'"
});
Подписчик получит только сообщения с нужным условием.
Допустимы логические выражения:
client.subscribe('/topic/orders', callback, {
selector: "priority = 'high' AND region = 'EU'"
});
Или:
client.subscribe('/topic/orders', callback, {
selector: "amount > 1000"
});
Некоторые брокеры используют нестандартные заголовки:
client.subscribe('/topic/logs', callback, {
browser: 'true'
});
Подобные параметры зависят исключительно от брокера сообщений.
STOMP.js не интерпретирует их содержимое.
Многие брокеры позволяют ограничивать количество сообщений в буфере клиента.
Пример:
client.subscribe('/queue/jobs', callback, {
'prefetch-count': '10'
});
Или:
client.subscribe('/queue/jobs', callback, {
'activemq.prefetchSize': '5'
});
Prefetch определяет:
Небольшие значения:
{
'prefetch-count': '1'
}
Преимущества:
Недостатки:
Высокие значения:
{
'prefetch-count': '1000'
}
Преимущества:
Недостатки:
Комплексный пример:
client.subscribe('/queue/orders', message => {
try {
processOrder(message.body);
message.ack();
} catch (e) {
message.nack();
}
}, {
id: 'orders-worker',
ack: 'client-individual',
durable: 'true',
selector: "priority = 'high'",
'prefetch-count': '5'
});
Параметры подписки часто собираются программно.
Пример:
const headers = {
ack: 'client-individual'
};
if (isDurable) {
headers.durable = 'true';
}
if (selector) {
headers.selector = selector;
}
client.subscribe(destination, callback, headers);
Распространённая практика — создание шаблонов подписок.
const defaultSubscriptionConfig = {
ack: 'client-individual',
durable: 'true'
};
client.subscribe('/queue/a', callbackA, {
...defaultSubscriptionConfig,
id: 'sub-a'
});
client.subscribe('/queue/b', callbackB, {
...defaultSubscriptionConfig,
id: 'sub-b'
});
Ошибки в заголовках часто приводят к труднообнаружимым проблемам.
Типичные ошибки:
{
ack: 'clients'
}
Неверное значение:
{
ack: 'client'
}
Поддержка параметров зависит от брокера:
| Брокер | Особенности |
|---|---|
| RabbitMQ | Очереди, prefetch, exchange |
| ActiveMQ | Durable, selectors |
| Artemis | JMS-заголовки |
| Apollo | Специфические subscription headers |
| HornetQ | Расширенные параметры ack |
STOMP.js позволяет анализировать передаваемые заголовки через debug-функцию.
client.debug = message => {
console.log(message);
};
Логирование покажет отправляемый SUBSCRIBE frame:
>>> SUBSCRIBE
id:orders-worker
ack:client-individual
destination:/queue/orders