В STOMP.js работа поверх WebSocket подразумевает нестабильность соединения как нормальное состояние среды. Соединение может быть разорвано по множеству причин: сетевые сбои, перезапуск брокера сообщений, истечение сессии на сервере, таймауты heartbeat, переключение сети на клиенте (Wi-Fi → LTE), ограничения прокси или балансировщика нагрузки.
Для прикладной логики это означает необходимость построения устойчивой стратегии переподключения, которая учитывает не только повторное соединение, но и корректное восстановление подписок, состояния и очередей сообщений.
Любая стратегия переподключения в STOMP.js строится вокруг трёх событий:
onWebSocketClose)onStompError)deactivate)Минимальная реализация обычно опирается на повторный вызов
activate().
import { Client } from "@stomp/stompjs";
const client = new Client({
brokerURL: "ws://localhost:8080/ws",
reconnectDelay: 0,
debug: (msg) => console.log(msg),
});
client.onWebSocketCl ose = () => {
console.log("Соединение закрыто");
};
client.onStompEr ror = (frame) => {
console.error("STOMP ошибка", frame.headers["message"]);
};
client.activate();
В таком виде переподключение отсутствует как механизм — оно управляется вручную.
STOMP.js предоставляет встроенный механизм автоматического
восстановления соединения через параметр
reconnectDelay.
const client = new Client({
brokerURL: "ws://localhost:8080/ws",
reconnectDelay: 5000,
});
Механизм работает следующим образом:
activate()Особенность: стратегия линейная, без адаптации к состоянию сети.
Линейный таймаут неэффективен при нестабильной сети или массовых сбоях сервиса. Более устойчивый подход — экспоненциальная задержка с ограничением максимального интервала.
Логика:
import { Client } from "@stomp/stompjs";
const client = new Client({
brokerURL: "ws://localhost:8080/ws",
reconnectDelay: 0,
});
let attempt = 0;
const maxDelay = 30000;
function scheduleReconnect() {
attempt += 1;
const delay = Math.min(1000 * Math.pow(2, attempt), maxDelay);
setTimeout(() => {
console.log(`Попытка переподключения #${attempt}`);
client.activate();
}, delay);
}
client.onWebSocketCl ose = scheduleReconnect;
client.onStompEr ror = scheduleReconnect;
Такой подход снижает нагрузку на сервер при массовых падениях и уменьшает риск «штормов переподключений».
При одновременном обрыве соединений у тысяч клиентов возникает эффект «thundering herd» — все клиенты переподключаются одновременно. Для предотвращения этого добавляется случайная задержка.
function getDelay(attempt) {
const base = Math.min(1000 * Math.pow(2, attempt), 30000);
const jitter = Math.random() * 1000;
return base + jitter;
}
Jitter особенно важен в системах реального времени (чаты, торговые терминалы, трекинг).
Переподключение без восстановления подписок делает систему фактически бесполезной: сообщения перестают доставляться.
Подход: централизованное хранилище подписок.
const subscriptions = new Map();
function subscribe(destination, callback) {
const sub = client.subscribe(destination, callback);
subscriptions.set(destination, sub);
}
При восстановлении соединения:
client.onConn ect = () => {
console.log("Подключено");
for (const [destination, _sub] of subscriptions) {
client.subscribe(destination, (msg) => {
console.log(msg.body);
});
}
};
Важно: старые подписки после переподключения становятся недействительными, их нельзя переиспользовать напрямую.
Для корректной логики приложения требуется отслеживание состояния клиента:
В STOMP.js нет явного state machine API, поэтому состояние реализуется вручную:
let status = "DISCONNECTED";
client.onConn ect = () => {
status = "OPEN";
};
client.onWebSocketCl ose = () => {
status = "RECONNECTING";
};
client.onStompEr ror = () => {
status = "RECONNECTING";
};
Это состояние используется для:
Одна из типичных ошибок — запуск нескольких activate()
одновременно. Это приводит к гонке соединений.
Решение — флаг блокировки:
let reconnecting = false;
function safeReconnect() {
if (reconnecting) return;
reconnecting = true;
setTimeout(() => {
client.activate();
reconnecting = false;
}, 3000);
}
STOMP.js поддерживает heartbeat, который позволяет обнаруживать «тихие» разрывы соединения.
const client = new Client({
brokerURL: "ws://localhost:8080/ws",
heartbeatIncoming: 10000,
heartbeatOutgoing: 10000,
});
Если heartbeat не подтверждается сервером:
В системах с нестабильной сетью heartbeat должен быть сбалансирован: слишком частый приводит к ложным разрывам, слишком редкий — к позднему обнаружению проблем.
При разрыве соединения возможна потеря сообщений, отправленных клиентом. Для критичных систем используется локальная очередь:
const queue = [];
function send(destination, body) {
if (client.connected) {
client.publish({ destination, body });
} else {
queue.push({ destination, body });
}
}
client.onConn ect = () => {
while (queue.length > 0) {
const msg = queue.shift();
client.publish(msg);
}
};
Практически применяемая модель включает несколько уровней:
reconnectDelay отключёнТакая композиция обеспечивает устойчивость при:
Флаппинг — частые циклы подключения/отключения. В этом случае важно вводить «карантин» соединения:
let failureCount = 0;
function onFailure() {
failureCount++;
if (failureCount > 5) {
console.warn("Карантин соединения");
setTimeout(() => {
failureCount = 0;
client.activate();
}, 60000);
} else {
safeReconnect();
}
}
Это защищает сервер от перегрузки при нестабильных клиентах.
После восстановления соединения часто требуется синхронизация состояния:
STOMP сам по себе не хранит состояние, поэтому используется прикладной протокол поверх сообщений:
client.onConn ect = () => {
client.publish({
destination: "/app/sync",
body: JSON.stringify({ lastMessageId: lastId }),
});
};
Устойчивое подключение в STOMP.js формируется не одной настройкой, а системой:
Такая модель превращает WebSocket-коммуникацию из хрупкого канала в управляемый транспортный слой с предсказуемым поведением при сбоях.