Отправка Server-Sent Events (SSE) в Laravel

Server-Sent Events (SSE) - это стандартный способ отправки односторонних событий от сервера к браузеру через HTTP. По своей идее SSE похож на WebSocket, но работает только в направлении сервер → клиент.

Это хорошо подходит, если:

  • нужно просто отправлять обновления с сервера в реальном времени;

  • нет необходимости в двусторонней связи (клиент → сервер);

  • не хочется разворачивать и поддерживать отдельный WebSocket-сервер.

При этом браузер получает события автоматически, без опроса сервера (polling).


Как это работает

В SSE всегда участвуют две стороны:

  • Клиентская - создаёт объект EventSource в JavaScript и открывает соединение.

  • Серверная (Laravel) - возвращает длительный потоковый HTTP-ответ (stream) и отправляет события по мере необходимости.

Соединение может жить долго, от нескольких минут до часов.


Настройка потокового ответа в Laravel

В Laravel поток легко создаётся с помощью метода response()->stream(). Для корректной работы нужно:

  • указать заголовок Content-Type: text/event-stream;

  • отключить буферизацию (важно для nginx);

  • отправлять данные в формате SSE.

Простой пример

return response()->stream(function () {
    // Здесь будем отправлять события
}, 200, [
    'Content-Type' => 'text/event-stream',
    'Cache-Control' => 'no-cache',
    'Connection' => 'keep-alive',
    'X-Accel-Buffering' => 'no',
    'Access-Control-Allow-Origin' => '*',
]);

Ключевой момент здесь text/event-stream заголовок, так браузер поймет, что ответ будет длительным и не закроет соединение.


Отправка события

Чтобы отправить событие клиенту, внутри stream достаточно вывести данные в нужном формате и принудительно сбросить буфер.

return response()->stream(function () {
    while (ob_get_level()) {
        ob_end_flush();
    }

    @ini_set('output_buffering', 'off');
    @ini_set('zlib.output_compression', '0');
    set_time_limit(0);

    // Время между попытками переподключения (мс)
    echo "retry: 2000\n\n";
    @ob_flush(); flush();

    // Отправляем данные
    echo "data: " . json_encode([
        'timestamp' => date('Y-m-d H:i:s'),
        'data' => 'Your Data'
    ]) . "\n\n";

    @ob_flush(); flush();
});

Важно помнить:

  • каждое событие обязательно начинается с data:;

  • событие заканчивается пустой строкой (\n\n);

  • данные могут быть как простой строкой, так и JSON.


Что делать при долгих процессах

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

Чтобы этого избежать, отправляют heartbeat-события - служебные сообщения, которые просто поддерживают соединение активным.

$lastHeartbeat = time();

// … ваш долгий процесс
while ($process->isRunning()) {

    // каждые 30 секунд отправляем heartbeat
    if (time() - $lastHeartbeat >= 30) {
        echo "data: " . json_encode([
            'timestamp' => date('Y-m-d H:i:s'),
            'type' => 'heartbeat',
            'output' => 'Connection alive',
        ]) . "\n\n";

        @ob_flush(); flush();
        $lastHeartbeat = time();
    }
}

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


Клиентская часть(подписка на SSE)

На стороне клиента Server-Sent Events работают из коробки, без библиотек и сложной настройки. Всё, что нужно, это обьект EventSource.

Базовый пример на JavaScript

<script>
    const source = new EventSource('/sse');

    source.onmessage = function (event) {
        const data = JSON.parse(event.data);
        console.log('Новое событие:', data);
    };

    source.onerror = function (error) {
        console.error('Ошибка SSE:', error);
    };
</script>

Что здесь происходит:

  • браузер открывает постоянное HTTP-соединение с /sse;

  • сервер может отправлять события сколько угодно долго;

  • каждая строка data: на сервере превращается в event.data в браузере.


Когда использовать SSE

SSE стоит использовать, если:

  • нужна простая, односторонняя связь от сервера к клиенту;

  • приложение небольшое и не требует сложной масштабируемости;

  • WebSocket-сервер избыточен для текущей задачи.

SSE не подойдёт, если:

  • требуется двусторонняя связь;

  • ожидается большое количество одновременных подключений;

  • нужна высокая масштабируемость (в таких случаях лучше WebSocket, Laravel Reverb или аналогичные решения).

Комментарии (0)

Войдите, чтобы оставить комментарий

Похожие статьи

NativePHP для мобильных приложений стал полностью бесплатным

NativePHP for Mobile с версии v3 стал полностью бесплатным и открытым, с переходом на плагинную архитектуру, новым инструментом Jump для тестирования и другими нововведениями для разработчиков на Laravel.

23 0 1 мин