SSE теряет события после обрыва: проверяем Last-Event-ID

Соединение SSE восстановилось, но часть обновлений исчезла. Проверяем курсор, повторную выдачу истории и обработку событий в интерфейсе.

После обрыва SSE-поток может снова открыться, а интерфейс — пропустить обновления. Проверьте три звена: последнее полученное событие с id, заголовок Last-Event-ID в повторном запросе и выдачу пропущенных событий сервером.

Переподключение восстанавливает соединение, но сохранность истории обеспечивает приложение. Для диагностики нужен контролируемый тест: событие до обрыва, события во время паузы и сравнение результата после возврата сети.

1. Зафиксируйте последнее событие до обрыва

Проводите проверку на стенде с тестовыми данными. Откройте Network в инструментах разработчика и найдите SSE-запрос: ожидаются HTTP 200 и Content-Type: text/event-stream.

Пример сообщения с условным номером:

id: 41
event: status
data: {"state":"processing"}

После data должна идти пустая строка. Если указан event: status, обработчик подписывают на status: один onmessage такое именованное событие не обработает. Формат полей описан в руководстве MDN по SSE.

Сопоставьте ID в потоке, event.lastEventId в обработчике и фактическое изменение интерфейса. ID внутри JSON не заменяет поле id:. Пустое поле id: сбрасывает сохранённый идентификатор, а сообщение без нового ID может унаследовать предыдущий.

2. Проверьте автоматический повторный запрос

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

В новом запросе того же объекта EventSource найдите Last-Event-ID. При сохранённом ID из примера ожидается значение 41. Такой механизм задаёт стандарт HTML.

Если заголовка нет, проверьте, получил ли браузер законченный блок с ID и не было ли его сброса. Затем изучите код обработки ошибки: вызовы close() и new EventSource() создают другой сценарий, чем встроенное переподключение.

Перезагрузка страницы и новый объект не наследуют курсор прежнего объекта. Если восстановление требуется и после них, приложению нужен отдельный согласованный способ хранения и передачи позиции. Не переносите идентификаторы доступа в URL ради такого обхода.

3. Найдите, где теряется повторная выдача

Допустим, перед обрывом клиент получил событие 41, а во время паузы появились 42 и 43. В тестовой модели с последовательными номерами после запроса с курсором 41 сервер должен выдать 42 и 43, затем продолжить живой поток.

Если сразу приходит 44, сравните наблюдения по цепочке:

  1. Заголовок виден в браузере, но приложение его не получает — проверьте правила прокси и передачу заголовков.
  2. Приложение получает курсор, но начинает с новых событий — проверьте реализацию чтения истории.
  3. История есть только на части узлов — проверьте смену экземпляра приложения и доступность журнала после рестарта.
  4. Пропуск возникает между чтением истории и подпиской — проверьте границу перехода к живому потоку.

Последняя ветка требует внимания разработчика: событие может появиться уже после выборки истории, но до подключения подписчика. Проверка должна охватывать этот промежуток, а реализация — исключать потерю на стыке.

Сами ID не обязаны быть числами без разрывов. Сравнивайте с журналом ожидаемых тестовых событий, а не объявляйте любой скачок идентификатора потерей.

4. Отделите доставку от обработки

Наличие события в Network ещё не доказывает, что интерфейс применил обновление. Зафиксируйте отдельно: получение сообщения, успешный разбор данных и применение к состоянию страницы.

Last-Event-ID не является подтверждением успешной бизнес-обработки. Если обработчик завершился ошибкой, браузер мог уже запомнить позицию. Для критичных данных нужны сверка с актуальным состоянием сервера и обработка повторов без повторного побочного эффекта.

Заранее определите поведение для слишком старого курсора: например, повторная загрузка актуального состояния с согласованной позицией потока. Молчаливое продолжение «с текущего момента» оставляет интерфейс неполным. Срок хранения истории выбирают по допустимой длительности отключения клиента.

Если события всё же приходят, но с задержкой и пачками, используйте отдельный порядок проверки буферизации SSE в nginx.

Как принять исправление

Повторите тест для короткого обрыва, перезапуска приложения, смены серверного узла и выхода за срок хранения истории. Для каждого сценария сравните ожидаемые события с применёнными: пропуск должен либо восстанавливаться, либо приводить к явной синхронизации состояния.

Web-Puls помогает контролировать доступность сайта, но обычная HTTP-проверка не подтверждает полноту SSE-истории. Этот тест восстановления нужно проверять отдельно.

Если не удаётся локализовать разрыв между браузером, прокси и приложением, отправьте заявку на диагностику. Укажите публичный URL, время и часовой пояс сбоя, схему соединения и обезличенное сравнение ожидаемых и полученных событий; пароли, Cookie и токены не прикладывайте.

Проверьте свой сайт прямо сейчас

Введите адрес сайта: Web-Puls покажет HTTP-код, время ответа и базовую диагностику. Для постоянного контроля можно подключить мониторинг.

Нужна помощь с диагностикой ошибки?

Опишите симптомы в короткой заявке: URL, код ответа, время появления и что менялось перед сбоем. Специалист поддержки оценит задачу и предложит формат работ.