diff --git a/src/Drivers/LaravelHttpServer.php b/src/Drivers/LaravelHttpServer.php index 97ae5fdb..9689823e 100644 --- a/src/Drivers/LaravelHttpServer.php +++ b/src/Drivers/LaravelHttpServer.php @@ -317,33 +317,33 @@ private function handleRequest(AmpRequest $request): Response */ private function asset(string $filepath): Response { - $file = fopen($filepath, 'r'); - - if ($file === false) { - return new Response(404); - } - $mimeTypes = new MimeTypes(); $contentType = $mimeTypes->getMimeTypes(pathinfo($filepath, PATHINFO_EXTENSION)); - $contentType = $contentType[0] ?? 'application/octet-stream'; if (str_ends_with($filepath, '.js')) { - $temporaryStream = fopen('php://temp', 'r+'); - assert($temporaryStream !== false, 'Failed to open temporary stream.'); - - // @phpstan-ignore-next-line - $temporaryContent = fread($file, (int) filesize($filepath)); - - assert($temporaryContent !== false, 'Failed to open temporary stream.'); + // ReadableResourceStream sets php://temp to non-blocking mode, causing + // AMPHP's event loop to deliver only the first 8192-byte chunk for files + // written to a php://temp stream. Using file_get_contents + a string body + // sends the full content in one shot, avoiding the truncation. + $content = file_get_contents($filepath); + + if ($content === false) { + return new Response(404); + } - $content = $this->rewriteAssetUrl($temporaryContent); + $content = $this->rewriteAssetUrl($content); - fwrite($temporaryStream, $content); + return new Response(200, [ + 'Content-Type' => $contentType, + 'Content-Length' => (string) strlen($content), + ], $content); + } - rewind($temporaryStream); + $file = fopen($filepath, 'r'); - $file = $temporaryStream; + if ($file === false) { + return new Response(404); } return new Response(200, [ diff --git a/tests/Unit/Drivers/Laravel/LaravelHttpServerTest.php b/tests/Unit/Drivers/Laravel/LaravelHttpServerTest.php index 7491d6a6..f92c9340 100644 --- a/tests/Unit/Drivers/Laravel/LaravelHttpServerTest.php +++ b/tests/Unit/Drivers/Laravel/LaravelHttpServerTest.php @@ -21,6 +21,19 @@ ->assertDontSee('http://localhost'); }); +it('serves JS files larger than 8192 bytes in full without truncation', function (): void { + // Create a JS file well over 8KB with a sentinel value at the very end. + // Before the fix, ReadableResourceStream on a php://temp stream would only + // deliver the first 8192-byte chunk, so the sentinel would be missing. + $padding = str_repeat('//' . str_repeat('x', 78) . PHP_EOL, 110); // ~8250 bytes of padding + @file_put_contents( + public_path('large.js'), + $padding . "console.log('END_SENTINEL');", + ); + + visit('/large.js')->assertSee('END_SENTINEL'); +}); + it('includes cookies set in the test', function (): void { Route::get('/cookies', fn (Request $request): array => $request->cookies->all());