laravel / octane

Supercharge your Laravel application's performance.
https://laravel.com/docs/octane
MIT License
3.77k stars 296 forks source link

fix: RoadRunner streaming through generators #939

Closed danharrin closed 2 months ago

danharrin commented 2 months ago

Fixes #903.

RoadRunner supports streamed responses, but not in the same way that other servers do. Instead out using the output buffer with echo, it supports using a Generator function as the response callback to "yield" each chunk of the response.

Previous fixes have involved collecting the output buffer and returning it at once from the server. This is not streaming as all chunks are collected into memory and sent at once.

This implementation allows you to use a Generator as the callable in the streamed response. Each yield sends a chunk in the response. It is quite simple and just interacts with the underlying HttpWorker that is used by the Roadrunner client. The new code is only invoked if the streamed response function is a generator, else the old behaviour still persists.

use Generator;

return response()->stream(function (): Generator {
    yield 1;

    sleep(1);

    yield 2;

    sleep(1);

    yield 3;

    sleep(1);

    yield 4;

    sleep(1);

    yield 5;
}, 200, [
    'Content-Type' => 'text/html; charset=utf-8;',
    'Cache-Control' => 'no-cache',
    'X-Accel-Buffering' => 'no',
]);

Note: we do not need to evaluate the function to know if we need to stream it in this way, we use reflection to do this ahead of time, ensuring that the function returns a Generator object.

I would like to thank @donnysim for his investigative work and initial solution which was adapted into this PR.

donnysim commented 2 months ago

I'm so sorry, I completely forgot about the whole thing and could've saved you some trouble if you were too busy 🙏. This looks good though 👍.