Отладка в реальном времени с Server-Sent Events
Традиционные инструменты отладки PHP требуют обновления страницы для просмотра новых данных. Вы выполняете запрос, переключаетесь на профилировщик, нажимаете обновить, находите последнюю запись и открываете её. Повторяете десятки раз за сессию отладки. ADP устраняет эту проблему с помощью Server-Sent Events (SSE) — отладочные данные поступают в панель в момент их сбора.
В этой статье мы разберём, как система SSE работает изнутри и почему мы выбрали именно её.
Почему SSE, а не WebSockets?
При проектировании слоя реального времени ADP мы рассмотрели три варианта: поллинг, WebSockets и SSE. У каждого свои компромиссы.
Поллинг — самый простой подход: фронтенд периодически запрашивает у сервера новые данные. Работает везде, но вносит задержку (новые данные видны только через интервал опроса) и генерирует лишний трафик, когда ничего не изменилось.
WebSockets обеспечивают полнодуплексную связь. Фронтенд и сервер могут отправлять сообщения в обоих направлениях в любое время. Это мощно, но добавляет сложность: WebSocket-соединения требуют постоянного серверного процесса, усложняют деплой за обратными прокси и нуждаются в явной логике переподключения.
SSE находится посередине. Сервер отправляет события клиенту через стандартное HTTP-соединение. Это однонаправленный канал (только от сервера к клиенту), что полностью соответствует потребностям панели отладки — сервер имеет новые записи, клиент должен о них узнать. SSE-соединения автоматически переподключаются при сбое, работают через стандартную HTTP-инфраструктуру и не требуют специальной настройки сервера.
Для инструмента отладки SSE — правильный выбор. Нам не нужно, чтобы клиент отправлял данные серверу в реальном времени. Нам нужно, чтобы сервер уведомлял клиента о поступлении новых отладочных данных. SSE делает это с минимальной сложностью.
Обзор архитектуры
Реализация SSE в ADP состоит из трёх компонентов:
- События хранилища — Когда отладчик сбрасывает данные коллекторов в хранилище, он генерирует событие, сигнализирующее о наличии новых данных.
- SSE-эндпоинт — HTTP-эндпоинт, удерживающий соединение открытым и передающий события клиенту по мере их возникновения.
- Слушатель на фронтенде — JavaScript
EventSource, который принимает события и обновляет Redux-хранилище.
Поток выглядит так:
Приложение → Сброс отладчика → Запись в хранилище → SSE-событие
↓
SSE-эндпоинт
↓
EventSource (браузер)
↓
Обновление Redux Store
↓
Перерисовка UISSE-эндпоинт
API-модуль предоставляет SSE-эндпоинт по адресу /debug/stream. Когда фронтенд подключается, сервер удерживает соединение открытым и отправляет события в стандартном формате SSE:
event: debug.entry
data: {"id":"abc123","url":"/api/users","method":"GET","status":200,"time":1.23}
event: debug.entry
data: {"id":"def456","url":"/api/orders","method":"POST","status":201,"time":0.87}Каждое событие содержит метаданные новой отладочной записи — достаточно для отрисовки в списке без загрузки полного payload. Когда пользователь кликает на запись, фронтенд запрашивает полные данные через REST-вызов.
Такой подход сохраняет SSE-поток лёгким. Мы отправляем только сводки записей (обычно менее 1 КБ каждая), а не полные данные коллекторов, которые могут быть значительно больше.
Интеграция с фронтендом
На фронтенде соединение управляется через стандартный EventSource:
const source = new EventSource('/debug/stream');
source.addEventListener('debug.entry', (event: MessageEvent) => {
const entry = JSON.parse(event.data);
dispatch(addDebugEntry(entry));
});
source.addEventListener('error', () => {
// EventSource автоматически переподключается.
// Мы обновляем состояние UI для отображения статуса переподключения.
dispatch(setConnectionStatus('reconnecting'));
});
source.addEventListener('open', () => {
dispatch(setConnectionStatus('connected'));
});API EventSource обрабатывает переподключение автоматически. Если соединение прерывается — из-за сетевого сбоя, перезапуска сервера или деплоя — браузер переподключается и возобновляет приём событий. Сервер может опционально отправлять Last-Event-ID для восстановления пропущенных событий.
Работа с несколькими приложениями
ADP может одновременно мониторить несколько приложений. Каждое приложение записывает в собственное пространство имён хранилища. SSE-эндпоинт принимает параметр фильтра для подписки на события от конкретных приложений:
GET /debug/stream?app=my-api
GET /debug/stream?app=my-worker
GET /debug/stream # все приложенияИнтерфейс панели позволяет переключаться между приложениями или просматривать все записи в единой временной шкале, что особенно полезно при отладке распределённых систем, где запрос фронтенда порождает API-вызовы, запускающие фоновые задачи.
Консольные команды и обработчики очередей
SSE особенно ценен для отладки консольных команд и обработчиков очередей. Эти процессы не генерируют HTTP-ответы, которые может перехватить традиционный профилировщик. С ADP отладчик сбрасывает данные в конце выполнения каждой команды или задачи, а SSE-поток немедленно доставляет их в панель.
Это означает, что вы можете наблюдать за обработкой очереди в реальном времени — видеть запросы к БД, сообщения логов и время выполнения каждой задачи по мере их обработки.
Производительность
SSE-соединения — это долгоживущие HTTP-соединения. Каждая открытая вкладка браузера удерживает одно соединение. Для инструмента разработки это не проблема — обычно открыта одна-две вкладки панели. Но мы предприняли шаги для минимизации потребления ресурсов:
- Буферизация событий — События группируются, когда несколько отладочных записей поступают в быстрой последовательности, уменьшая количество отдельных сообщений.
- Heartbeat — Сервер отправляет комментарий (
: heartbeat) каждые 30 секунд для поддержания соединения через прокси и балансировщики нагрузки. - Корректное завершение — Когда сервер отладки останавливается, он отправляет событие закрытия, чтобы фронтенд мог показать соответствующий статус вместо бесконечных попыток переподключения.
Попробуйте
Если у вас установлен ADP, SSE-соединение активно по умолчанию. Откройте панель, выполните несколько запросов в приложении и наблюдайте, как записи появляются в реальном времени. Настройка не требуется.
Для приложений, выполняющих консольные команды или обработчики очередей, убедитесь, что сервер отладки ADP запущен рядом с вашим приложением. CLI-модуль предоставляет простую команду для запуска:
php vendor/bin/adp serveВ следующей публикации мы рассмотрим, как ADP интегрируется с ИИ-ассистентами через MCP-сервер (Model Context Protocol) — привнося интеллектуальный анализ в ваши отладочные данные.