
Наконец-то приходит сообщение: страница перестала обновляться. Никакой ошибки не было. Цифры просто перестали меняться, а обновление страницы всё исправило.
Поэтому об этом редко кто-то говорит. Человек, который столкнулся с этим, получает рабочее решение в руках в течение нескольких секунд, поэтому большинство никогда вам об этом не рассказывают. Те, кто рассказывает, не могут описать ничего, кроме отсутствия.
Закрытое соединение - это не ошибка
Первое, что нужно переучить, это то, что это событие не приходит как ошибка запроса. Оно не приходит так.
WebSocket срабатывает с событием error, и MDN описывает его тип тремя словами: “Общее
событие”. Под ним нет пользовательского интерфейса, что означает отсутствие кода, причины и
статуса на объекте, который вы получите. Ничего не выбросит исключение, поэтому ничего не
попадёт в try/catch. Соединение заканчивается, и единственное, что несёт информацию, это
событие close, которое следует за ним.
Поэтому код закрытия не является деталью. Это всё сообщение целиком.
Коды, которые что-то означают
Есть четыре, которые стоит знать наизусть. MDN цитирует их напрямую.
- 1000, нормальное закрытие: “Соединение успешно завершило цель, для которой оно было создано”. Нечего расследовать.
- 1001, уходит: “Конечная точка уходит, либо из-за отказа сервера, либо потому что браузер переходит на другую страницу, которая открыла это соединение”.
- 1011, внутренняя ошибка: “Сервер прерывает соединение, потому что столкнулся с неожиданным условием, которое помешало ему выполнить запрос”.
- 1006, ненормальное закрытие - это тот, который вы действительно увидите, и об этом будет рассказываться в остальной части этой статьи.
Прочитайте 1001 дважды, потому что он охватывает два не связанных между собой события с одним номером. Отключение сервера и переход пользователя по ссылке оба производят его. Если вы считаете 1001 как инциденты, вы считаете людей, уходящих с вашего сайта, как инциденты, и это число будет отслеживать ваш трафик, а не вашу надёжность.
1011 - это противоположность, и это тот, на который стоит поставить оповещение. Это сервер, признающий неисправность в его собственных словах.
Код, который вы видите чаще всего, не мог прийти ни от кого
Вот та часть, которая почти никогда не написана, и это меняет то, как вы расследуете.
RFC 6455 определяет 1006 так:
1006 - это зарезервированное значение, и конечная точка НЕ ДОЛЖНА устанавливать его в качестве кода статуса в фрейме управления Close. Оно предназначено для использования в приложениях, ожидающих кода состояния, чтобы указать, что соединение было закрыто ненормально, например, без отправки или получения фрейма Close.
1005 несёт то же самое запрещение: “1005 - это зарезервированное значение, и конечная точка НЕ ДОЛЖНА устанавливать его в качестве кода статуса в фрейме управления Close”.
Поэтому ни один из них не может быть отправлен. Не вашим сервером, не браузером, не чем-либо посредине. Они запрещены на проводе. Когда ваш обработчик читает 1006, он читает число, которое ваш собственный браузер придумал локально, чтобы описать тот факт, что соединение закончилось и никогда не пришёл фрейм закрытия.
Это более сильное утверждение, чем оно выглядит. Это означает, что нет соответствующего события где-то ещё, куда можно пойти и найти.
- Что люди ищут
- Строку логов сервера, объясняющую 1006, в предположении, что сервер его отправил
- Что существует
- Ничего. Код был сгенерирован в браузере. Сервер может даже не знать, что соединение исчезло, и то, что его убило, часто вообще не сервер
1006 поэтому не является причиной. Это форма доказательства: что-то между двумя концами перестало передавать, и ни один конец не смог попрощаться.
Кто на самом деле закрывает соединение, которое никто не закрывал
Как только вы признаёте, что браузер сообщает о молчании, подозреваемыми становятся вещи, которые сидят посередине и имеют мнения о простаивающих соединениях.
Обратный прокси или балансировщик нагрузки с таймаутом неактивности - это распространённый случай. Эти таймауты настраиваются и часто коротки, и WebSocket, не передающий ничего между обновлениями, выглядит ровно как заброшенное соединение. Корпоративное сетевое оборудование делает то же самое, более агрессивно. Телефон, переходящий с wifi на мобильные данные, меняет адрес клиента под открытым сокетом, что заканчивает его без церемоний. Ноутбуки при переходе в режим сна делают то же самое.
Обратите внимание на то, что у каждого из них есть общее: ваш код приложения верен, ваш сервер здоров, и с данными ничего не так. Транспорт исчез. Это также объясняет, почему это происходит у некоторых пользователей постоянно, а у других никогда, что является признаком, отправляющим отчёт об ошибке с меткой “невозможно воспроизвести”.
Стандартная защита - это сердцебиение: отправляйте что-нибудь маленькое на таймере, чаще, чем самый короткий таймаут неактивности между вами и вашими пользователями. Это не исправляет разорванное соединение. Это предотвращает ошибку простаивающего соединения, принимаемого за неработающее, что является другой проблемой, которая производит тот же отчёт.
Что записывать, потому что событие закрытия - это единственный свидетель
Событие закрытия происходит один раз в браузере, а затем страница обычно переподключается и доказательство исчезает. Четыре поля стоит захватить в этот момент.
socket.addEventListener('close', (event) => {
report({
code: event.code, // 1006 означает, что никто не отправил код
wasClean: event.wasClean, // true, если соединение закрылось чисто
reason: event.reason, // обычно пусто, и пусто информативно
secondsSinceLastMessage: (Date.now() - lastMessageAt) / 1000,
reconnects: reconnectCount
});
});
wasClean - это самый дешёвый сигнал из четырёх: MDN определяет его как true “если соединение
закрылось чисто”, поэтому false разделяет соединение, которое закончилось, от того, которое было
закрыто. И secondsSinceLastMessage - это поле, которое ловит таймаут неактивности, потому что
интервал будет группироваться вокруг одного значения для каждого затронутого пользователя. Кластер
чуть менее шестидесяти секунд - это не совпадение, это настроенное число где-то на пути.
Session Replay
Бесплатное расширение Chrome. Один щелчок на странице, которая неправильно ведёт себя, захватывает снимок экрана, консоль и журнал сети, и передаёт вам ссылку для вставки в тикет.
Вопрос, который нужно задать человеку, который об этом сообщил
Один вопрос, и это не технический: обновление страницы это исправило?
Если да, то данные никогда не были неправильными и ваш рендеринг никогда не был неправильным. Свежее соединение было всё, что было нужно, что указывает на транспорт и уходит от всего остального. Этот один ответ устраняет больше, чем любое количество угадывания их сети, и они могут дать его, не зная, что такое WebSocket.
Следующий вопрос, стоящий того, чтобы его задать, это как долго была открыта страница. Вкладка, оставленная открытой на обед, и вкладка, открытая минуту назад, это разные случаи, и докладчик знает ответ.
Что взять из этого
Отказ молчалив по конструкции. Ничего не выбросит исключение, событие error не несёт деталей, и
страница продолжает отображать последнее, что ей было сказано.
Код закрытия - это всё сообщение, и самый распространённый из них - это локальное изобретение: 1006 это ваш браузер, говорящий, что ничего не пришло, а не ваш сервер, говорящий, почему он ушёл. Коды, которые никто не может отправить, это коды, которые никто не может объяснить, поэтому перестаньте искать серверную половину и начните искать то, что находится между вами и пользователем.
Спецификация сама по себе - это RFC 6455, и коды закрытия с их однострочными значениями находятся на странице CloseEvent.code MDN.