STOMP.js работает поверх WebSocket или SockJS и реализует текстовый протокол STOMP (Simple/Streaming Text Oriented Messaging Protocol). Несмотря на единый API клиента, поведение системы сильно зависит от брокера сообщений, поскольку каждый сервер реализует STOMP-слой с собственными расширениями, ограничениями и соглашениями.
Ключевая особенность конфигурации заключается в том, что клиент STOMP.js не является универсальным «plug-and-play» решением. Он требует точной настройки:
Разные брокеры по-разному трактуют STOMP-стандарт, поэтому одинаковый клиентский код может вести себя иначе.
Типовая конфигурация клиента выглядит следующим образом:
import { Client } from '@stomp/stompjs';
const client = new Client({
brokerURL: 'ws://localhost:15674/ws',
connectHeaders: {
login: 'user',
passcode: 'password',
},
debug: (str) => console.log(str),
reconnectDelay: 5000,
heartbeatIncoming: 10000,
heartbeatOutgoing: 10000,
});
Однако значения этих параметров меняются в зависимости от брокера.
ActiveMQ и Artemis часто используют разные WebSocket-коннекторы:
ActiveMQ Classic:
ws://host:61614/stompArtemis:
ws://host:61614/wsws://host:8161/activemq-websocketКлючевая особенность — наличие или отсутствие отдельного WebSocket transport.
ActiveMQ часто требует минимальный набор:
connectHeaders: {
login: 'admin',
passcode: 'admin'
}
Artemis более строг в случае включенной security-domain и может требовать дополнительные headers:
connectHeaders: {
login: 'user',
passcode: 'pass',
host: 'vhost1'
}
Поле host в STOMP frame становится критичным при
использовании виртуальных хостов.
ActiveMQ традиционно использует:
/queue/NAME/topic/NAMEArtemis допускает более гибкую настройку:
/queue//topic//address/ (в зависимости от конфигурации broker address
model)Это влияет на формирование destination в подписках:
client.subscribe('/queue/orders', callback);
или
client.subscribe('/topic/events', callback);
ActiveMQ часто плохо работает с агрессивными heartbeat значениями. Рекомендуемая настройка:
heartbeatIncoming: 20000,
heartbeatOutgoing: 20000
Artemis, наоборот, корректно обрабатывает короткие интервалы:
heartbeatIncoming: 5000,
heartbeatOutgoing: 5000
RabbitMQ реализует STOMP через отдельный plugin, что существенно влияет на поведение клиента.
Типичный endpoint:
ws://localhost:15674/wsВажно, что порт 15674 относится именно к STOMP-over-WebSocket плагину, а не к AMQP.
Ключевая особенность RabbitMQ — обязательная работа с vhost:
connectHeaders: {
login: 'user',
passcode: 'pass',
host: '/'
}
Если vhost не указан, соединение может быть установлено, но подписки не будут работать.
RabbitMQ не использует строгие /queue и
/topic как обязательные префиксы. Часто используется:
/queue/name/exchange/exchange-name/routing-key/amq/queue/nameПример подписки:
client.subscribe('/queue/task_queue', callback);
Или через exchange:
client.subscribe('/exchange/logs/info', callback);
RabbitMQ требует более внимательного управления подтверждениями сообщений.
Доступны режимы:
autoclientclient-individualПример:
client.subscribe('/queue/tasks', (message) => {
console.log(message.body);
message.ack();
}, { ack: 'client' });
Ошибка в ack-режиме может привести к бесконечной повторной доставке сообщений.
RabbitMQ STOMP plugin чувствителен к heartbeat и может разрывать соединение при несовпадении настроек между клиентом и сервером.
Рекомендуется синхронизация:
heartbeatOutgoing: 10000,
heartbeatIncoming: 10000
Apollo как STOMP-брокер имел достаточно «чистую» реализацию протокола, но с ограничениями на расширения.
brokerURL: 'ws://localhost:61623/stomp'
Apollo часто использовал нестандартные порты и требовал явного включения WebSocket acceptor.
Apollo строго разделял:
/queue//topic/и не поддерживал произвольные схемы destination без конфигурации broker.xml.
HornetQ использовал STOMP как вторичный протокол и имел ряд ограничений, которые унаследовал Artemis в ранних версиях.
Главный источник проблем:
/stomp/ws/ws/stompОшибка в URL полностью блокирует соединение, даже при корректных credentials.
Разные брокеры по-разному используют CONNECT headers:
| Брокер | login/passcode | host (vhost) | дополнительные поля |
|---|---|---|---|
| ActiveMQ | да | редко | нет |
| Artemis | да | да | иногда roles |
| RabbitMQ | да | обязательно | нет |
STOMP.js предоставляет единый интерфейс, но:
Частая проблема:
Решение всегда зависит от конкретного брокера, а не от STOMP.js.
Некоторые брокеры поддерживают durable subscriptions только при дополнительных header:
client.subscribe('/topic/news', callback, {
id: 'sub-1',
persistent: 'true'
});
Но такие расширения не являются частью стандарта STOMP и работают только на отдельных реализациях.
При работе с несколькими брокерами одновременно используется слой адаптации конфигурации:
function createStompConfig(broker) {
if (broker === 'rabbitmq') {
return {
brokerURL: 'ws://localhost:15674/ws',
connectHeaders: {
login: 'user',
passcode: 'pass',
host: '/'
},
heartbeatIncoming: 10000,
heartbeatOutgoing: 10000
};
}
if (broker === 'activemq') {
return {
brokerURL: 'ws://localhost:61614/stomp',
connectHeaders: {
login: 'admin',
passcode: 'admin'
},
heartbeatIncoming: 20000,
heartbeatOutgoing: 20000
};
}
if (broker === 'artemis') {
return {
brokerURL: 'ws://localhost:61614/ws',
connectHeaders: {
login: 'admin',
passcode: 'admin',
host: 'vhost1'
},
heartbeatIncoming: 5000,
heartbeatOutgoing: 5000
};
}
}
Одинаковое имя очереди может интерпретироваться по-разному:
/queue/testtest/amq/queue/testОсобенно критично для RabbitMQ: соединение устанавливается, но подписки не работают.
Приводит к:
Типичный сценарий:
STOMP.js в реальных системах рассматривается не как изолированный клиент, а как адаптер к конкретной реализации брокера. Конфигурация всегда включает три слоя:
Именно различия в этих слоях формируют поведение системы при работе с ActiveMQ, Artemis, RabbitMQ и другими реализациями STOMP.