Подключение к брокеру

Подключение клиента STOMP.js к брокеру сообщений является ключевым этапом организации двустороннего обмена данными в реальном времени. В основе модели лежит установление поверх WebSocket логического STOMP-соединения, через которое выполняется публикация сообщений, подписка на каналы и управление жизненным циклом сессии.

Базовая модель подключения

STOMP.js не реализует транспорт самостоятельно, а использует уже существующее WebSocket-соединение или его совместимые аналоги. Это означает, что первым шагом всегда создаётся транспортный слой, после чего поверх него инициализируется STOMP-клиент.

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

  1. Создание WebSocket-соединения
  2. Оборачивание соединения в STOMP-клиент
  3. Выполнение CONNECT-команды к брокеру
  4. Получение подтверждения соединения
  5. Переход в состояние активной сессии

WebSocket выступает лишь каналом передачи байтовых фреймов, тогда как STOMP определяет структуру сообщений, команды и семантику взаимодействия.


Создание клиента STOMP.js

Современные версии STOMP.js используют фабричный подход через функцию Stomp.over() или прямое создание клиента через Client.

Пример инициализации:

import { Client } from '@stomp/stompjs';

const client = new Client({
  brokerURL: 'ws://localhost:8080/ws',
  reconnectDelay: 5000,
  heartbeatIncoming: 4000,
  heartbeatOutgoing: 4000,
});

В данном случае:

  • brokerURL определяет WebSocket-адрес брокера
  • reconnectDelay задаёт автоматическое переподключение при разрыве соединения
  • heartbeatIncoming и heartbeatOutgoing активируют механизм проверки живости соединения

Альтернативный вариант с явным WebSocket:

import { Client } from '@stomp/stompjs';

const socket = new WebSocket('ws://localhost:8080/ws');

const client = new Client({
  webSocketFactory: () => socket,
});

Использование webSocketFactory особенно важно в случаях, когда требуется полный контроль над транспортом: добавление заголовков, прокси-логика, кастомные реализации WebSocket.


Состояния клиента и жизненный цикл

STOMP-клиент проходит несколько стадий жизненного цикла, каждая из которых определяет допустимые операции.

Основные состояния:

  • DISCONNECTED — соединение отсутствует
  • CONNECTING — выполняется процесс установки соединения
  • CONNECTED — активная сессия с брокером
  • DISCONNECTING — выполняется завершение сессии

Переходы между состояниями управляются автоматически при использовании client.activate() и client.deactivate().

client.onConn ect = (frame) => {
  console.log('Соединение установлено');
};

client.onStompEr ror = (frame) => {
  console.error('Ошибка брокера:', frame.headers['message']);
};

client.activate();

Метод activate() инициирует подключение, после чего клиент начинает выполнять STOMP-рукопожатие с сервером.


STOMP handshake и команда CONNECT

После установления WebSocket-соединения STOMP-клиент отправляет команду CONNECT или STOMP (в зависимости от версии протокола).

Формат включает заголовки:

  • accept-version
  • host
  • login и passcode (если требуется аутентификация)
  • дополнительные пользовательские заголовки

Пример внутреннего представления:

CONNECT
accept-version:1.2
host:broker.example.com
login:user
passcode:password

\0

Брокер отвечает кадром CONNECTED, подтверждая успешную инициализацию сессии:

CONNECTED
version:1.2
session:abc123
heart-beat:4000,4000

\0

С этого момента соединение считается установленным, и клиент может подписываться на каналы и отправлять сообщения.


Настройка heartbeat

Механизм heartbeat используется для контроля живости соединения и предотвращения “тихих” разрывов.

В STOMP.js параметры задаются в миллисекундах:

const client = new Client({
  brokerURL: 'ws://localhost:8080/ws',
  heartbeatIncoming: 5000,
  heartbeatOutgoing: 5000,
});

Логика работы:

  • heartbeatOutgoing — клиент отправляет периодический сигнал брокеру
  • heartbeatIncoming — клиент ожидает сигнал от брокера

Если сигналы перестают поступать, соединение считается разорванным, и при включённом reconnectDelay запускается процесс переподключения.


Обработка успешного подключения

После успешного выполнения CONNECT-рукопожатия вызывается обработчик onConnect.

client.onConn ect = (frame) => {
  const sessionId = frame.headers['session'];
};

Объект frame содержит:

  • заголовки сессии
  • информацию о версии протокола
  • параметры heartbeat
  • идентификатор сессии

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


Ошибки подключения

Ошибки могут возникать на разных этапах:

  1. Ошибка WebSocket (сетевой уровень)
  2. Ошибка STOMP CONNECT
  3. Ошибка аутентификации
  4. Ошибка брокера при обработке команд

Для обработки используется:

client.onStompEr ror = (frame) => {
  const message = frame.headers['message'];
  const details = frame.body;
};

Также важно учитывать низкоуровневые ошибки транспорта:

client.onWebSocketEr ror = (event) => {
  console.error('WebSocket error');
};

Автоматическое переподключение

STOMP.js поддерживает механизм восстановления соединения при разрывах.

const client = new Client({
  brokerURL: 'ws://localhost:8080/ws',
  reconnectDelay: 3000,
});

Алгоритм работы:

  • фиксируется разрыв WebSocket
  • клиент переходит в состояние DISCONNECTED
  • запускается таймер переподключения
  • выполняется новая попытка CONNECT
  • при успехе восстанавливаются подписки

Важно учитывать, что подписки после переподключения не сохраняются автоматически в некоторых конфигурациях и должны быть восстановлены вручную через onConnect.


Заголовки подключения и кастомная аутентификация

STOMP позволяет передавать произвольные заголовки при подключении:

const client = new Client({
  brokerURL: 'ws://localhost:8080/ws',
  connectHeaders: {
    login: 'user',
    passcode: 'secret',
    deviceId: 'web-client-01'
  }
});

Такая схема часто используется для:

  • передачи JWT токенов
  • идентификации устройства
  • интеграции с backend-сессиями
  • проксирования авторизации через WebSocket слой

Подключение через SockJS

Во многих серверных реализациях WebSocket может быть недоступен напрямую, поэтому используется SockJS как транспорт-обёртка.

import SockJS from 'sockjs-client';
import { Client } from '@stomp/stompjs';

const client = new Client({
  webSocketFactory: () => new SockJS('http://localhost:8080/ws'),
});

SockJS автоматически выбирает доступный транспорт:

  • WebSocket
  • XHR-streaming
  • Long polling

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


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

Корректное завершение соединения выполняется через deactivate():

client.deactivate();

При вызове:

  • отправляется команда DISCONNECT
  • закрывается WebSocket
  • очищаются внутренние таймеры heartbeat
  • сбрасывается состояние клиента

Если требуется обработка завершения:

client.onDisconn ect = () => {
  console.log('Соединение закрыто');
};

Поведение при нестабильной сети

При нестабильных сетевых условиях STOMP.js может:

  • многократно инициировать переподключение
  • терять неподтверждённые сообщения
  • временно накапливать очереди команд
  • восстанавливать соединение без сохранения состояния подписок

По этой причине архитектурно важно рассматривать STOMP как транспорт доставки “best effort”, а не гарантированную очередь сообщений.


Особенности работы с несколькими соединениями

В некоторых сценариях создаются отдельные STOMP-клиенты для разных доменов сообщений:

  • отдельный канал уведомлений
  • отдельный поток чата
  • административный канал управления

Каждый клиент имеет собственный WebSocket и независимый жизненный цикл, что упрощает изоляцию логики, но увеличивает нагрузку на сеть и сервер брокера.