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 или аналогичные решения).