Звичайна відповідь формується повністю, а потім відправляється. Потокова - відправляється частинами в міру готовності: користувач бачить перші дані раніше, а сервер не тримає всю відповідь у пам'яті.
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 по токенах, експорт великих даних, прогрес довгої операції.