Отслеживание состояния соединения

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

Состояние соединения в STOMP-клиенте обычно сводится к нескольким ключевым фазам:

  • инициализация клиента
  • ожидание подключения транспортного уровня
  • установление STOMP-сессии (CONNECT / CONNECTED)
  • активное соединение
  • потеря соединения (дисконнект или обрыв)
  • повторное подключение

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

События жизненного цикла клиента

В STOMP.js ключевым механизмом отслеживания состояния являются callback-и:

  • onConnect
  • onDisconnect
  • onWebSocketClose
  • onWebSocketError
  • onStompError

Каждое из этих событий отражает отдельный слой состояния.

onConnect как точка входа в активное состояние

Событие подключения фиксирует успешное завершение STOMP-рукопожатия. В этот момент клиент:

  • получает frame CONNECTED
  • может подписываться на топики
  • считается находящимся в активном состоянии

Именно здесь обычно устанавливается флаг уровня приложения, например:

let isConnected = false;

const client = new StompJs.Client({
  brokerURL: 'ws://localhost:15674/ws',
  onConnect: () => {
    isConnected = true;
  }
});

Важно учитывать, что это логическое состояние не синхронизировано автоматически с внутренними механизмами библиотеки.

Потеря соединения и деградация состояния

Потеря соединения может происходить на разных уровнях:

  • разрыв WebSocket-соединения
  • сетевой таймаут
  • серверный disconnect frame
  • ошибка протокола STOMP

Каждый сценарий требует отдельной реакции, поскольку STOMP.js может по-разному интерпретировать причину разрыва.

WebSocket close как базовый сигнал

Событие закрытия транспортного канала является самым надёжным индикатором проблем:

onWebSocketClose: (event) => {
  isConnected = false;
}

Однако оно не всегда означает завершение STOMP-сессии корректным образом, поэтому состояние приложения не должно полагаться только на него.

Разделение транспортного и протокольного состояния

Архитектурно важно разделять два уровня:

Транспортный уровень

  • WebSocket открыт / закрыт
  • наличие сетевого канала

Протокольный уровень

  • STOMP CONNECTED получен
  • активны подписки
  • доступны маршруты обмена сообщениями

Это разделение критично, поскольку возможны промежуточные состояния:

  • WebSocket открыт, STOMP не подключён
  • STOMP подключён, но подписки ещё не восстановлены
  • WebSocket переподключился, но STOMP-сессия требует повторной инициализации

Реализация конечного автомата состояний

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

  • DISCONNECTED
  • CONNECTING
  • CONNECTED
  • RECONNECTING
  • FAILED

Каждый переход должен быть детерминированным и сопровождаться явным обновлением состояния:

const STATE = {
  DISCONNECTED: 'DISCONNECTED',
  CONNECTING: 'CONNECTING',
  CONNECTED: 'CONNECTED',
  RECONNECTING: 'RECONNECTING',
  FAILED: 'FAILED'
};

let connectionState = STATE.DISCONNECTED;

Изменение состояния синхронизируется с событиями клиента:

  • CONNECT → CONNECTED
  • CLOSE → RECONNECTING или DISCONNECTED
  • ERROR → FAILED или RECONNECTING

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

STOMP.js поддерживает автоматическое переподключение через параметры клиента:

  • reconnectDelay
  • maxReconnectDelay
  • exponentialBackoff

Механизм переподключения создаёт дополнительные состояния гонки, когда:

  • соединение уже восстановлено, но старые подписки ещё активны
  • события приходят в неправильном порядке
  • callback-и выполняются параллельно

Типичная проблема — дублирование подписок после восстановления соединения.

Защита от гонок состояния

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

  • versioning соединения (connectionId)
  • флаг текущей сессии
  • очистка подписок при reconnect

Пример концепции идентификатора сессии:

let sessionId = 0;

const client = new StompJs.Client({
  onConnect: () => {
    sessionId++;
    const current = sessionId;
  }
});

Любые асинхронные операции проверяют актуальность sessionId перед выполнением.

Heartbeat как индикатор живого соединения

Heartbeat механизм используется для определения «живости» соединения:

  • клиент отправляет heartbeat
  • сервер отвечает подтверждением активности

Если heartbeat перестаёт приходить, соединение считается деградировавшим даже при открытом WebSocket.

Это особенно важно в сетях с NAT и прокси, где соединение может «зависать» без явного close-события.

Синхронизация подписок с состоянием соединения

Подписки в STOMP.js не являются устойчивыми к разрыву соединения. После реконнекта требуется:

  • восстановление всех subscription
  • повторная регистрация handlers
  • очистка устаревших подписок

Типовой подход:

  • хранение списка активных подписок
  • восстановление в onConnect
  • очистка в onDisconnect

Обработка ошибок STOMP-протокола

onStompError сигнализирует о проблемах уровня протокола:

  • неверный frame
  • отказ в авторизации
  • ошибки маршрутизации на брокере

Такие ошибки не всегда приводят к разрыву WebSocket, но логически завершают STOMP-сессию.

Состояние в условиях нестабильной сети

При нестабильной сети возможны следующие сценарии:

  • временная потеря пакетов без разрыва WebSocket
  • задержка heartbeat
  • ложные reconnection циклы

Для таких случаев применяется задержка реакции на события:

  • debounce обработки disconnect
  • подтверждение разрыва через таймер
  • отложенная смена состояния

Логирование состояния соединения

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

  • переходы состояний
  • причины disconnect
  • коды ошибок WebSocket
  • STOMP error frames
  • длительность сессий

Логирование должно быть привязано к идентификатору сессии для корреляции событий.

Поведение при скрытии вкладки браузера

При сворачивании вкладки или переходе в background режим:

  • heartbeat может замедляться
  • WebSocket может быть приостановлен браузером
  • соединение может быть ошибочно помечено как потерянное

Поэтому состояние соединения не должно напрямую зависеть от таймингов UI-слоя.

Итоговая модель состояния

Корректная модель управления соединением в STOMP.js строится на сочетании:

  • событий WebSocket
  • событий STOMP протокола
  • heartbeat механизма
  • локальной машины состояний
  • защиты от гонок и устаревших callback-ов

Такое разделение позволяет стабилизировать работу клиента даже в условиях нестабильной сети и частых переподключений.