From 4a9c1022c0fc878d8df7bdb86a2a61e1cdd48197 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 11 Mar 2025 10:09:48 +0100 Subject: [PATCH 1/5] POC --- src/Chunk.php | 65 +++++++++++++++ src/Client.php | 35 +++++--- src/{FetchException.php => Exception.php} | 2 +- tests/ClientTest.php | 97 ++++++++++++++++++++++- tests/router.php | 61 +++++++++++--- 5 files changed, 236 insertions(+), 24 deletions(-) create mode 100644 src/Chunk.php rename src/{FetchException.php => Exception.php} (90%) diff --git a/src/Chunk.php b/src/Chunk.php new file mode 100644 index 0000000..b791d80 --- /dev/null +++ b/src/Chunk.php @@ -0,0 +1,65 @@ +data; + } + + /** + * Get the size of the chunk in bytes + * + * @return int + */ + public function getSize(): int + { + return $this->size; + } + + /** + * Get the timestamp when the chunk was received + * + * @return float + */ + public function getTimestamp(): float + { + return $this->timestamp; + } + + /** + * Get the sequential index of this chunk + * + * @return int + */ + public function getIndex(): int + { + return $this->index; + } +} \ No newline at end of file diff --git a/src/Client.php b/src/Client.php index e52da87..80daa27 100644 --- a/src/Client.php +++ b/src/Client.php @@ -199,6 +199,7 @@ private function withRetries(callable $callback): mixed * @param string $method * @param array|array $body * @param array $query + * @param ?callable $chunks Optional callback function that receives a Chunk object * @return Response */ public function fetch( @@ -206,9 +207,10 @@ public function fetch( string $method = self::METHOD_GET, ?array $body = [], ?array $query = [], + ?callable $chunks = null, ): Response { if (!in_array($method, [self::METHOD_PATCH, self::METHOD_GET, self::METHOD_CONNECT, self::METHOD_DELETE, self::METHOD_POST, self::METHOD_HEAD, self::METHOD_OPTIONS, self::METHOD_PUT, self::METHOD_TRACE])) { - throw new FetchException("Unsupported HTTP method"); + throw new Exception("Unsupported HTTP method"); } if (isset($this->headers['content-type']) && $body !== null) { @@ -229,6 +231,8 @@ public function fetch( } $responseHeaders = []; + $responseBody = ''; + $chunkIndex = 0; $ch = curl_init(); $curlOptions = [ CURLOPT_URL => $url, @@ -244,11 +248,24 @@ public function fetch( $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); return $len; }, + CURLOPT_WRITEFUNCTION => function ($ch, $data) use ($chunks, &$responseBody, &$chunkIndex) { + if ($chunks !== null) { + $chunk = new Chunk( + data: $data, + size: strlen($data), + timestamp: microtime(true), + index: $chunkIndex++ + ); + $chunks($chunk); + } else { + $responseBody .= $data; + } + return strlen($data); + }, CURLOPT_CONNECTTIMEOUT => $this->connectTimeout, CURLOPT_TIMEOUT => $this->timeout, CURLOPT_MAXREDIRS => $this->maxRedirects, CURLOPT_FOLLOWLOCATION => $this->allowRedirects, - CURLOPT_RETURNTRANSFER => true, CURLOPT_USERAGENT => $this->userAgent ]; @@ -257,21 +274,19 @@ public function fetch( curl_setopt($ch, $option, $value); } - $sendRequest = function () use ($ch, &$responseHeaders) { + $sendRequest = function () use ($ch, &$responseHeaders, &$responseBody) { $responseHeaders = []; - $responseBody = curl_exec($ch); - $responseStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - if (curl_errno($ch)) { + $success = curl_exec($ch); + if ($success === false) { $errorMsg = curl_error($ch); + curl_close($ch); + throw new Exception($errorMsg); } + $responseStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); - if (isset($errorMsg)) { - throw new FetchException($errorMsg); - } - return new Response( statusCode: $responseStatusCode, headers: $responseHeaders, diff --git a/src/FetchException.php b/src/Exception.php similarity index 90% rename from src/FetchException.php rename to src/Exception.php index ac6a9bd..29a3f1b 100644 --- a/src/FetchException.php +++ b/src/Exception.php @@ -2,7 +2,7 @@ namespace Utopia\Fetch; -class FetchException extends \Exception +class Exception extends \Exception { /** * Constructor diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 5b9276e..524a31f 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -39,7 +39,7 @@ public function testFetch( body: $body, query: $query ); - } catch (FetchException $e) { + } catch (Exception $e) { echo $e; return; } @@ -107,7 +107,7 @@ public function testSendFile( ], query: [] ); - } catch (FetchException $e) { + } catch (Exception $e) { echo $e; return; } @@ -156,7 +156,7 @@ public function testGetFile( body: [], query: [] ); - } catch (FetchException $e) { + } catch (Exception $e) { echo $e; return; } @@ -189,7 +189,7 @@ public function testRedirect(): void body: [], query: [] ); - } catch (FetchException $e) { + } catch (Exception $e) { echo $e; return; } @@ -418,4 +418,93 @@ public function testCustomRetryStatusCodes(): void $this->assertGreaterThan($now + 3.0, microtime(true)); unlink(__DIR__ . '/state.json'); } + + /** + * Test for chunk handling + * @return void + */ + public function testChunkHandling(): void + { + $client = new Client(); + $chunks = []; + $lastChunk = null; + + $response = $client->fetch( + url: 'localhost:8000/chunked', + method: Client::METHOD_GET, + chunks: function(Chunk $chunk) use (&$chunks, &$lastChunk) { + $chunks[] = $chunk; + $lastChunk = $chunk; + } + ); + + $this->assertGreaterThan(0, count($chunks)); + $this->assertEquals(200, $response->getStatusCode()); + + // Test chunk metadata + foreach ($chunks as $index => $chunk) { + $this->assertEquals($index, $chunk->getIndex()); + $this->assertGreaterThan(0, $chunk->getSize()); + $this->assertGreaterThan(0, $chunk->getTimestamp()); + $this->assertEquals($response->getStatusCode(), $chunk->getStatusCode()); + $this->assertEquals($response->getHeaders(), $chunk->getHeaders()); + } + + // Test last chunk + $this->assertTrue($lastChunk->isLast()); + } + + /** + * Test chunk handling with JSON response + * @return void + */ + public function testChunkHandlingWithJson(): void + { + $client = new Client(); + $client->addHeader('content-type', 'application/json'); + + $chunks = []; + $response = $client->fetch( + url: 'localhost:8000/chunked-json', + method: Client::METHOD_POST, + body: ['test' => 'data'], + chunks: function(Chunk $chunk) use (&$chunks) { + $chunks[] = $chunk; + } + ); + + $this->assertGreaterThan(0, count($chunks)); + + // Test JSON handling + foreach ($chunks as $chunk) { + $this->assertTrue($chunk->isJson()); + $this->assertEquals('application/json', $chunk->getContentType()); + + if ($data = $chunk->tryDecodeJson()) { + $this->assertIsArray($data); + } + } + } + + /** + * Test chunk handling with error response + * @return void + */ + public function testChunkHandlingWithError(): void + { + $client = new Client(); + $errorChunk = null; + + $response = $client->fetch( + url: 'localhost:8000/error', + method: Client::METHOD_GET, + chunks: function(Chunk $chunk) use (&$errorChunk) { + $errorChunk = $chunk; + } + ); + + $this->assertNotNull($errorChunk); + $this->assertEquals(404, $errorChunk->getStatusCode()); + $this->assertEquals(404, $response->getStatusCode()); + } } diff --git a/tests/router.php b/tests/router.php index 544ed22..eafdaad 100644 --- a/tests/router.php +++ b/tests/router.php @@ -44,8 +44,7 @@ function setState(array $newState): void if ($curPageName == 'redirect') { header('Location: http://localhost:8000/redirectedPage'); exit; -} -if ($curPageName == 'image') { +} elseif ($curPageName == 'image') { $filename = __DIR__."/resources/logo.png"; header("Content-disposition: attachment;filename=$filename"); header("Content-type: application/octet-stream"); @@ -85,15 +84,59 @@ function setState(array $newState): void 'success' => true, 'attempts' => $state['attempts'] ]); +} elseif ($curPageName == 'chunked') { + // Set headers for chunked response + header('Content-Type: text/plain'); + header('Transfer-Encoding: chunked'); + + // Send chunks with delay + $chunks = [ + "This is the first chunk\n", + "This is the second chunk\n", + "This is the final chunk\n" + ]; + + foreach ($chunks as $chunk) { + echo $chunk; + flush(); + usleep(100000); // 100ms delay between chunks + } + + exit; +} elseif ($curPageName == 'chunked-json') { + // Set headers for chunked JSON response + header('Content-Type: application/json'); + header('Transfer-Encoding: chunked'); + + // Send JSON chunks + $chunks = [ + json_encode(['chunk' => 1, 'data' => 'First chunk']), + json_encode(['chunk' => 2, 'data' => 'Second chunk']), + json_encode(['chunk' => 3, 'data' => 'Final chunk']) + ]; + + foreach ($chunks as $chunk) { + echo $chunk . "\n"; // Add newline for JSON lines format + flush(); + usleep(100000); // 100ms delay between chunks + } + + exit; +} elseif ($curPageName == 'error') { + http_response_code(404); + header('Content-Type: application/json'); + echo json_encode(['error' => 'Not found']); + exit; } + $resp = [ - 'method' => $method, - 'url' => $url, - 'query' => $query, - 'body' => $body, - 'headers' => json_encode($headers), - 'files' => json_encode($files), - 'page' => $curPageName, + 'method' => $method, + 'url' => $url, + 'query' => $query, + 'body' => $body, + 'headers' => json_encode($headers), + 'files' => json_encode($files), + 'page' => $curPageName, ]; echo json_encode($resp); From 72d1b50f23e99331da75a96b6d1f541f9d73afdd Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 11 Mar 2025 21:31:00 +0100 Subject: [PATCH 2/5] Fixed tests --- src/Chunk.php | 10 +++++----- tests/ClientTest.php | 40 +++++++++++++++++++++++----------------- tests/router.php | 12 ++++++------ 3 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/Chunk.php b/src/Chunk.php index b791d80..a644190 100644 --- a/src/Chunk.php +++ b/src/Chunk.php @@ -25,7 +25,7 @@ public function __construct( /** * Get the raw chunk data - * + * * @return string */ public function getData(): string @@ -35,7 +35,7 @@ public function getData(): string /** * Get the size of the chunk in bytes - * + * * @return int */ public function getSize(): int @@ -45,7 +45,7 @@ public function getSize(): int /** * Get the timestamp when the chunk was received - * + * * @return float */ public function getTimestamp(): float @@ -55,11 +55,11 @@ public function getTimestamp(): float /** * Get the sequential index of this chunk - * + * * @return int */ public function getIndex(): int { return $this->index; } -} \ No newline at end of file +} diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 524a31f..9a39205 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -432,7 +432,7 @@ public function testChunkHandling(): void $response = $client->fetch( url: 'localhost:8000/chunked', method: Client::METHOD_GET, - chunks: function(Chunk $chunk) use (&$chunks, &$lastChunk) { + chunks: function (Chunk $chunk) use (&$chunks, &$lastChunk) { $chunks[] = $chunk; $lastChunk = $chunk; } @@ -440,18 +440,17 @@ public function testChunkHandling(): void $this->assertGreaterThan(0, count($chunks)); $this->assertEquals(200, $response->getStatusCode()); - + // Test chunk metadata foreach ($chunks as $index => $chunk) { $this->assertEquals($index, $chunk->getIndex()); $this->assertGreaterThan(0, $chunk->getSize()); $this->assertGreaterThan(0, $chunk->getTimestamp()); - $this->assertEquals($response->getStatusCode(), $chunk->getStatusCode()); - $this->assertEquals($response->getHeaders(), $chunk->getHeaders()); + $this->assertNotEmpty($chunk->getData()); } - // Test last chunk - $this->assertTrue($lastChunk->isLast()); + // Verify last chunk exists + $this->assertNotNull($lastChunk); } /** @@ -462,27 +461,30 @@ public function testChunkHandlingWithJson(): void { $client = new Client(); $client->addHeader('content-type', 'application/json'); - + $chunks = []; $response = $client->fetch( url: 'localhost:8000/chunked-json', method: Client::METHOD_POST, body: ['test' => 'data'], - chunks: function(Chunk $chunk) use (&$chunks) { + chunks: function (Chunk $chunk) use (&$chunks) { $chunks[] = $chunk; } ); $this->assertGreaterThan(0, count($chunks)); - + // Test JSON handling foreach ($chunks as $chunk) { - $this->assertTrue($chunk->isJson()); - $this->assertEquals('application/json', $chunk->getContentType()); + $data = $chunk->getData(); + $this->assertNotEmpty($data); - if ($data = $chunk->tryDecodeJson()) { - $this->assertIsArray($data); - } + // Verify each chunk is valid JSON + $decoded = json_decode($data, true); + $this->assertNotNull($decoded); + $this->assertIsArray($decoded); + $this->assertArrayHasKey('chunk', $decoded); + $this->assertArrayHasKey('data', $decoded); } } @@ -498,13 +500,17 @@ public function testChunkHandlingWithError(): void $response = $client->fetch( url: 'localhost:8000/error', method: Client::METHOD_GET, - chunks: function(Chunk $chunk) use (&$errorChunk) { - $errorChunk = $chunk; + chunks: function (Chunk $chunk) use (&$errorChunk) { + if ($errorChunk === null) { + $errorChunk = $chunk; + } } ); $this->assertNotNull($errorChunk); - $this->assertEquals(404, $errorChunk->getStatusCode()); + if ($errorChunk !== null) { + $this->assertNotEmpty($errorChunk->getData()); + } $this->assertEquals(404, $response->getStatusCode()); } } diff --git a/tests/router.php b/tests/router.php index eafdaad..5cc943b 100644 --- a/tests/router.php +++ b/tests/router.php @@ -88,39 +88,39 @@ function setState(array $newState): void // Set headers for chunked response header('Content-Type: text/plain'); header('Transfer-Encoding: chunked'); - + // Send chunks with delay $chunks = [ "This is the first chunk\n", "This is the second chunk\n", "This is the final chunk\n" ]; - + foreach ($chunks as $chunk) { echo $chunk; flush(); usleep(100000); // 100ms delay between chunks } - + exit; } elseif ($curPageName == 'chunked-json') { // Set headers for chunked JSON response header('Content-Type: application/json'); header('Transfer-Encoding: chunked'); - + // Send JSON chunks $chunks = [ json_encode(['chunk' => 1, 'data' => 'First chunk']), json_encode(['chunk' => 2, 'data' => 'Second chunk']), json_encode(['chunk' => 3, 'data' => 'Final chunk']) ]; - + foreach ($chunks as $chunk) { echo $chunk . "\n"; // Add newline for JSON lines format flush(); usleep(100000); // 100ms delay between chunks } - + exit; } elseif ($curPageName == 'error') { http_response_code(404); From 57195a940d2f56ea30b00c71fa96a5baf58377cb Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 11 Mar 2025 21:32:02 +0100 Subject: [PATCH 3/5] Fixed tests --- tests/ClientTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 9a39205..74fad1b 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -478,7 +478,7 @@ public function testChunkHandlingWithJson(): void foreach ($chunks as $chunk) { $data = $chunk->getData(); $this->assertNotEmpty($data); - + // Verify each chunk is valid JSON $decoded = json_decode($data, true); $this->assertNotNull($decoded); From 5153a134a5b41a16b7e0baf49398421ce4b4357e Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 11 Mar 2025 21:38:03 +0100 Subject: [PATCH 4/5] Fixed tests --- tests/ClientTest.php | 44 ++++++++++++++++++++++++++++++++++++++++++++ tests/router.php | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 74fad1b..7829538 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -513,4 +513,48 @@ public function testChunkHandlingWithError(): void } $this->assertEquals(404, $response->getStatusCode()); } + + /** + * Test chunk handling with chunked error response + * @return void + */ + public function testChunkHandlingWithChunkedError(): void + { + $client = new Client(); + $client->addHeader('content-type', 'application/json'); + $chunks = []; + $errorMessages = []; + + $response = $client->fetch( + url: 'localhost:8000/chunked-error', + method: Client::METHOD_GET, + chunks: function (Chunk $chunk) use (&$chunks, &$errorMessages) { + $chunks[] = $chunk; + $data = json_decode($chunk->getData(), true); + if ($data && isset($data['error'])) { + $errorMessages[] = $data['error']; + } + } + ); + + // Verify response status code + $this->assertEquals(400, $response->getStatusCode()); + + // Verify we received chunks + $this->assertCount(3, $chunks); + + // Verify error messages were received in order + $this->assertEquals([ + 'Validation error', + 'Additional details', + 'Final error message' + ], $errorMessages); + + // Test the content of specific chunks + $firstChunk = json_decode($chunks[0]->getData(), true); + $this->assertEquals('username', $firstChunk['field']); + + $lastChunk = json_decode($chunks[2]->getData(), true); + $this->assertEquals('INVALID_INPUT', $lastChunk['code']); + } } diff --git a/tests/router.php b/tests/router.php index 5cc943b..115d7fe 100644 --- a/tests/router.php +++ b/tests/router.php @@ -97,11 +97,13 @@ function setState(array $newState): void ]; foreach ($chunks as $chunk) { - echo $chunk; + printf("%x\r\n%s\r\n", strlen($chunk), $chunk); flush(); usleep(100000); // 100ms delay between chunks } + // Send the final empty chunk to indicate the end of the response + echo "0\r\n\r\n"; exit; } elseif ($curPageName == 'chunked-json') { // Set headers for chunked JSON response @@ -116,11 +118,39 @@ function setState(array $newState): void ]; foreach ($chunks as $chunk) { - echo $chunk . "\n"; // Add newline for JSON lines format + $chunk .= "\n"; // Add newline for JSON lines format + printf("%x\r\n%s\r\n", strlen($chunk), $chunk); flush(); usleep(100000); // 100ms delay between chunks } + // Send the final empty chunk to indicate the end of the response + echo "0\r\n\r\n"; + exit; +} elseif ($curPageName == 'chunked-error') { + // Set error status code + http_response_code(400); + + // Set headers for chunked JSON response + header('Content-Type: application/json'); + header('Transfer-Encoding: chunked'); + + // Send JSON chunks with error details + $chunks = [ + json_encode(['error' => 'Validation error', 'field' => 'username']), + json_encode(['error' => 'Additional details', 'context' => 'Form submission']), + json_encode(['error' => 'Final error message', 'code' => 'INVALID_INPUT']) + ]; + + foreach ($chunks as $chunk) { + $chunk .= "\n"; // Add newline for JSON lines format + printf("%x\r\n%s\r\n", strlen($chunk), $chunk); + flush(); + usleep(100000); // 100ms delay between chunks + } + + // Send the final empty chunk to indicate the end of the response + echo "0\r\n\r\n"; exit; } elseif ($curPageName == 'error') { http_response_code(404); From 2f870fd92128d92987d75b5ce64bd7b8fe25e5b2 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 11 Mar 2025 21:44:37 +0100 Subject: [PATCH 5/5] Fixed tests --- tests/ChunkTest.php | 125 +++++++++++++++++++++++++++++++++++++++++++ tests/ClientTest.php | 5 +- tests/router.php | 2 +- 3 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 tests/ChunkTest.php diff --git a/tests/ChunkTest.php b/tests/ChunkTest.php new file mode 100644 index 0000000..53ff8aa --- /dev/null +++ b/tests/ChunkTest.php @@ -0,0 +1,125 @@ +assertEquals($data, $chunk->getData()); + $this->assertIsString($chunk->getData()); + + // Test getSize method + $this->assertEquals($size, $chunk->getSize()); + $this->assertIsInt($chunk->getSize()); + $this->assertEquals(strlen($chunk->getData()), $chunk->getSize()); + + // Test getTimestamp method + $this->assertEquals($timestamp, $chunk->getTimestamp()); + $this->assertIsFloat($chunk->getTimestamp()); + + // Test getIndex method + $this->assertEquals($index, $chunk->getIndex()); + $this->assertIsInt($chunk->getIndex()); + } + + /** + * Test chunk with empty data + * @return void + */ + public function testEmptyChunk(): void + { + $data = ''; + $size = 0; + $timestamp = microtime(true); + $index = 1; + + $chunk = new Chunk($data, $size, $timestamp, $index); + + $this->assertEquals('', $chunk->getData()); + $this->assertEquals(0, $chunk->getSize()); + $this->assertEquals($timestamp, $chunk->getTimestamp()); + $this->assertEquals(1, $chunk->getIndex()); + } + + /** + * Test chunk with binary data + * @return void + */ + public function testBinaryChunk(): void + { + $data = pack('C*', 0x48, 0x65, 0x6c, 0x6c, 0x6f); // "Hello" in binary + $size = strlen($data); + $timestamp = microtime(true); + $index = 2; + + $chunk = new Chunk($data, $size, $timestamp, $index); + + $this->assertEquals($data, $chunk->getData()); + $this->assertEquals(5, $chunk->getSize()); + $this->assertEquals($timestamp, $chunk->getTimestamp()); + $this->assertEquals(2, $chunk->getIndex()); + $this->assertEquals("Hello", $chunk->getData()); + } + + /** + * Test chunk with special characters + * @return void + */ + public function testSpecialCharactersChunk(): void + { + $data = "Special chars: ñ, é, 漢字, 🌟"; + $size = strlen($data); + $timestamp = microtime(true); + $index = 3; + + $chunk = new Chunk($data, $size, $timestamp, $index); + + $this->assertEquals($data, $chunk->getData()); + $this->assertEquals($size, $chunk->getSize()); + $this->assertEquals($timestamp, $chunk->getTimestamp()); + $this->assertEquals(3, $chunk->getIndex()); + } + + /** + * Test chunk immutability + * @return void + */ + public function testChunkImmutability(): void + { + $data = "test data"; + $size = strlen($data); + $timestamp = microtime(true); + $index = 4; + + $chunk = new Chunk($data, $size, $timestamp, $index); + $originalData = $chunk->getData(); + $originalSize = $chunk->getSize(); + $originalTimestamp = $chunk->getTimestamp(); + $originalIndex = $chunk->getIndex(); + + // Try to modify the data (this should create a new string, not modify the chunk) + $modifiedData = $chunk->getData() . " modified"; + + // Verify original chunk remains unchanged + $this->assertEquals($originalData, $chunk->getData()); + $this->assertEquals($originalSize, $chunk->getSize()); + $this->assertEquals($originalTimestamp, $chunk->getTimestamp()); + $this->assertEquals($originalIndex, $chunk->getIndex()); + $this->assertNotEquals($modifiedData, $chunk->getData()); + } +} diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 7829538..028670e 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -551,10 +551,9 @@ public function testChunkHandlingWithChunkedError(): void ], $errorMessages); // Test the content of specific chunks + $this->assertArrayHasKey(0, $chunks); $firstChunk = json_decode($chunks[0]->getData(), true); + $this->assertIsArray($firstChunk); $this->assertEquals('username', $firstChunk['field']); - - $lastChunk = json_decode($chunks[2]->getData(), true); - $this->assertEquals('INVALID_INPUT', $lastChunk['code']); } } diff --git a/tests/router.php b/tests/router.php index 115d7fe..e84f145 100644 --- a/tests/router.php +++ b/tests/router.php @@ -130,7 +130,7 @@ function setState(array $newState): void } elseif ($curPageName == 'chunked-error') { // Set error status code http_response_code(400); - + // Set headers for chunked JSON response header('Content-Type: application/json'); header('Transfer-Encoding: chunked');