diff --git a/src/Drivers/LaravelHttpServer.php b/src/Drivers/LaravelHttpServer.php index fd5a4144..ac251f10 100644 --- a/src/Drivers/LaravelHttpServer.php +++ b/src/Drivers/LaravelHttpServer.php @@ -369,9 +369,10 @@ private function handleRequest(AmpRequest $request): Response ob_start(); $response->sendContent(); } finally { - // @phpstan-ignore-next-line - $content = mb_trim(ob_get_clean()); + $buffer = ob_get_clean(); } + + $content = $buffer === false ? '' : $buffer; } return new Response( diff --git a/tests/Browser/Visit/StreamedResponseTest.php b/tests/Browser/Visit/StreamedResponseTest.php new file mode 100644 index 00000000..6ca44f83 --- /dev/null +++ b/tests/Browser/Visit/StreamedResponseTest.php @@ -0,0 +1,92 @@ + '
Home
'); + Route::get('/binary', fn (): StreamedResponse => response()->stream( + function () use ($bytes): void { + echo $bytes; + }, + 200, + ['Content-Type' => 'image/jpeg'], + )); + + $page = visit('/'); + + $page->assertScript( + "async () => { + const response = await fetch('/binary'); + const bytes = new Uint8Array(await response.arrayBuffer()); + + return Array.from(bytes).join(','); + }", + implode(',', unpack('C*', $bytes)), + ); +}); + +it('may serve a binary streamed image that the browser is able to decode', function (): void { + $image = file_get_contents(__DIR__.'/../../Fixtures/v4.jpg'); + + Route::get('/', fn (): string => 'Streamed Image'); + Route::get('/image', fn (): StreamedResponse => response()->stream( + function () use ($image): void { + echo $image; + }, + 200, + ['Content-Type' => 'image/jpeg'], + )); + + $page = visit('/'); + + $page->assertScript("document.getElementById('image').complete && document.getElementById('image').naturalWidth > 0"); +}); + +it('may serve a textual streamed response without altering its whitespace', function (): void { + Route::get('/', fn (): string => '
Home
'); + Route::get('/text', fn (): StreamedResponse => response()->stream( + function (): void { + echo "\n Hello World \n"; + }, + 200, + ['Content-Type' => 'text/plain'], + )); + + $page = visit('/'); + + $page->assertScript( + "async () => JSON.stringify(await (await fetch('/text')).text())", + json_encode("\n Hello World \n"), + ); +}); + +it('delivers every server-sent event to the browser', function (): void { + Route::get('/', fn (): string => '
-
+ '); + Route::get('/events', fn (): StreamedResponse => response()->stream( + function (): void { + echo "data: first\n\n"; + echo "data: second\n\n"; + }, + 200, + ['Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache'], + )); + + $page = visit('/'); + + $page->assertSee('first'); + + expect($page->text('#received'))->toBe('first,second'); +});