Skip to content

Commit 57fc537

Browse files
committed
feat: split Swoole adapters, add compression support, and adopt utopia-php/servers
- Split Swoole adapter into Swoole (SWOOLE_PROCESS) and SwooleCoroutine (coroutine-based) servers - Add response compression support with configurable min size and algorithm selection - Migrate Hook to utopia-php/servers and Route now extends Servers\Hook - Add View class for template rendering - Add trusted IP header support and IP validation in Request - Enhance Response with cookie management, content-type helpers, and chunked transfer - Add utopia-php/servers and utopia-php/compression dependencies - Fix server-swoole.php test server to work with non-coroutine Swoole adapter - Disable Swoole cookie parsing to preserve raw Cookie headers
1 parent 15d1195 commit 57fc537

File tree

18 files changed

+938
-502
lines changed

18 files changed

+938
-502
lines changed

composer.json

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@
1010
],
1111
"license": "MIT",
1212
"minimum-stability": "stable",
13-
"autoload": {
13+
"autoload": {
14+
"psr-4": {
15+
"Utopia\\": "src/"
16+
}
17+
},
18+
"autoload-dev": {
1419
"psr-4": {
15-
"Utopia\\": "src/",
16-
"Tests\\E2E\\": "tests/e2e"
17-
}
18-
},
19-
"autoload-dev": {
20-
"psr-4": {
21-
"Utopia\\Http\\Tests\\": "tests/"
22-
}
23-
},
20+
"Utopia\\Http\\Tests\\": "tests/",
21+
"Tests\\E2E\\": "tests/e2e"
22+
}
23+
},
2424
"scripts": {
2525
"lint": "vendor/bin/pint --test",
2626
"format": "vendor/bin/pint",
@@ -30,9 +30,11 @@
3030
},
3131
"require": {
3232
"php": ">=8.2",
33+
"ext-swoole": "*",
3334
"utopia-php/di": "0.3.*",
34-
"utopia-php/validators": "0.2.*",
35-
"ext-swoole": "*"
35+
"utopia-php/servers": "0.3.*",
36+
"utopia-php/compression": "0.1.*",
37+
"utopia-php/validators": "0.2.*"
3638
},
3739
"config": {
3840
"allow-plugins": {

composer.lock

Lines changed: 102 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Http/Adapter/FPM/Request.php

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,26 @@ public function setServer(string $key, string $value): static
6868
*/
6969
public function getIP(): string
7070
{
71-
$ips = explode(',', $this->getHeader('HTTP_X_FORWARDED_FOR', $this->getServer('REMOTE_ADDR') ?? '0.0.0.0'));
71+
$remoteAddr = $this->getServer('REMOTE_ADDR') ?? '0.0.0.0';
7272

73-
return trim($ips[0] ?? '');
73+
foreach ($this->trustedIpHeaders as $header) {
74+
$headerValue = $this->getHeader($header);
75+
76+
if (empty($headerValue)) {
77+
continue;
78+
}
79+
80+
// Leftmost IP address is the address of the originating client
81+
$ips = \explode(',', $headerValue);
82+
$ip = \trim($ips[0]);
83+
84+
// Validate IP format (supports both IPv4 and IPv6)
85+
if (\filter_var($ip, FILTER_VALIDATE_IP)) {
86+
return $ip;
87+
}
88+
}
89+
90+
return $remoteAddr;
7491
}
7592

7693
/**

src/Http/Adapter/Swoole/Request.php

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,26 @@ public function setServer(string $key, string $value): static
7373
*/
7474
public function getIP(): string
7575
{
76-
$ips = explode(',', $this->getHeader('x-forwarded-for', $this->getServer('remote_addr') ?? '0.0.0.0'));
76+
$remoteAddr = $this->getServer('remote_addr') ?? '0.0.0.0';
7777

78-
return trim($ips[0] ?? '');
78+
foreach ($this->trustedIpHeaders as $header) {
79+
$headerValue = $this->getHeader($header);
80+
81+
if (empty($headerValue)) {
82+
continue;
83+
}
84+
85+
// Leftmost IP address is the address of the originating client
86+
$ips = explode(',', $headerValue);
87+
$ip = trim($ips[0]);
88+
89+
// Validate IP format (supports both IPv4 and IPv6)
90+
if (filter_var($ip, FILTER_VALIDATE_IP)) {
91+
return $ip;
92+
}
93+
}
94+
95+
return $remoteAddr;
7996
}
8097

8198
/**
@@ -259,9 +276,12 @@ public function getCookie(string $key, string $default = ''): string
259276
$cookies = \explode(';', $this->getHeader('cookie', ''));
260277
foreach ($cookies as $cookie) {
261278
$cookie = \trim($cookie);
262-
[$cookieKey, $cookieValue] = \explode('=', $cookie, 2);
263-
$cookieKey = \trim($cookieKey);
264-
$cookieValue = \trim($cookieValue);
279+
if ($cookie === '') {
280+
continue;
281+
}
282+
$parts = \explode('=', $cookie, 2);
283+
$cookieKey = \trim($parts[0]);
284+
$cookieValue = isset($parts[1]) ? \trim($parts[1]) : '';
265285
if ($cookieKey === $key) {
266286
return $cookieValue;
267287
}

src/Http/Adapter/Swoole/Response.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,14 @@ public function sendHeader(string $key, mixed $value): void
8686
protected function sendCookie(string $name, string $value, array $options): void
8787
{
8888
$this->swoole->cookie(
89-
name: $name,
90-
value: $value,
91-
expires: $options['expire'] ?? 0,
92-
path: $options['path'] ?? '',
93-
domain: $options['domain'] ?? '',
94-
secure: $options['secure'] ?? false,
95-
httponly: $options['httponly'] ?? false,
96-
samesite: $options['samesite'] ?? false,
89+
$name,
90+
$value,
91+
$options['expire'] ?? 0,
92+
$options['path'] ?? '',
93+
$options['domain'] ?? '',
94+
$options['secure'] ?? false,
95+
$options['httponly'] ?? false,
96+
$options['samesite'] ?? false,
9797
);
9898
}
9999
}

src/Http/Adapter/Swoole/Server.php

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,61 +5,63 @@
55
use Swoole\Coroutine;
66
use Utopia\Http\Adapter;
77
use Utopia\DI\Container;
8-
use Swoole\Coroutine\Http\Server as SwooleServer;
8+
use Swoole\Http\Server as SwooleServer;
99
use Swoole\Http\Request as SwooleRequest;
1010
use Swoole\Http\Response as SwooleResponse;
1111

12-
use function Swoole\Coroutine\run;
13-
1412
class Server extends Adapter
1513
{
1614
protected SwooleServer $server;
1715
protected const REQUEST_CONTAINER_CONTEXT_KEY = '__utopia_http_request_container';
1816
protected Container $container;
1917

20-
public function __construct(string $host, ?string $port = null, array $settings = [], ?Container $container = null)
18+
public function __construct(string $host, ?string $port = null, array $settings = [], int $mode = SWOOLE_PROCESS, ?Container $container = null)
2119
{
22-
$this->server = new SwooleServer($host, $port);
20+
$this->server = new SwooleServer($host, (int) $port, $mode);
2321
$this->server->set(\array_merge($settings, [
24-
'enable_coroutine' => true,
2522
'http_parse_cookie' => false,
2623
]));
2724
$this->container = $container ?? new Container();
2825
}
2926

3027
public function onRequest(callable $callback)
3128
{
32-
$this->server->handle('/', function (SwooleRequest $request, SwooleResponse $response) use ($callback) {
29+
$this->server->on('request', function (SwooleRequest $request, SwooleResponse $response) use ($callback) {
3330
$requestContainer = new Container($this->container);
3431
$requestContainer->set('swooleRequest', fn () => $request);
3532
$requestContainer->set('swooleResponse', fn () => $response);
3633

3734
Coroutine::getContext()[self::REQUEST_CONTAINER_CONTEXT_KEY] = $requestContainer;
3835

39-
$utopiaRequest = new Request($request);
40-
$utopiaResponse = new Response($response);
41-
42-
\call_user_func($callback, $utopiaRequest, $utopiaResponse);
36+
\call_user_func($callback, new Request($request), new Response($response));
4337
});
4438
}
4539

4640
public function getContainer(): Container
4741
{
48-
return Coroutine::getContext()[self::REQUEST_CONTAINER_CONTEXT_KEY] ?? $this->container;
42+
if (Coroutine::getCid() !== -1) {
43+
return Coroutine::getContext()[self::REQUEST_CONTAINER_CONTEXT_KEY] ?? $this->container;
44+
}
45+
46+
return $this->container;
4947
}
5048

51-
public function onStart(callable $callback)
49+
public function getServer(): SwooleServer
5250
{
51+
return $this->server;
52+
}
5353

54-
\call_user_func($callback, $this);
54+
public function onStart(callable $callback)
55+
{
56+
$this->server->on('start', function () use ($callback) {
57+
go(function () use ($callback) {
58+
\call_user_func($callback, $this);
59+
});
60+
});
5561
}
5662

5763
public function start()
5864
{
59-
if (Coroutine::getCid() === -1) {
60-
run(fn () => $this->server->start());
61-
} else {
62-
$this->server->start();
63-
}
65+
return $this->server->start();
6466
}
6567
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Utopia\Http\Adapter\SwooleCoroutine;
4+
5+
use Utopia\Http\Adapter\Swoole\Request as SwooleAdapterRequest;
6+
7+
class Request extends SwooleAdapterRequest
8+
{
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Utopia\Http\Adapter\SwooleCoroutine;
4+
5+
use Utopia\Http\Adapter\Swoole\Response as SwooleAdapterResponse;
6+
7+
class Response extends SwooleAdapterResponse
8+
{
9+
}

0 commit comments

Comments
 (0)