From d7da6238412588add36eef52bbe7df0992c51301 Mon Sep 17 00:00:00 2001 From: Vlad Shashkov Date: Thu, 7 Aug 2025 13:06:52 +0700 Subject: [PATCH 01/11] Added ConfiguratorSever --- src/ConfiguratorSever.php | 41 +++++++++++++++++++++++++++++++++++ src/PSR7Worker.php | 45 ++------------------------------------- 2 files changed, 43 insertions(+), 43 deletions(-) create mode 100644 src/ConfiguratorSever.php diff --git a/src/ConfiguratorSever.php b/src/ConfiguratorSever.php new file mode 100644 index 0000000..f204208 --- /dev/null +++ b/src/ConfiguratorSever.php @@ -0,0 +1,41 @@ + + */ + public function configure(Request $request): array + { + $_SERVER['REQUEST_URI'] = $request->uri; + $_SERVER['REQUEST_TIME'] = time(); + $_SERVER['REQUEST_TIME_FLOAT'] = microtime(true); + $_SERVER['REMOTE_ADDR'] = $request->getRemoteAddr(); + $_SERVER['REQUEST_METHOD'] = $request->method; + $_SERVER['HTTP_USER_AGENT'] = ''; + + foreach ($request->headers as $key => $value) { + $key = \strtoupper(\str_replace('-', '_', $key)); + + if (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH'])) { + $_SERVER[$key] = \implode(', ', $value); + + continue; + } + + $_SERVER['HTTP_' . $key] = \implode(', ', $value); + } + + return $_SERVER; + } +} diff --git a/src/PSR7Worker.php b/src/PSR7Worker.php index 209beea..c5984cb 100644 --- a/src/PSR7Worker.php +++ b/src/PSR7Worker.php @@ -32,8 +32,6 @@ class PSR7Worker implements PSR7WorkerInterface private readonly HttpWorker $httpWorker; - private readonly array $originalServer; - /** * @var string[] Valid values for HTTP protocol version */ @@ -44,9 +42,9 @@ public function __construct( private readonly ServerRequestFactoryInterface $requestFactory, private readonly StreamFactoryInterface $streamFactory, private readonly UploadedFileFactoryInterface $uploadsFactory, + private readonly ConfiguratorSever $configuratorServer = new ConfiguratorSever(), ) { $this->httpWorker = new HttpWorker($worker); - $this->originalServer = $_SERVER; } public function getWorker(): WorkerInterface @@ -69,7 +67,7 @@ public function waitRequest(): ?ServerRequestInterface return null; } - $_SERVER = $this->configureServer($httpRequest); + $_SERVER = $this->configuratorServer->configure($httpRequest); return $this->mapRequest($httpRequest, $_SERVER); } @@ -117,45 +115,6 @@ private function streamToGenerator(StreamInterface $stream): Generator } } - /** - * Returns altered copy of _SERVER variable. Sets ip-address, - * request-time and other values. - * - * @return non-empty-array - */ - protected function configureServer(Request $request): array - { - $server = $this->originalServer; - - $server['REQUEST_URI'] = $request->uri; - $server['REQUEST_TIME'] = $this->timeInt(); - $server['REQUEST_TIME_FLOAT'] = $this->timeFloat(); - $server['REMOTE_ADDR'] = $request->getRemoteAddr(); - $server['REQUEST_METHOD'] = $request->method; - - $server['HTTP_USER_AGENT'] = ''; - foreach ($request->headers as $key => $value) { - $key = \strtoupper(\str_replace('-', '_', $key)); - if (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH'])) { - $server[$key] = \implode(', ', $value); - } else { - $server['HTTP_' . $key] = \implode(', ', $value); - } - } - - return $server; - } - - protected function timeInt(): int - { - return \time(); - } - - protected function timeFloat(): float - { - return \microtime(true); - } - /** * @throws \JsonException */ From a65e90d45ab80d4bfcabae7f212b2fccaa5741c9 Mon Sep 17 00:00:00 2001 From: Vlad Shashkov Date: Thu, 7 Aug 2025 13:11:55 +0700 Subject: [PATCH 02/11] Fixed naming --- src/{ConfiguratorSever.php => ConfiguratorServer.php} | 2 +- src/PSR7Worker.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/{ConfiguratorSever.php => ConfiguratorServer.php} (97%) diff --git a/src/ConfiguratorSever.php b/src/ConfiguratorServer.php similarity index 97% rename from src/ConfiguratorSever.php rename to src/ConfiguratorServer.php index f204208..82c5d11 100644 --- a/src/ConfiguratorSever.php +++ b/src/ConfiguratorServer.php @@ -7,7 +7,7 @@ use function time; use function microtime; -class ConfiguratorSever +class ConfiguratorServer { /** * Returns altered copy of _SERVER variable. Sets ip-address, diff --git a/src/PSR7Worker.php b/src/PSR7Worker.php index c5984cb..342cec4 100644 --- a/src/PSR7Worker.php +++ b/src/PSR7Worker.php @@ -42,7 +42,7 @@ public function __construct( private readonly ServerRequestFactoryInterface $requestFactory, private readonly StreamFactoryInterface $streamFactory, private readonly UploadedFileFactoryInterface $uploadsFactory, - private readonly ConfiguratorSever $configuratorServer = new ConfiguratorSever(), + private readonly ConfiguratorServer $configuratorServer = new ConfiguratorServer(), ) { $this->httpWorker = new HttpWorker($worker); } From bd6ad41970d1ba9a6f64a2843fe70b2eb1ef33f8 Mon Sep 17 00:00:00 2001 From: Vlad Shashkov Date: Thu, 7 Aug 2025 16:40:47 +0700 Subject: [PATCH 03/11] Fixing replacement code review --- ...ConfiguratorServer.php => GlobalState.php} | 22 +++++++-------- src/PSR7Worker.php | 27 +++++++++++++++++-- 2 files changed, 35 insertions(+), 14 deletions(-) rename src/{ConfiguratorServer.php => GlobalState.php} (51%) diff --git a/src/ConfiguratorServer.php b/src/GlobalState.php similarity index 51% rename from src/ConfiguratorServer.php rename to src/GlobalState.php index 82c5d11..3f9b462 100644 --- a/src/ConfiguratorServer.php +++ b/src/GlobalState.php @@ -6,16 +6,16 @@ use function time; use function microtime; +use function strtoupper; +use function str_replace; +use function implode; -class ConfiguratorServer +final class GlobalState { /** - * Returns altered copy of _SERVER variable. Sets ip-address, - * request-time and other values. - * - * @return non-empty-array + * Sets ip-address, request-time and other values. */ - public function configure(Request $request): array + public static function populateServer(Request $request): void { $_SERVER['REQUEST_URI'] = $request->uri; $_SERVER['REQUEST_TIME'] = time(); @@ -25,17 +25,15 @@ public function configure(Request $request): array $_SERVER['HTTP_USER_AGENT'] = ''; foreach ($request->headers as $key => $value) { - $key = \strtoupper(\str_replace('-', '_', $key)); + $key = strtoupper(str_replace('-', '_', $key)); - if (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH'])) { - $_SERVER[$key] = \implode(', ', $value); + if ($key == 'CONTENT_TYPE' || $key == 'CONTENT_LENGTH') { + $_SERVER[$key] = implode(', ', $value); continue; } - $_SERVER['HTTP_' . $key] = \implode(', ', $value); + $_SERVER['HTTP_' . $key] = implode(', ', $value); } - - return $_SERVER; } } diff --git a/src/PSR7Worker.php b/src/PSR7Worker.php index 342cec4..8b72440 100644 --- a/src/PSR7Worker.php +++ b/src/PSR7Worker.php @@ -32,6 +32,7 @@ class PSR7Worker implements PSR7WorkerInterface private readonly HttpWorker $httpWorker; + /** * @var string[] Valid values for HTTP protocol version */ @@ -42,7 +43,6 @@ public function __construct( private readonly ServerRequestFactoryInterface $requestFactory, private readonly StreamFactoryInterface $streamFactory, private readonly UploadedFileFactoryInterface $uploadsFactory, - private readonly ConfiguratorServer $configuratorServer = new ConfiguratorServer(), ) { $this->httpWorker = new HttpWorker($worker); } @@ -67,7 +67,7 @@ public function waitRequest(): ?ServerRequestInterface return null; } - $_SERVER = $this->configuratorServer->configure($httpRequest); + $_SERVER = $this->configureServer($httpRequest); return $this->mapRequest($httpRequest, $_SERVER); } @@ -115,6 +115,29 @@ private function streamToGenerator(StreamInterface $stream): Generator } } + /** + * Returns altered copy of _SERVER variable. Sets ip-address, + * request-time and other values. + * + * @return non-empty-array + */ + protected function configureServer(Request $request): array + { + GlobalState::populateServer($request); + + return $_SERVER; + } + + protected function timeInt(): int + { + return \time(); + } + + protected function timeFloat(): float + { + return \microtime(true); + } + /** * @throws \JsonException */ From 06729f93b4a3eeef785161343b1a319a9b15a6d5 Mon Sep 17 00:00:00 2001 From: Vlad Shashkov Date: Thu, 7 Aug 2025 16:45:02 +0700 Subject: [PATCH 04/11] Added deprecated --- src/PSR7Worker.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/PSR7Worker.php b/src/PSR7Worker.php index 8b72440..f79ceba 100644 --- a/src/PSR7Worker.php +++ b/src/PSR7Worker.php @@ -128,11 +128,19 @@ protected function configureServer(Request $request): array return $_SERVER; } + + /** + * @deprecated + */ protected function timeInt(): int { return \time(); } + + /** + * @deprecated + */ protected function timeFloat(): float { return \microtime(true); From 740c24ae76ae5de57383cea60a8490a7c14e3e1c Mon Sep 17 00:00:00 2001 From: Vlad Shashkov Date: Sat, 9 Aug 2025 03:06:42 +0700 Subject: [PATCH 05/11] Refactoring global state --- src/GlobalState.php | 28 ++++++++++++++++-------- src/HttpWorker.php | 1 + src/PSR7Worker.php | 6 +++--- tests/Unit/PSR7WorkerTest.php | 40 ++++++++++++++++++++++++++++++++--- 4 files changed, 60 insertions(+), 15 deletions(-) diff --git a/src/GlobalState.php b/src/GlobalState.php index 3f9b462..a2f8d90 100644 --- a/src/GlobalState.php +++ b/src/GlobalState.php @@ -14,26 +14,36 @@ final class GlobalState { /** * Sets ip-address, request-time and other values. + * + * @return non-empty-array */ - public static function populateServer(Request $request): void + public static function populateServer(Request $request): array { - $_SERVER['REQUEST_URI'] = $request->uri; - $_SERVER['REQUEST_TIME'] = time(); - $_SERVER['REQUEST_TIME_FLOAT'] = microtime(true); - $_SERVER['REMOTE_ADDR'] = $request->getRemoteAddr(); - $_SERVER['REQUEST_METHOD'] = $request->method; - $_SERVER['HTTP_USER_AGENT'] = ''; + static $server = []; + + if ([] == $server) { + $server = $_SERVER; + } + + $server['REQUEST_URI'] = $request->uri; + $server['REQUEST_TIME'] = time(); + $server['REQUEST_TIME_FLOAT'] = microtime(true); + $server['REMOTE_ADDR'] = $request->getRemoteAddr(); + $server['REQUEST_METHOD'] = $request->method; + $server['HTTP_USER_AGENT'] = ''; foreach ($request->headers as $key => $value) { $key = strtoupper(str_replace('-', '_', $key)); if ($key == 'CONTENT_TYPE' || $key == 'CONTENT_LENGTH') { - $_SERVER[$key] = implode(', ', $value); + $server[$key] = implode(', ', $value); continue; } - $_SERVER['HTTP_' . $key] = implode(', ', $value); + $server['HTTP_' . $key] = implode(', ', $value); } + + return $server; } } diff --git a/src/HttpWorker.php b/src/HttpWorker.php index e82c1bb..77b94ab 100644 --- a/src/HttpWorker.php +++ b/src/HttpWorker.php @@ -76,6 +76,7 @@ public function waitRequest(): ?Request /** @var RequestContext $context */ $context = \json_decode($payload->header, true, 512, \JSON_THROW_ON_ERROR); + return $this->arrayToRequest($payload->body, $context); } diff --git a/src/PSR7Worker.php b/src/PSR7Worker.php index f79ceba..be86b31 100644 --- a/src/PSR7Worker.php +++ b/src/PSR7Worker.php @@ -119,13 +119,13 @@ private function streamToGenerator(StreamInterface $stream): Generator * Returns altered copy of _SERVER variable. Sets ip-address, * request-time and other values. * + * @deprecated + * * @return non-empty-array */ protected function configureServer(Request $request): array { - GlobalState::populateServer($request); - - return $_SERVER; + return GlobalState::populateServer($request); } diff --git a/tests/Unit/PSR7WorkerTest.php b/tests/Unit/PSR7WorkerTest.php index c79d52b..a5792b8 100644 --- a/tests/Unit/PSR7WorkerTest.php +++ b/tests/Unit/PSR7WorkerTest.php @@ -6,26 +6,60 @@ use Nyholm\Psr7\Factory\Psr17Factory; use PHPUnit\Framework\TestCase; +use Spiral\Goridge\Frame; use Spiral\RoadRunner\Http\HttpWorker; use Spiral\RoadRunner\Http\PSR7Worker; +use Spiral\RoadRunner\Tests\Http\Unit\Stub\TestRelay; use Spiral\RoadRunner\Worker; final class PSR7WorkerTest extends TestCase { - public function testHttpWorkerIsAvailable(): void + /** + * @param array $headers + * + * @dataProvider testStateLeakDataProvider + */ + public function testStateLeak(array $headers): void { $psrFactory = new Psr17Factory(); + $relay = new TestRelay(); + + $body = [ + 'headers' => $headers, + 'rawQuery' => '', + 'remoteAddr' => '127.0.0.1', + 'protocol' => 'HTTP/1.1', + 'method' => 'GET', + 'uri' => 'http://localhost', + 'parsed' => false, + ]; + + $head = (string)\json_encode($body, \JSON_THROW_ON_ERROR); + $frame = new Frame($head .'test', [\strlen($head)]); + + $relay->addFrames($frame); $psrWorker = new PSR7Worker( - Worker::create(), + new Worker($relay), $psrFactory, $psrFactory, $psrFactory, ); - self::assertInstanceOf(HttpWorker::class, $psrWorker->getHttpWorker()); + $psrWorker->waitRequest(); + + + var_dump($_SERVER); } + + public static function testStateLeakDataProvider(): iterable + { + yield [['Content-Type' => ['application/json'], 'Accept' => ['application/html']]]; + yield [['Content-Type' => ['application/json']]]; + } + + protected function tearDown(): void { // Clean all extra output buffers From 4452b37c7409b11210d9d150d646d390852ded09 Mon Sep 17 00:00:00 2001 From: Vlad Shashkov Date: Sat, 9 Aug 2025 05:16:26 +0700 Subject: [PATCH 06/11] Fixed leak state server --- composer.json | 5 +-- src/GlobalState.php | 26 +++++++------- tests/Unit/PSR7WorkerTest.php | 66 +++++++++++++++++++---------------- 3 files changed, 52 insertions(+), 45 deletions(-) diff --git a/composer.json b/composer.json index 10b034a..de028ce 100644 --- a/composer.json +++ b/composer.json @@ -41,10 +41,11 @@ "ext-json": "*", "psr/http-factory": "^1.0.1", "psr/http-message": "^1.0.1 || ^2.0", + "roadrunner-php/roadrunner-api-dto": "^1.6", "spiral/roadrunner": "^2023.3 || ^2024.1 || ^2025.1", "spiral/roadrunner-worker": "^3.5", - "roadrunner-php/roadrunner-api-dto": "^1.6", - "symfony/polyfill-php83": "^1.29" + "symfony/polyfill-php83": "^1.29", + "symfony/var-dumper": "^6.4" }, "require-dev": { "jetbrains/phpstorm-attributes": "^1.0", diff --git a/src/GlobalState.php b/src/GlobalState.php index a2f8d90..314f28e 100644 --- a/src/GlobalState.php +++ b/src/GlobalState.php @@ -19,31 +19,33 @@ final class GlobalState */ public static function populateServer(Request $request): array { - static $server = []; + static $originalServer = null; - if ([] == $server) { - $server = $_SERVER; + if ($originalServer == null) { + $originalServer = $_SERVER; } - $server['REQUEST_URI'] = $request->uri; - $server['REQUEST_TIME'] = time(); - $server['REQUEST_TIME_FLOAT'] = microtime(true); - $server['REMOTE_ADDR'] = $request->getRemoteAddr(); - $server['REQUEST_METHOD'] = $request->method; - $server['HTTP_USER_AGENT'] = ''; + $newServer = $originalServer; + + $newServer['REQUEST_URI'] = $request->uri; + $newServer['REQUEST_TIME'] = time(); + $newServer['REQUEST_TIME_FLOAT'] = microtime(true); + $newServer['REMOTE_ADDR'] = $request->getRemoteAddr(); + $newServer['REQUEST_METHOD'] = $request->method; + $newServer['HTTP_USER_AGENT'] = ''; foreach ($request->headers as $key => $value) { $key = strtoupper(str_replace('-', '_', $key)); if ($key == 'CONTENT_TYPE' || $key == 'CONTENT_LENGTH') { - $server[$key] = implode(', ', $value); + $newServer[$key] = implode(', ', $value); continue; } - $server['HTTP_' . $key] = implode(', ', $value); + $newServer['HTTP_' . $key] = implode(', ', $value); } - return $server; + return $newServer; } } diff --git a/tests/Unit/PSR7WorkerTest.php b/tests/Unit/PSR7WorkerTest.php index a5792b8..a46b690 100644 --- a/tests/Unit/PSR7WorkerTest.php +++ b/tests/Unit/PSR7WorkerTest.php @@ -2,61 +2,65 @@ declare(strict_types=1); -namespace Spiral\RoadRunner\Tests\Http\Unit; - use Nyholm\Psr7\Factory\Psr17Factory; +use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Spiral\Goridge\Frame; -use Spiral\RoadRunner\Http\HttpWorker; +use Spiral\RoadRunner\Http\GlobalState; use Spiral\RoadRunner\Http\PSR7Worker; use Spiral\RoadRunner\Tests\Http\Unit\Stub\TestRelay; use Spiral\RoadRunner\Worker; + +#[CoversClass(PSR7Worker::class)] +#[CoversClass(GlobalState::class)] final class PSR7WorkerTest extends TestCase { - /** + /*** * @param array $headers - * - * @dataProvider testStateLeakDataProvider */ - public function testStateLeak(array $headers): void + public function testStateServerLeak(): void { $psrFactory = new Psr17Factory(); $relay = new TestRelay(); - - $body = [ - 'headers' => $headers, - 'rawQuery' => '', - 'remoteAddr' => '127.0.0.1', - 'protocol' => 'HTTP/1.1', - 'method' => 'GET', - 'uri' => 'http://localhost', - 'parsed' => false, - ]; - - $head = (string)\json_encode($body, \JSON_THROW_ON_ERROR); - $frame = new Frame($head .'test', [\strlen($head)]); - - $relay->addFrames($frame); - - $psrWorker = new PSR7Worker( + $psrWorker = new PSR7Worker( new Worker($relay), $psrFactory, $psrFactory, $psrFactory, ); - $psrWorker->waitRequest(); + //dataProvider is always random and we need to keep the order + $fixtures = [ + [['Content-Type' => ['application/html'], 'Connection' => ['keep-alive']], ['REQUEST_URI' => 'http://localhost', 'REMOTE_ADDR' => '127.0.0.1', 'REQUEST_METHOD' => 'GET', 'HTTP_USER_AGENT' => '', 'CONTENT_TYPE' => 'application/html', 'HTTP_CONNECTION' => 'keep-alive',]], + [['Content-Type' => ['application/json']], ['REQUEST_URI' => 'http://localhost', 'REMOTE_ADDR' => '127.0.0.1', 'REQUEST_METHOD' => 'GET', 'HTTP_USER_AGENT' => '', 'CONTENT_TYPE' => 'application/json']], + ]; + foreach ($fixtures as [$headers, $expectedServer]) { + $body = [ + 'headers' => $headers, + 'rawQuery' => '', + 'remoteAddr' => '127.0.0.1', + 'protocol' => 'HTTP/1.1', + 'method' => 'GET', + 'uri' => 'http://localhost', + 'parsed' => false, + ]; - var_dump($_SERVER); - } + $head = (string)\json_encode($body, \JSON_THROW_ON_ERROR); + $frame = new Frame($head .'test', [\strlen($head)]); + $relay->addFrames($frame); - public static function testStateLeakDataProvider(): iterable - { - yield [['Content-Type' => ['application/json'], 'Accept' => ['application/html']]]; - yield [['Content-Type' => ['application/json']]]; + $_SERVER = []; + + $psrWorker->waitRequest(); + + unset($_SERVER['REQUEST_TIME']); + unset($_SERVER['REQUEST_TIME_FLOAT']); + + self::assertEquals($expectedServer, $_SERVER); + } } From 8cdf0b52ff209e90a9e959e8853b3deb19b6e9a7 Mon Sep 17 00:00:00 2001 From: Vlad Shashkov Date: Sat, 9 Aug 2025 05:17:23 +0700 Subject: [PATCH 07/11] Fixed leak state server --- src/HttpWorker.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/HttpWorker.php b/src/HttpWorker.php index 77b94ab..e82c1bb 100644 --- a/src/HttpWorker.php +++ b/src/HttpWorker.php @@ -76,7 +76,6 @@ public function waitRequest(): ?Request /** @var RequestContext $context */ $context = \json_decode($payload->header, true, 512, \JSON_THROW_ON_ERROR); - return $this->arrayToRequest($payload->body, $context); } From 6ac9a9c2b7776591848e3224a926382639f991b9 Mon Sep 17 00:00:00 2001 From: Vlad Shashkov Date: Sat, 9 Aug 2025 05:22:03 +0700 Subject: [PATCH 08/11] Rollback composer.json --- composer.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index de028ce..10b034a 100644 --- a/composer.json +++ b/composer.json @@ -41,11 +41,10 @@ "ext-json": "*", "psr/http-factory": "^1.0.1", "psr/http-message": "^1.0.1 || ^2.0", - "roadrunner-php/roadrunner-api-dto": "^1.6", "spiral/roadrunner": "^2023.3 || ^2024.1 || ^2025.1", "spiral/roadrunner-worker": "^3.5", - "symfony/polyfill-php83": "^1.29", - "symfony/var-dumper": "^6.4" + "roadrunner-php/roadrunner-api-dto": "^1.6", + "symfony/polyfill-php83": "^1.29" }, "require-dev": { "jetbrains/phpstorm-attributes": "^1.0", From 3ececb80e6b67be1a75f12baaa4aa740d7b3c2d3 Mon Sep 17 00:00:00 2001 From: Vlad Shashkov Date: Mon, 11 Aug 2025 21:21:10 +0700 Subject: [PATCH 09/11] Fixed pslam & tests --- src/GlobalState.php | 3 ++- src/PSR7Worker.php | 2 ++ tests/Unit/PSR7WorkerTest.php | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/GlobalState.php b/src/GlobalState.php index 314f28e..571593d 100644 --- a/src/GlobalState.php +++ b/src/GlobalState.php @@ -15,10 +15,11 @@ final class GlobalState /** * Sets ip-address, request-time and other values. * - * @return non-empty-array + * @return non-empty-array */ public static function populateServer(Request $request): array { + /** @var non-empty-array|null $originalServer */ static $originalServer = null; if ($originalServer == null) { diff --git a/src/PSR7Worker.php b/src/PSR7Worker.php index be86b31..0217de9 100644 --- a/src/PSR7Worker.php +++ b/src/PSR7Worker.php @@ -58,6 +58,8 @@ public function getHttpWorker(): HttpWorker } /** + * @psalm-suppress DeprecatedMethod + * * @throws \JsonException */ public function waitRequest(): ?ServerRequestInterface diff --git a/tests/Unit/PSR7WorkerTest.php b/tests/Unit/PSR7WorkerTest.php index a46b690..b99cb7e 100644 --- a/tests/Unit/PSR7WorkerTest.php +++ b/tests/Unit/PSR7WorkerTest.php @@ -4,6 +4,7 @@ use Nyholm\Psr7\Factory\Psr17Factory; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\RunClassInSeparateProcess; use PHPUnit\Framework\TestCase; use Spiral\Goridge\Frame; use Spiral\RoadRunner\Http\GlobalState; @@ -14,6 +15,7 @@ #[CoversClass(PSR7Worker::class)] #[CoversClass(GlobalState::class)] +#[RunClassInSeparateProcess] final class PSR7WorkerTest extends TestCase { /*** From b439591f21987dd71263c2cebc415b372c33919d Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Sun, 31 Aug 2025 15:52:35 +0400 Subject: [PATCH 10/11] Fix test --- tests/Unit/PSR7WorkerTest.php | 36 ++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/tests/Unit/PSR7WorkerTest.php b/tests/Unit/PSR7WorkerTest.php index b99cb7e..2cbf17e 100644 --- a/tests/Unit/PSR7WorkerTest.php +++ b/tests/Unit/PSR7WorkerTest.php @@ -2,6 +2,8 @@ declare(strict_types=1); +namespace Spiral\RoadRunner\Tests\Http\Unit; + use Nyholm\Psr7\Factory\Psr17Factory; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\RunClassInSeparateProcess; @@ -18,9 +20,6 @@ #[RunClassInSeparateProcess] final class PSR7WorkerTest extends TestCase { - /*** - * @param array $headers - */ public function testStateServerLeak(): void { $psrFactory = new Psr17Factory(); @@ -34,10 +33,35 @@ public function testStateServerLeak(): void //dataProvider is always random and we need to keep the order $fixtures = [ - [['Content-Type' => ['application/html'], 'Connection' => ['keep-alive']], ['REQUEST_URI' => 'http://localhost', 'REMOTE_ADDR' => '127.0.0.1', 'REQUEST_METHOD' => 'GET', 'HTTP_USER_AGENT' => '', 'CONTENT_TYPE' => 'application/html', 'HTTP_CONNECTION' => 'keep-alive',]], - [['Content-Type' => ['application/json']], ['REQUEST_URI' => 'http://localhost', 'REMOTE_ADDR' => '127.0.0.1', 'REQUEST_METHOD' => 'GET', 'HTTP_USER_AGENT' => '', 'CONTENT_TYPE' => 'application/json']], + [ + [ + 'Content-Type' => ['application/html'], + 'Connection' => ['keep-alive'] + ], + [ + 'REQUEST_URI' => 'http://localhost', + 'REMOTE_ADDR' => '127.0.0.1', + 'REQUEST_METHOD' => 'GET', + 'HTTP_USER_AGENT' => '', + 'CONTENT_TYPE' => 'application/html', + 'HTTP_CONNECTION' => 'keep-alive', + ], + ], + [ + [ + 'Content-Type' => ['application/json'] + ], + [ + 'REQUEST_URI' => 'http://localhost', + 'REMOTE_ADDR' => '127.0.0.1', + 'REQUEST_METHOD' => 'GET', + 'HTTP_USER_AGENT' => '', + 'CONTENT_TYPE' => 'application/json' + ], + ], ]; + $_SERVER = []; foreach ($fixtures as [$headers, $expectedServer]) { $body = [ 'headers' => $headers, @@ -54,8 +78,6 @@ public function testStateServerLeak(): void $relay->addFrames($frame); - $_SERVER = []; - $psrWorker->waitRequest(); unset($_SERVER['REQUEST_TIME']); From a734671f37ad636427cfbebd80aa31e92d7ec2bc Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Sun, 31 Aug 2025 16:09:55 +0400 Subject: [PATCH 11/11] Refactor GlobalState and PSR7Worker to cache and enrich $_SERVER variables --- composer.json | 1 + src/GlobalState.php | 48 ++++++++++++++++++++++++++------------------- src/PSR7Worker.php | 20 ++++++++++--------- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/composer.json b/composer.json index 10b034a..2459f2d 100644 --- a/composer.json +++ b/composer.json @@ -50,6 +50,7 @@ "jetbrains/phpstorm-attributes": "^1.0", "nyholm/psr7": "^1.3", "phpunit/phpunit": "^10.0", + "spiral/dumper": "^3.3", "symfony/process": "^6.2 || ^7.0", "vimeo/psalm": "^5.9" }, diff --git a/src/GlobalState.php b/src/GlobalState.php index 571593d..04f87ed 100644 --- a/src/GlobalState.php +++ b/src/GlobalState.php @@ -13,40 +13,48 @@ final class GlobalState { /** - * Sets ip-address, request-time and other values. - * - * @return non-empty-array + * @var array Cached state of the $_SERVER superglobal. */ - public static function populateServer(Request $request): array - { - /** @var non-empty-array|null $originalServer */ - static $originalServer = null; + private static array $cachedServer = []; - if ($originalServer == null) { - $originalServer = $_SERVER; - } + /** + * Cache superglobal $_SERVER to avoid state leaks between requests. + */ + public static function cacheServerVars(): void + { + self::$cachedServer = $_SERVER; + } - $newServer = $originalServer; + /** + * Enrich cached $_SERVER with data from the request. + * + * @return non-empty-array Cached $_SERVER data enriched with request data. + */ + public static function enrichServerVars(Request $request): array + { + $server = self::$cachedServer; - $newServer['REQUEST_URI'] = $request->uri; - $newServer['REQUEST_TIME'] = time(); - $newServer['REQUEST_TIME_FLOAT'] = microtime(true); - $newServer['REMOTE_ADDR'] = $request->getRemoteAddr(); - $newServer['REQUEST_METHOD'] = $request->method; - $newServer['HTTP_USER_AGENT'] = ''; + $server['REQUEST_URI'] = $request->uri; + $server['REQUEST_TIME'] = time(); + $server['REQUEST_TIME_FLOAT'] = microtime(true); + $server['REMOTE_ADDR'] = $request->getRemoteAddr(); + $server['REQUEST_METHOD'] = $request->method; + $server['HTTP_USER_AGENT'] = ''; foreach ($request->headers as $key => $value) { $key = strtoupper(str_replace('-', '_', $key)); if ($key == 'CONTENT_TYPE' || $key == 'CONTENT_LENGTH') { - $newServer[$key] = implode(', ', $value); + $server[$key] = implode(', ', $value); continue; } - $newServer['HTTP_' . $key] = implode(', ', $value); + $server['HTTP_' . $key] = implode(', ', $value); } - return $newServer; + return $server; } } + +GlobalState::cacheServerVars(); diff --git a/src/PSR7Worker.php b/src/PSR7Worker.php index 0217de9..43569b6 100644 --- a/src/PSR7Worker.php +++ b/src/PSR7Worker.php @@ -60,18 +60,24 @@ public function getHttpWorker(): HttpWorker /** * @psalm-suppress DeprecatedMethod * + * @param bool $populateServer Whether to populate $_SERVER superglobal. + * * @throws \JsonException */ - public function waitRequest(): ?ServerRequestInterface + public function waitRequest(bool $populateServer = true): ?ServerRequestInterface { $httpRequest = $this->httpWorker->waitRequest(); if ($httpRequest === null) { return null; } - $_SERVER = $this->configureServer($httpRequest); + $vars = $this->configureServer($httpRequest); + + if ($populateServer) { + $_SERVER = $vars; + } - return $this->mapRequest($httpRequest, $_SERVER); + return $this->mapRequest($httpRequest, $vars); } /** @@ -92,7 +98,7 @@ public function respond(ResponseInterface $response): void /** * @return Generator Compatible - * with {@see \Spiral\RoadRunner\Http\HttpWorker::respondStream()}. + * with {@see HttpWorker::respondStream}. */ private function streamToGenerator(StreamInterface $stream): Generator { @@ -121,16 +127,13 @@ private function streamToGenerator(StreamInterface $stream): Generator * Returns altered copy of _SERVER variable. Sets ip-address, * request-time and other values. * - * @deprecated - * * @return non-empty-array */ protected function configureServer(Request $request): array { - return GlobalState::populateServer($request); + return GlobalState::enrichServerVars($request); } - /** * @deprecated */ @@ -139,7 +142,6 @@ protected function timeInt(): int return \time(); } - /** * @deprecated */