From 239d7638f2637c1013023875ec9590dbce710e4f Mon Sep 17 00:00:00 2001 From: Savio Resende Date: Thu, 24 Aug 2023 21:37:25 -0500 Subject: [PATCH] Added SSL and pathinfo adjustment. --- composer.json | 3 +- composer.lock | 176 ++++++++++++++++++++++++++++++++-------- src/Services/Server.php | 139 +++++++++++++++++++++++++------ 3 files changed, 261 insertions(+), 57 deletions(-) diff --git a/composer.json b/composer.json index 32dee7a..3ea9158 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ "require": { "ext-openswoole": "22.*", "adoy/fastcgi-client": "^1.0", - "guzzlehttp/guzzle": "^7.7" + "guzzlehttp/guzzle": "^7.7", + "kanata-php/socket-conveyor": "^1.3" }, "extra": { "laravel": { diff --git a/composer.lock b/composer.lock index 8934f97..4fdbabb 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "cf110600af6d7efd36bfedd21cb994f2", + "content-hash": "9b52293c99452346ec86e3d494ee9ea9", "packages": [ { "name": "adoy/fastcgi-client", @@ -372,6 +372,118 @@ ], "time": "2023-08-03T15:06:02+00:00" }, + { + "name": "kanata-php/socket-conveyor", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/kanata-php/socket-conveyor.git", + "reference": "c4daeb5ec518490456af29675fef46e9300b58aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kanata-php/socket-conveyor/zipball/c4daeb5ec518490456af29675fef46e9300b58aa", + "reference": "c4daeb5ec518490456af29675fef46e9300b58aa", + "shasum": "" + }, + "require": { + "league/pipeline": "^1.0", + "php": "^8.0" + }, + "require-dev": { + "codedungeon/phpunit-result-printer": "^0.30.0", + "mockery/mockery": "^1.4", + "openswoole/ide-helper": "^22.0", + "phpunit/phpunit": "^9.2", + "symfony/var-dumper": "^6.1" + }, + "suggest": { + "ext-openswoole": "This package requires at least one of Open Swoole or Swoole" + }, + "bin": [ + "start-ws-server" + ], + "type": "library", + "autoload": { + "psr-4": { + "Tests\\": "tests", + "Conveyor\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A WebSocket/Socket message Router", + "support": { + "issues": "https://github.com/kanata-php/socket-conveyor/issues", + "source": "https://github.com/kanata-php/socket-conveyor/tree/1.3.0" + }, + "funding": [ + { + "url": "https://github.com/kanata-php", + "type": "github" + } + ], + "time": "2023-01-15T22:53:14+00:00" + }, + { + "name": "league/pipeline", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/pipeline.git", + "reference": "aa14b0e3133121f8be39e9a3b6ddd011fc5bb9a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/pipeline/zipball/aa14b0e3133121f8be39e9a3b6ddd011fc5bb9a8", + "reference": "aa14b0e3133121f8be39e9a3b6ddd011fc5bb9a8", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "leanphp/phpspec-code-coverage": "^4.2", + "phpspec/phpspec": "^4.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Pipeline\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net", + "role": "Author" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com", + "role": "Maintainer" + } + ], + "description": "A plug and play pipeline implementation.", + "keywords": [ + "composition", + "design pattern", + "pattern", + "pipeline", + "sequential" + ], + "support": { + "issues": "https://github.com/thephpleague/pipeline/issues", + "source": "https://github.com/thephpleague/pipeline/tree/master" + }, + "time": "2018-06-05T21:06:51+00:00" + }, { "name": "psr/http-client", "version": "1.0.2", @@ -1409,16 +1521,16 @@ }, { "name": "laravel/framework", - "version": "v10.19.0", + "version": "v10.20.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "b8557e4a708a1bd2bc8229bd53feecfa2ac1c6fb" + "reference": "a655dca3fbe83897e22adff652b1878ba352d041" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/b8557e4a708a1bd2bc8229bd53feecfa2ac1c6fb", - "reference": "b8557e4a708a1bd2bc8229bd53feecfa2ac1c6fb", + "url": "https://api.github.com/repos/laravel/framework/zipball/a655dca3fbe83897e22adff652b1878ba352d041", + "reference": "a655dca3fbe83897e22adff652b1878ba352d041", "shasum": "" }, "require": { @@ -1605,20 +1717,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-08-15T13:42:57+00:00" + "time": "2023-08-22T13:37:09+00:00" }, { "name": "laravel/prompts", - "version": "v0.1.5", + "version": "v0.1.6", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "d880a909df144a4bf5760ebd09aba114f79d9adc" + "reference": "b514c5620e1b3b61221b0024dc88def26d9654f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/d880a909df144a4bf5760ebd09aba114f79d9adc", - "reference": "d880a909df144a4bf5760ebd09aba114f79d9adc", + "url": "https://api.github.com/repos/laravel/prompts/zipball/b514c5620e1b3b61221b0024dc88def26d9654f4", + "reference": "b514c5620e1b3b61221b0024dc88def26d9654f4", "shasum": "" }, "require": { @@ -1651,9 +1763,9 @@ ], "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.1.5" + "source": "https://github.com/laravel/prompts/tree/v0.1.6" }, - "time": "2023-08-15T14:29:44+00:00" + "time": "2023-08-18T13:32:23+00:00" }, { "name": "laravel/serializable-closure", @@ -2751,25 +2863,25 @@ }, { "name": "orchestra/testbench", - "version": "v8.9.0", + "version": "v8.9.1", "source": { "type": "git", "url": "https://github.com/orchestral/testbench.git", - "reference": "299ff2246058fef9663f8e04e9ab0f9ac2099491" + "reference": "71d3fffcceb993a20f3666b3cbce6ee057a1a9ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/orchestral/testbench/zipball/299ff2246058fef9663f8e04e9ab0f9ac2099491", - "reference": "299ff2246058fef9663f8e04e9ab0f9ac2099491", + "url": "https://api.github.com/repos/orchestral/testbench/zipball/71d3fffcceb993a20f3666b3cbce6ee057a1a9ec", + "reference": "71d3fffcceb993a20f3666b3cbce6ee057a1a9ec", "shasum": "" }, "require": { "composer-runtime-api": "^2.2", "fakerphp/faker": "^1.21", - "laravel/framework": ">=10.17.0 <10.20.0", + "laravel/framework": ">=10.17.0 <10.21.0", "mockery/mockery": "^1.5.1", - "orchestra/testbench-core": ">=8.9.0 <8.10.0", - "orchestra/workbench": "^0.1.6", + "orchestra/testbench-core": ">=8.9.1 <8.10.0", + "orchestra/workbench": "^0.1.7 || ^0.2.0", "php": "^8.1", "phpunit/phpunit": "^9.6 || ^10.1", "spatie/laravel-ray": "^1.32.4", @@ -2801,22 +2913,22 @@ ], "support": { "issues": "https://github.com/orchestral/testbench/issues", - "source": "https://github.com/orchestral/testbench/tree/v8.9.0" + "source": "https://github.com/orchestral/testbench/tree/v8.9.1" }, - "time": "2023-08-19T04:07:31+00:00" + "time": "2023-08-22T13:41:07+00:00" }, { "name": "orchestra/testbench-core", - "version": "v8.9.0", + "version": "v8.9.1", "source": { "type": "git", "url": "https://github.com/orchestral/testbench-core.git", - "reference": "f216306afcce50803562949133a930190d8be408" + "reference": "8c6d37b937c8932fda021797ba5fcea036f65eef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/f216306afcce50803562949133a930190d8be408", - "reference": "f216306afcce50803562949133a930190d8be408", + "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/8c6d37b937c8932fda021797ba5fcea036f65eef", + "reference": "8c6d37b937c8932fda021797ba5fcea036f65eef", "shasum": "" }, "require": { @@ -2884,20 +2996,20 @@ "issues": "https://github.com/orchestral/testbench/issues", "source": "https://github.com/orchestral/testbench-core" }, - "time": "2023-08-19T03:43:49+00:00" + "time": "2023-08-22T08:49:34+00:00" }, { "name": "orchestra/workbench", - "version": "v0.1.6", + "version": "v0.1.7", "source": { "type": "git", "url": "https://github.com/orchestral/workbench.git", - "reference": "4dd369f654e43925fa051e2f5a50cdc1aa0f3686" + "reference": "b7d03cb05c8f59db1eca4aff4174f7330399bd60" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/orchestral/workbench/zipball/4dd369f654e43925fa051e2f5a50cdc1aa0f3686", - "reference": "4dd369f654e43925fa051e2f5a50cdc1aa0f3686", + "url": "https://api.github.com/repos/orchestral/workbench/zipball/b7d03cb05c8f59db1eca4aff4174f7330399bd60", + "reference": "b7d03cb05c8f59db1eca4aff4174f7330399bd60", "shasum": "" }, "require": { @@ -2940,9 +3052,9 @@ "description": "Workbench Companion for Laravel Packages Development", "support": { "issues": "https://github.com/orchestral/workbench/issues", - "source": "https://github.com/orchestral/workbench/tree/v0.1.6" + "source": "https://github.com/orchestral/workbench/tree/v0.1.7" }, - "time": "2023-08-19T02:10:56+00:00" + "time": "2023-08-20T13:44:25+00:00" }, { "name": "phar-io/manifest", diff --git a/src/Services/Server.php b/src/Services/Server.php index 8d5c3b6..239349d 100644 --- a/src/Services/Server.php +++ b/src/Services/Server.php @@ -13,12 +13,15 @@ use JackedPhp\JackedServer\Events\JackedRequestReceived; use JackedPhp\JackedServer\Events\JackedServerStarted; use JackedPhp\JackedServer\Services\Response as JackedResponse; +use OpenSwoole\Constant; use OpenSwoole\Http\Request; use OpenSwoole\Http\Response; use OpenSwoole\Server as OpenSwooleBaseServer; -use OpenSwoole\Http\Server as OpenSwooleServer; +use OpenSwoole\WebSocket\Server as OpenSwooleServer; +use OpenSwoole\WebSocket\Frame; use Psr\Log\LoggerInterface; use OpenSwoole\Util; +use Conveyor\SocketHandlers\SocketMessageRouter as Conveyor; class Server { @@ -47,43 +50,99 @@ public function __construct( public function run(): void { - $server = new OpenSwooleServer( - $this->host ?? config('jacked-server.host', '0.0.0.0'), - $this->port ?? config('jacked-server.port', 8080), - config('jacked-server.server-type', OpenSwooleBaseServer::POOL_MODE), - ); - $server->set(config('jacked-server.openswoole-server-settings', [ + $ssl = config('jacked-server.ssl-enabled', false); + $primaryPort = $ssl ? config('jacked-server.ssl-port', 443) : config('jacked-server.port', 8080); + $serverConfig = array_merge(config('jacked-server.openswoole-server-settings', [ 'document_root' => $this->publicDocumentRoot ?? public_path(), 'enable_static_handler' => true, 'static_handler_locations' => [ '/imgs', '/css' ], // reactor and workers 'reactor_num' => Util::getCPUNum() + 2, 'worker_num' => Util::getCPUNum() + 2, - ])); + ]), ($ssl ? [ + 'ssl_cert_file' => config('jacked-server.ssl-cert-file'), + 'ssl_key_file' => config('jacked-server.ssl-key-file'), + 'open_http_protocol' => true, + ] : [])); + + $server = new OpenSwooleServer( + $this->host ?? config('jacked-server.host', '0.0.0.0'), + $this->port ?? $primaryPort, + config('jacked-server.server-type', OpenSwooleBaseServer::POOL_MODE), + $ssl ? Constant::SOCK_TCP | Constant::SSL : Constant::SOCK_TCP, + ); + $server->set($serverConfig); $server->on('start', [$this, 'handleStart']); // ssl - if (config('jacked-server.ssl-enabled', false)) { - $sslPort = $server->listen( + if ($ssl) { + $secondaryPort = $server->listen( $this->host ?? config('jacked-server.host', '0.0.0.0'), - config('jacked-server.ssl-port', 443), - SWOOLE_SOCK_TCP | SWOOLE_SSL + config('jacked-server.port', 8080), + Constant::SOCK_TCP ); - $sslPort->set([ - 'ssl_cert_file' => base_path('packages/jacked-php/jacked-server/js-cert.pem'), - 'ssl_key_file' => base_path('packages/jacked-php/jacked-server/js-key.pem'), - 'open_http_protocol' => true, - ]); - $sslPort->on('request', [$this, 'handleRequest']); - $server->on('request', [$this, 'sslRedirectRequest']); - } else { - $server->on('request', [$this, 'handleRequest']); + + $secondaryPort->on('request', [$this, 'sslRedirectRequest']); } + $server->on('request', [$this, 'handleRequest']); + $server->on('message', [$this, 'handleWsMessage']); + $server->on('handshake', [$this, 'handleWsHandshake']); + $server->on('open', [$this, 'handleWsOpen']); + $server->start(); } - public function sslRedirectRequest(Request $request, Response $response) { + public function handleWsOpen(OpenSwooleServer $server, Request $request): void + { + $message = 'OpenSwoole Connection opened' + . ' with FD: ' . $request->fd + . ' on ' . $server->host . ':' . $server->port + . ' at ' . Carbon::now()->format('Y-m-d H:i:s'); + $this->logger->info($this->logPrefix . $message); + } + + public function handleWsHandshake(Request $request, Response $response): bool + { + // evaluate intention to upgrade to websocket + try { + $headers = $this->processSecWebSocketKey($request); + } catch (Exception $e) { + $response->status(400); + $response->end($e->getMessage()); + return false; + } + + // check for authorization + try { + $authToken = $request->header['authorization'] ?? null; + if ($authToken !== 'YOUR_SECRET_TOKEN') { + throw new Exception('Unauthorized'); + } + } catch (Exception $e) { + $response->status(401); + $response->end($e->getMessage()); + return false; + } + + foreach($headers as $headerKey => $val) { + $response->header($headerKey, $val); + } + + $response->status(101); + $response->end(); + + return true; + } + + public function handleWsMessage(OpenSwooleServer $server, Frame $frame): void + { + $this->logger->info($this->logPrefix . ' Message received from ' . $frame->fd); + Conveyor::run($frame->data, $frame->fd, $server); + } + + public function sslRedirectRequest(Request $request, Response $response): void + { $response->status(301); $response->header( 'Location', @@ -117,6 +176,39 @@ public function handleRequest(Request $request, Response $response): void $this->sendResponse($response, $jackedResponse); } + /** + * @param Request $request + * @return array + * @throws Exception + */ + private function processSecWebSocketKey(Request $request): array + { + $secWebSocketKey = $request->header['sec-websocket-key']; + $patten = '#^[+/0-9A-Za-z]{21}[AQgw]==$#'; + + if ( + 0 === preg_match($patten, $secWebSocketKey) + || 16 !== strlen(base64_decode($secWebSocketKey)) + ) { + throw new Exception('Invalid Sec-WebSocket-Key'); + } + + $key = base64_encode(sha1($request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true)); + + $headers = [ + 'Upgrade' => 'websocket', + 'Connection' => 'Upgrade', + 'Sec-WebSocket-Accept' => $key, + 'Sec-WebSocket-Version' => '13', + ]; + + if(isset($request->header['sec-websocket-protocol'])) { + $headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol']; + } + + return $headers; + } + private function sendResponse(Response $response, JackedResponse $jackedResponse): void { if (!empty($jackedResponse->getError())) { @@ -216,8 +308,7 @@ private function prepareRequestOptions( private function getPathInfo(array $serverInfo): string { - $pathInfo = Arr::get($serverInfo, 'path_info', ''); - return rtrim(dirname($pathInfo), '/'); + return Arr::get($serverInfo, 'path_info', ''); } /**