Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@
],
"license": "MIT",
"minimum-stability": "stable",
"autoload": {
"autoload": {
"psr-4": {
"Utopia\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Utopia\\": "src/",
"Tests\\E2E\\": "tests/e2e"
}
},
"autoload-dev": {
"psr-4": {
"Utopia\\Http\\Tests\\": "tests/"
}
},
"Utopia\\Http\\Tests\\": "tests/",
"Tests\\E2E\\": "tests/e2e"
}
},
"scripts": {
"lint": "vendor/bin/pint --test",
"format": "vendor/bin/pint",
Expand All @@ -30,9 +30,11 @@
},
"require": {
"php": ">=8.2",
"ext-swoole": "*",
"utopia-php/di": "0.3.*",
"utopia-php/validators": "0.2.*",
"ext-swoole": "*"
"utopia-php/servers": "0.3.*",
"utopia-php/compression": "0.1.*",
"utopia-php/validators": "0.2.*"
},
"config": {
"allow-plugins": {
Expand Down
104 changes: 102 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 19 additions & 2 deletions src/Http/Adapter/FPM/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,26 @@ public function setServer(string $key, string $value): static
*/
public function getIP(): string
{
$ips = explode(',', $this->getHeader('HTTP_X_FORWARDED_FOR', $this->getServer('REMOTE_ADDR') ?? '0.0.0.0'));
$remoteAddr = $this->getServer('REMOTE_ADDR') ?? '0.0.0.0';

return trim($ips[0] ?? '');
foreach ($this->trustedIpHeaders as $header) {
$headerValue = $this->getHeader($header);

if (empty($headerValue)) {
continue;
}

// Leftmost IP address is the address of the originating client
$ips = \explode(',', $headerValue);
$ip = \trim($ips[0]);

// Validate IP format (supports both IPv4 and IPv6)
if (\filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}

return $remoteAddr;
}

/**
Expand Down
30 changes: 25 additions & 5 deletions src/Http/Adapter/Swoole/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,26 @@ public function setServer(string $key, string $value): static
*/
public function getIP(): string
{
$ips = explode(',', $this->getHeader('x-forwarded-for', $this->getServer('remote_addr') ?? '0.0.0.0'));
$remoteAddr = $this->getServer('remote_addr') ?? '0.0.0.0';

return trim($ips[0] ?? '');
foreach ($this->trustedIpHeaders as $header) {
$headerValue = $this->getHeader($header);

if (empty($headerValue)) {
continue;
}

// Leftmost IP address is the address of the originating client
$ips = explode(',', $headerValue);
$ip = trim($ips[0]);

// Validate IP format (supports both IPv4 and IPv6)
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}

return $remoteAddr;
}

/**
Expand Down Expand Up @@ -259,9 +276,12 @@ public function getCookie(string $key, string $default = ''): string
$cookies = \explode(';', $this->getHeader('cookie', ''));
foreach ($cookies as $cookie) {
$cookie = \trim($cookie);
[$cookieKey, $cookieValue] = \explode('=', $cookie, 2);
$cookieKey = \trim($cookieKey);
$cookieValue = \trim($cookieValue);
if ($cookie === '') {
continue;
}
$parts = \explode('=', $cookie, 2);
$cookieKey = \trim($parts[0]);
$cookieValue = isset($parts[1]) ? \trim($parts[1]) : '';
if ($cookieKey === $key) {
return $cookieValue;
}
Expand Down
16 changes: 8 additions & 8 deletions src/Http/Adapter/Swoole/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,14 @@ public function sendHeader(string $key, mixed $value): void
protected function sendCookie(string $name, string $value, array $options): void
{
$this->swoole->cookie(
name: $name,
value: $value,
expires: $options['expire'] ?? 0,
path: $options['path'] ?? '',
domain: $options['domain'] ?? '',
secure: $options['secure'] ?? false,
httponly: $options['httponly'] ?? false,
samesite: $options['samesite'] ?? false,
$name,
$value,
$options['expire'] ?? 0,
$options['path'] ?? '',
$options['domain'] ?? '',
$options['secure'] ?? false,
$options['httponly'] ?? false,
$options['samesite'] ?? false,
);
}
}
44 changes: 22 additions & 22 deletions src/Http/Adapter/Swoole/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,61 +5,61 @@
use Swoole\Coroutine;
use Utopia\Http\Adapter;
use Utopia\DI\Container;
use Swoole\Coroutine\Http\Server as SwooleServer;
use Swoole\Http\Server as SwooleServer;
use Swoole\Http\Request as SwooleRequest;
use Swoole\Http\Response as SwooleResponse;

use function Swoole\Coroutine\run;

class Server extends Adapter
{
protected SwooleServer $server;
protected const REQUEST_CONTAINER_CONTEXT_KEY = '__utopia_http_request_container';
protected Container $container;

public function __construct(string $host, ?string $port = null, array $settings = [], ?Container $container = null)
public function __construct(string $host, ?string $port = null, array $settings = [], int $mode = SWOOLE_PROCESS, ?Container $container = null)
{
$this->server = new SwooleServer($host, $port);
$this->server->set(\array_merge($settings, [
'enable_coroutine' => true,
'http_parse_cookie' => false,
]));
$this->server = new SwooleServer($host, (int) $port, $mode);
$this->server->set($settings);
$this->container = $container ?? new Container();
}

public function onRequest(callable $callback)
{
$this->server->handle('/', function (SwooleRequest $request, SwooleResponse $response) use ($callback) {
$this->server->on('request', function (SwooleRequest $request, SwooleResponse $response) use ($callback) {
$requestContainer = new Container($this->container);
$requestContainer->set('swooleRequest', fn () => $request);
$requestContainer->set('swooleResponse', fn () => $response);

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

$utopiaRequest = new Request($request);
$utopiaResponse = new Response($response);

\call_user_func($callback, $utopiaRequest, $utopiaResponse);
\call_user_func($callback, new Request($request), new Response($response));
});
}

public function getContainer(): Container
{
return Coroutine::getContext()[self::REQUEST_CONTAINER_CONTEXT_KEY] ?? $this->container;
if (Coroutine::getCid() !== -1) {
return Coroutine::getContext()[self::REQUEST_CONTAINER_CONTEXT_KEY] ?? $this->container;
}

return $this->container;
}

public function onStart(callable $callback)
public function getServer(): SwooleServer
{
return $this->server;
}

\call_user_func($callback, $this);
public function onStart(callable $callback)
{
$this->server->on('start', function () use ($callback) {
go(function () use ($callback) {
\call_user_func($callback, $this);
});
});
}

public function start()
{
if (Coroutine::getCid() === -1) {
run(fn () => $this->server->start());
} else {
$this->server->start();
}
return $this->server->start();
}
}
9 changes: 9 additions & 0 deletions src/Http/Adapter/SwooleCoroutine/Request.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Utopia\Http\Adapter\SwooleCoroutine;

use Utopia\Http\Adapter\Swoole\Request as SwooleAdapterRequest;

class Request extends SwooleAdapterRequest
{
}
9 changes: 9 additions & 0 deletions src/Http/Adapter/SwooleCoroutine/Response.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Utopia\Http\Adapter\SwooleCoroutine;

use Utopia\Http\Adapter\Swoole\Response as SwooleAdapterResponse;

class Response extends SwooleAdapterResponse
{
}
Loading
Loading