Skip to content

Commit 8578a87

Browse files
authored
Added GlobalState (#28)
Co-authored-by: roxblnfk <[email protected]>
2 parents c00ab7a + a734671 commit 8578a87

File tree

4 files changed

+148
-31
lines changed

4 files changed

+148
-31
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"jetbrains/phpstorm-attributes": "^1.0",
5151
"nyholm/psr7": "^1.3",
5252
"phpunit/phpunit": "^10.0",
53+
"spiral/dumper": "^3.3",
5354
"symfony/process": "^6.2 || ^7.0",
5455
"vimeo/psalm": "^5.9"
5556
},

src/GlobalState.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Spiral\RoadRunner\Http;
6+
7+
use function time;
8+
use function microtime;
9+
use function strtoupper;
10+
use function str_replace;
11+
use function implode;
12+
13+
final class GlobalState
14+
{
15+
/**
16+
* @var array<array-key, mixed> Cached state of the $_SERVER superglobal.
17+
*/
18+
private static array $cachedServer = [];
19+
20+
/**
21+
* Cache superglobal $_SERVER to avoid state leaks between requests.
22+
*/
23+
public static function cacheServerVars(): void
24+
{
25+
self::$cachedServer = $_SERVER;
26+
}
27+
28+
/**
29+
* Enrich cached $_SERVER with data from the request.
30+
*
31+
* @return non-empty-array<array-key, mixed> Cached $_SERVER data enriched with request data.
32+
*/
33+
public static function enrichServerVars(Request $request): array
34+
{
35+
$server = self::$cachedServer;
36+
37+
$server['REQUEST_URI'] = $request->uri;
38+
$server['REQUEST_TIME'] = time();
39+
$server['REQUEST_TIME_FLOAT'] = microtime(true);
40+
$server['REMOTE_ADDR'] = $request->getRemoteAddr();
41+
$server['REQUEST_METHOD'] = $request->method;
42+
$server['HTTP_USER_AGENT'] = '';
43+
44+
foreach ($request->headers as $key => $value) {
45+
$key = strtoupper(str_replace('-', '_', $key));
46+
47+
if ($key == 'CONTENT_TYPE' || $key == 'CONTENT_LENGTH') {
48+
$server[$key] = implode(', ', $value);
49+
50+
continue;
51+
}
52+
53+
$server['HTTP_' . $key] = implode(', ', $value);
54+
}
55+
56+
return $server;
57+
}
58+
}
59+
60+
GlobalState::cacheServerVars();

src/PSR7Worker.php

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ class PSR7Worker implements PSR7WorkerInterface
3232

3333
private readonly HttpWorker $httpWorker;
3434

35-
private readonly array $originalServer;
3635

3736
/**
3837
* @var string[] Valid values for HTTP protocol version
@@ -46,7 +45,6 @@ public function __construct(
4645
private readonly UploadedFileFactoryInterface $uploadsFactory,
4746
) {
4847
$this->httpWorker = new HttpWorker($worker);
49-
$this->originalServer = $_SERVER;
5048
}
5149

5250
public function getWorker(): WorkerInterface
@@ -60,18 +58,26 @@ public function getHttpWorker(): HttpWorker
6058
}
6159

6260
/**
61+
* @psalm-suppress DeprecatedMethod
62+
*
63+
* @param bool $populateServer Whether to populate $_SERVER superglobal.
64+
*
6365
* @throws \JsonException
6466
*/
65-
public function waitRequest(): ?ServerRequestInterface
67+
public function waitRequest(bool $populateServer = true): ?ServerRequestInterface
6668
{
6769
$httpRequest = $this->httpWorker->waitRequest();
6870
if ($httpRequest === null) {
6971
return null;
7072
}
7173

72-
$_SERVER = $this->configureServer($httpRequest);
74+
$vars = $this->configureServer($httpRequest);
75+
76+
if ($populateServer) {
77+
$_SERVER = $vars;
78+
}
7379

74-
return $this->mapRequest($httpRequest, $_SERVER);
80+
return $this->mapRequest($httpRequest, $vars);
7581
}
7682

7783
/**
@@ -92,7 +98,7 @@ public function respond(ResponseInterface $response): void
9298

9399
/**
94100
* @return Generator<mixed, scalar|Stringable, mixed, Stringable|scalar|null> Compatible
95-
* with {@see \Spiral\RoadRunner\Http\HttpWorker::respondStream()}.
101+
* with {@see HttpWorker::respondStream}.
96102
*/
97103
private function streamToGenerator(StreamInterface $stream): Generator
98104
{
@@ -125,32 +131,20 @@ private function streamToGenerator(StreamInterface $stream): Generator
125131
*/
126132
protected function configureServer(Request $request): array
127133
{
128-
$server = $this->originalServer;
129-
130-
$server['REQUEST_URI'] = $request->uri;
131-
$server['REQUEST_TIME'] = $this->timeInt();
132-
$server['REQUEST_TIME_FLOAT'] = $this->timeFloat();
133-
$server['REMOTE_ADDR'] = $request->getRemoteAddr();
134-
$server['REQUEST_METHOD'] = $request->method;
135-
136-
$server['HTTP_USER_AGENT'] = '';
137-
foreach ($request->headers as $key => $value) {
138-
$key = \strtoupper(\str_replace('-', '_', $key));
139-
if (\in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH'])) {
140-
$server[$key] = \implode(', ', $value);
141-
} else {
142-
$server['HTTP_' . $key] = \implode(', ', $value);
143-
}
144-
}
145-
146-
return $server;
134+
return GlobalState::enrichServerVars($request);
147135
}
148136

137+
/**
138+
* @deprecated
139+
*/
149140
protected function timeInt(): int
150141
{
151142
return \time();
152143
}
153144

145+
/**
146+
* @deprecated
147+
*/
154148
protected function timeFloat(): float
155149
{
156150
return \microtime(true);

tests/Unit/PSR7WorkerTest.php

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,89 @@
55
namespace Spiral\RoadRunner\Tests\Http\Unit;
66

77
use Nyholm\Psr7\Factory\Psr17Factory;
8+
use PHPUnit\Framework\Attributes\CoversClass;
9+
use PHPUnit\Framework\Attributes\RunClassInSeparateProcess;
810
use PHPUnit\Framework\TestCase;
9-
use Spiral\RoadRunner\Http\HttpWorker;
11+
use Spiral\Goridge\Frame;
12+
use Spiral\RoadRunner\Http\GlobalState;
1013
use Spiral\RoadRunner\Http\PSR7Worker;
14+
use Spiral\RoadRunner\Tests\Http\Unit\Stub\TestRelay;
1115
use Spiral\RoadRunner\Worker;
1216

17+
18+
#[CoversClass(PSR7Worker::class)]
19+
#[CoversClass(GlobalState::class)]
20+
#[RunClassInSeparateProcess]
1321
final class PSR7WorkerTest extends TestCase
1422
{
15-
public function testHttpWorkerIsAvailable(): void
23+
public function testStateServerLeak(): void
1624
{
1725
$psrFactory = new Psr17Factory();
18-
19-
$psrWorker = new PSR7Worker(
20-
Worker::create(),
26+
$relay = new TestRelay();
27+
$psrWorker = new PSR7Worker(
28+
new Worker($relay),
2129
$psrFactory,
2230
$psrFactory,
2331
$psrFactory,
2432
);
2533

26-
self::assertInstanceOf(HttpWorker::class, $psrWorker->getHttpWorker());
34+
//dataProvider is always random and we need to keep the order
35+
$fixtures = [
36+
[
37+
[
38+
'Content-Type' => ['application/html'],
39+
'Connection' => ['keep-alive']
40+
],
41+
[
42+
'REQUEST_URI' => 'http://localhost',
43+
'REMOTE_ADDR' => '127.0.0.1',
44+
'REQUEST_METHOD' => 'GET',
45+
'HTTP_USER_AGENT' => '',
46+
'CONTENT_TYPE' => 'application/html',
47+
'HTTP_CONNECTION' => 'keep-alive',
48+
],
49+
],
50+
[
51+
[
52+
'Content-Type' => ['application/json']
53+
],
54+
[
55+
'REQUEST_URI' => 'http://localhost',
56+
'REMOTE_ADDR' => '127.0.0.1',
57+
'REQUEST_METHOD' => 'GET',
58+
'HTTP_USER_AGENT' => '',
59+
'CONTENT_TYPE' => 'application/json'
60+
],
61+
],
62+
];
63+
64+
$_SERVER = [];
65+
foreach ($fixtures as [$headers, $expectedServer]) {
66+
$body = [
67+
'headers' => $headers,
68+
'rawQuery' => '',
69+
'remoteAddr' => '127.0.0.1',
70+
'protocol' => 'HTTP/1.1',
71+
'method' => 'GET',
72+
'uri' => 'http://localhost',
73+
'parsed' => false,
74+
];
75+
76+
$head = (string)\json_encode($body, \JSON_THROW_ON_ERROR);
77+
$frame = new Frame($head .'test', [\strlen($head)]);
78+
79+
$relay->addFrames($frame);
80+
81+
$psrWorker->waitRequest();
82+
83+
unset($_SERVER['REQUEST_TIME']);
84+
unset($_SERVER['REQUEST_TIME_FLOAT']);
85+
86+
self::assertEquals($expectedServer, $_SERVER);
87+
}
2788
}
2889

90+
2991
protected function tearDown(): void
3092
{
3193
// Clean all extra output buffers

0 commit comments

Comments
 (0)