Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Як віддавати дані потоком у Laravel: stream, streamJson і eventStream?

Звичайна відповідь формується повністю, а потім відправляється. Потокова - відправляється частинами в міру готовності: користувач бачить перші дані раніше, а сервер не тримає всю відповідь у пам'яті.

stream - довільні дані частинами:

return response()->stream(function (): void {
    foreach ($this->generator->chunks() as $chunk) {
        echo $chunk;
        ob_flush();
        flush();
    }
}, 200, ['X-Accel-Buffering' => 'no']);

Простіше - передати замикання-генератор: Laravel сам відправлятиме кожне yield і скидатиме буфер:

Route::get('/answer', function (LlmClient $llm) {
    return response()->stream(function () use ($llm) {
        foreach ($llm->stream(request('prompt')) as $token) {
            yield $token;
        }
    });
});

streamJson - великий JSON, де частина даних - лінива колекція:

return response()->streamJson([
    'users' => User::query()->cursor(),
]);

Рядки читаються з бази й кодуються в JSON по одному - пам'ять не залежить від кількості записів.

eventStream - Server-Sent Events (text/event-stream):

return response()->eventStream(function () {
    while ($progress = $this->import->progress()) {
        yield new StreamedEvent(event: 'progress', data: ['percent' => $progress]);
        sleep(1);
    }
});

Браузер слухає через EventSource, а наприкінці Laravel надсилає завершальне повідомлення </stream>. Для React і Vue є готові хуки useStream і useEventStream з пакетів @laravel/stream-react / @laravel/stream-vue.

Що ламає потокову передачу:

  • буферизація на шляху: Nginx за замовчуванням буферизує відповідь від PHP-FPM - заголовок X-Accel-Buffering: no чи налаштування proxy_buffering off; так само CDN і стиснення gzip, що чекає на заповнення буфера;
  • тайм-аути: max_execution_time, тайм-аути проксі й балансувальника обривають довге з'єднання;
  • воркер зайнятий: кожен відкритий потік тримає процес PHP-FPM. Сотня користувачів, що слухають SSE, - сотня зайнятих воркерів. Для масових підписок на події краще WebSocket через Reverb;
  • сесія: блокування сесії (->block()) чи сесія, відкрита на весь час потоку, затримує інші запити того самого користувача;
  • помилка посеред потоку: статус 200 уже відправлено - про помилку доведеться повідомити в самих даних.

Коли доречно: відповіді LLM по токенах, експорт великих даних, прогрес довгої операції.

Докладніше в документації: Відповіді: потокові відповіді

Схожі питання