diff --git a/composer.json b/composer.json index c183201..9f0bd93 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "cloudtay/ripple-driver", - "version": "v1.2.11", + "version": "dev-main", "license": "MIT", "autoload": { "psr-4": { @@ -23,7 +23,7 @@ "ext-posix": "*", "ext-sockets": "*", "ext-pdo": "*", - "cloudtay/ripple": "dev-main" + "cloudtay/ripple": "*" }, "require-dev": { "amphp/mysql": "^3.0", @@ -37,7 +37,9 @@ "symfony/console": "*", "topthink/framework": "*", "workerman/workerman": "dev-master", - "workerman/webman-framework": "dev-master" + "workerman/webman-framework": "dev-master", + "yiisoft/yii2": "^2.0@beta", + "cloudtay/ripple": "dev-main" }, "extra": { "laravel": { @@ -50,11 +52,12 @@ "Psc\\Drive\\ThinkPHP\\Service" ] }, - "include_files": [ - "src/helpers.php" - ] + "yii\\bootstrap": "Psc\\Drive\\Yii2\\Bootstrap" }, "config": { + "allow-plugins": { + "yiisoft/yii2-composer": true + } }, "minimum-stability": "dev", "prefer-stable": true diff --git a/src/Laravel/Coroutine/ContainerMap.php b/src/Laravel/Coroutine/ContainerMap.php index 824e590..aef454d 100644 --- a/src/Laravel/Coroutine/ContainerMap.php +++ b/src/Laravel/Coroutine/ContainerMap.php @@ -37,8 +37,8 @@ use Fiber; use Illuminate\Container\Container; -use function spl_object_hash; use function is_null; +use function spl_object_hash; class ContainerMap { @@ -69,18 +69,6 @@ public static function unbind(): void unset(ContainerMap::$applications[spl_object_hash($fiber)]); } - /** - * @return \Illuminate\Container\Container - */ - public static function current(): Container - { - if (!$fiber = Fiber::getCurrent()) { - return Container::getInstance(); - } - - return ContainerMap::$applications[spl_object_hash($fiber)] ?? Container::getInstance(); - } - /** * @param string|null $abstract * @param array $parameters @@ -97,4 +85,16 @@ public static function app(string $abstract = null, array $parameters = []): mix return $container->make($abstract, $parameters); } + + /** + * @return \Illuminate\Container\Container + */ + public static function current(): Container + { + if (!$fiber = Fiber::getCurrent()) { + return Container::getInstance(); + } + + return ContainerMap::$applications[spl_object_hash($fiber)] ?? Container::getInstance(); + } } diff --git a/src/Laravel/Events/RequestHandled.php b/src/Laravel/Events/RequestHandled.php index 041bf98..45eb9a8 100644 --- a/src/Laravel/Events/RequestHandled.php +++ b/src/Laravel/Events/RequestHandled.php @@ -43,8 +43,8 @@ class RequestHandled public function __construct( public Application $app, public Application $sandbox, - public Request $request, - public Response $response + public Request $request, + public Response $response ) { } } diff --git a/src/Laravel/Events/RequestReceived.php b/src/Laravel/Events/RequestReceived.php index 71bb6ca..6f2cbd8 100644 --- a/src/Laravel/Events/RequestReceived.php +++ b/src/Laravel/Events/RequestReceived.php @@ -43,7 +43,7 @@ class RequestReceived public function __construct( public Application $app, public Application $sandbox, - public Request $request + public Request $request ) { ContainerMap::bind($sandbox); } diff --git a/src/Laravel/Events/RequestTerminated.php b/src/Laravel/Events/RequestTerminated.php index 4b210b2..f0b0f60 100644 --- a/src/Laravel/Events/RequestTerminated.php +++ b/src/Laravel/Events/RequestTerminated.php @@ -45,8 +45,8 @@ class RequestTerminated public function __construct( public Application $app, public Application $sandbox, - public Request $request, - public Response $response + public Request $request, + public Response $response ) { ContainerMap::unbind(); diff --git a/src/Laravel/Events/WorkerErrorOccurred.php b/src/Laravel/Events/WorkerErrorOccurred.php index 53a8b22..cce2046 100644 --- a/src/Laravel/Events/WorkerErrorOccurred.php +++ b/src/Laravel/Events/WorkerErrorOccurred.php @@ -43,7 +43,7 @@ class WorkerErrorOccurred public function __construct( public Application $app, public Application $sandbox, - public Throwable $exception + public Throwable $exception ) { ContainerMap::unbind(); } diff --git a/src/Laravel/Provider.php b/src/Laravel/Provider.php index fe83293..f45d823 100644 --- a/src/Laravel/Provider.php +++ b/src/Laravel/Provider.php @@ -60,7 +60,7 @@ public function register(): void public function boot(): void { $this->publishes([ - __DIR__.'/config/ripple.php' => config_path('ripple.php'), + __DIR__ . '/config/ripple.php' => config_path('ripple.php'), ], 'ripple-config'); } } diff --git a/src/Laravel/Response/IteratorResponse.php b/src/Laravel/Response/IteratorResponse.php index 0137c2b..8b17b4b 100644 --- a/src/Laravel/Response/IteratorResponse.php +++ b/src/Laravel/Response/IteratorResponse.php @@ -45,7 +45,7 @@ class IteratorResponse extends Response /** * @param Iterator|Closure $generator - * @param array $headers + * @param array $headers */ public function __construct(Iterator|Closure $generator, array $headers = []) { diff --git a/src/Laravel/Worker.php b/src/Laravel/Worker.php index a9b46bb..8416c63 100644 --- a/src/Laravel/Worker.php +++ b/src/Laravel/Worker.php @@ -84,7 +84,7 @@ class Worker extends \Psc\Worker\Worker */ public function __construct( private readonly string $address = 'http://127.0.0.1:8008', - int $count = 4, + int $count = 4, private readonly bool $sandbox = true, ) { $this->name = 'http-server'; diff --git a/src/Laravel/config/ripple.php b/src/Laravel/config/ripple.php index de741dd..679dbd7 100644 --- a/src/Laravel/config/ripple.php +++ b/src/Laravel/config/ripple.php @@ -1,4 +1,5 @@ get(App::class) ?? App::getInstance(); - } - /** * @param string $name * @param array $args @@ -93,4 +81,16 @@ public static function app(string $name = '', array $args = [], bool $newInstanc return $container->make($name ?: App::class, $args, $newInstance); } + + /** + * @return \think\App|\think\Container + */ + public static function current(): App|Container + { + if (!Fiber::getCurrent()) { + return App::getInstance(); + } + + return \Co\container()->get(App::class) ?? App::getInstance(); + } } diff --git a/src/ThinkPHP/Worker.php b/src/ThinkPHP/Worker.php index 67fe387..1b24ff7 100644 --- a/src/ThinkPHP/Worker.php +++ b/src/ThinkPHP/Worker.php @@ -86,7 +86,7 @@ class Worker extends \Psc\Worker\Worker */ public function __construct( private readonly string $address = 'http://127.0.0.1:8008', - int $count = 4 + int $count = 4 ) { $this->count = $count; $this->name = 'http-server'; diff --git a/src/Utils/Config.php b/src/Utils/Config.php index ce06c82..642c17a 100644 --- a/src/Utils/Config.php +++ b/src/Utils/Config.php @@ -35,24 +35,11 @@ namespace Psc\Drive\Utils; use function in_array; -use function strtolower; use function is_string; +use function strtolower; class Config { - /** - * @param mixed $value - * - * @return bool - */ - public static function value2bool(mixed $value): bool - { - if (is_string($value)) { - $value = strtolower($value); - } - return in_array($value, ['on', 'true', 'yes', '1', 1, true], true); - } - /** * @param mixed $value * @param string $type @@ -62,8 +49,21 @@ public static function value2bool(mixed $value): bool public static function value2string(mixed $value, string $type): string { return match ($type) { - 'bool' => Config::value2bool($value) ? 'on' : 'off', + 'bool' => Config::value2bool($value) ? 'on' : 'off', default => (string)$value, }; } + + /** + * @param mixed $value + * + * @return bool + */ + public static function value2bool(mixed $value): bool + { + if (is_string($value)) { + $value = strtolower($value); + } + return in_array($value, ['on', 'true', 'yes', '1', 1, true], true); + } } diff --git a/src/Workerman/Driver4.php b/src/Workerman/Driver4.php index 1efb2e5..9e3850d 100644 --- a/src/Workerman/Driver4.php +++ b/src/Workerman/Driver4.php @@ -44,6 +44,7 @@ use Workerman\Events\EventInterface; use Workerman\Worker; +use function array_search; use function call_user_func; use function call_user_func_array; use function Co\cancel; @@ -64,7 +65,6 @@ use function sleep; use function str_contains; use function string2int; -use function array_search; class Driver4 implements EventInterface { diff --git a/src/Workerman/Driver5.php b/src/Workerman/Driver5.php index 610991b..55d509d 100644 --- a/src/Workerman/Driver5.php +++ b/src/Workerman/Driver5.php @@ -39,18 +39,18 @@ use Psc\Utils\Output; use Revolt\EventLoop; use Revolt\EventLoop\UnsupportedFeatureException; -use Workerman\Events\EventInterface; use Throwable; +use Workerman\Events\EventInterface; +use function array_shift; use function Co\cancelAll; use function count; +use function getmypid; use function pcntl_signal; use function sleep; -use function array_shift; -use function getmypid; -use const SIGINT; use const SIG_IGN; +use const SIGINT; final class Driver5 implements EventInterface { @@ -268,14 +268,9 @@ public function offSignal(int $signal): bool * * @return bool */ - public function offDelay(int $timerId): bool + public function offRepeat(int $timerId): bool { - if (isset($this->eventTimer[$timerId])) { - \Co\cancel($this->eventTimer[$timerId]); - unset($this->eventTimer[$timerId]); - return true; - } - return false; + return $this->offDelay($timerId); } /** @@ -283,9 +278,14 @@ public function offDelay(int $timerId): bool * * @return bool */ - public function offRepeat(int $timerId): bool + public function offDelay(int $timerId): bool { - return $this->offDelay($timerId); + if (isset($this->eventTimer[$timerId])) { + \Co\cancel($this->eventTimer[$timerId]); + unset($this->eventTimer[$timerId]); + return true; + } + return false; } /** diff --git a/src/Workerman/Extensions/IteratorResponse.php b/src/Workerman/Extensions/IteratorResponse.php index 90d2c81..2f3e37a 100644 --- a/src/Workerman/Extensions/IteratorResponse.php +++ b/src/Workerman/Extensions/IteratorResponse.php @@ -64,8 +64,8 @@ class IteratorResponse extends Response public function __construct( Iterator|Closure $iterator, protected readonly TcpConnection $tcpConnection, - protected readonly bool $autopilot = true, protected readonly bool $closeWhenFinish = false, + protected readonly bool $autopilot = true, ) { if ($iterator instanceof Closure) { $iterator = $iterator(); diff --git a/src/Yii2/Application.php b/src/Yii2/Application.php new file mode 100644 index 0000000..add38ae --- /dev/null +++ b/src/Yii2/Application.php @@ -0,0 +1,149 @@ +injectRequest = $this->rippleYii2RequestBuild($originalRequest); + $originalResponse = $originalRequest->getResponse(); + + try { + $this->state = parent::STATE_BEFORE_REQUEST; + $this->trigger(parent::EVENT_BEFORE_REQUEST); + + $this->state = parent::STATE_HANDLING_REQUEST; + $response = $this->handleRequest($this->getRequest()); + + $this->state = parent::STATE_AFTER_REQUEST; + $this->trigger(parent::EVENT_AFTER_REQUEST); + + $this->state = parent::STATE_SENDING_RESPONSE; + $originalResponse->setStatusCode($response->statusCode); + $originalResponse->setBody($response->data); + $originalResponse->withHeaders($response->headers->toArray()); + + /*** @var \yii\web\Cookie $cookie */ + foreach ($response->cookies as $cookie) { + $originalResponse->withHeader('Set-Cookie', $cookie->__toString()); + } + + $originalResponse->setStatusText($response->statusText); + $originalResponse->respond(); + $this->state = parent::STATE_END; + + return; + } catch (Throwable $e) { + try { + $this->end($e->getCode(), $response ?? null); + } catch (ExitException $ej) { + return; + } + return; + } + } + + /** + * @param \Psc\Core\Http\Server\Request $request + * + * @return \Psc\Drive\Yii2\Request + */ + public function rippleYii2RequestBuild(\Psc\Core\Http\Server\Request $request): Request + { + $headers = new HeaderCollection(); + $cookies = new CookieCollection(); + foreach ($request->SERVER as $key => $value) { + if (str_starts_with($key, 'HTTP_')) { + $headers->add( + str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($key, 5))))), + $value + ); + } + } + + foreach ($request->COOKIE as $key => $value) { + try { + $cookies->add(new Cookie([ + 'name' => $key, + 'value' => $value, + ])); + } catch (InvalidCallException) { + // ignore + } + } + + $yii2Request = new Request(); + $yii2Request->setHeaders($headers); + $yii2Request->setCookies($cookies); + $yii2Request->setMethod($request->SERVER['REQUEST_METHOD']); + $yii2Request->setUrl($request->SERVER['REQUEST_URI']); + $yii2Request->setBodyParams($request->POST); + $yii2Request->setQueryParams($request->GET); + $yii2Request->setCsrfToken($request->COOKIE['csrf_token'] ?? null); + $yii2Request->rawBody = strval($request->CONTENT); + return $yii2Request; + } + + /** + * @return \yii\web\Request + */ + public function getRequest(): \yii\web\Request + { + return $this->injectRequest ?? parent::getRequest(); + } +} diff --git a/src/Yii2/Bootstrap.php b/src/Yii2/Bootstrap.php new file mode 100644 index 0000000..ac98ca1 --- /dev/null +++ b/src/Yii2/Bootstrap.php @@ -0,0 +1,50 @@ +controllerMap['ripple:server'] = RippleController::class; + } +} diff --git a/src/Yii2/Driver.php b/src/Yii2/Driver.php new file mode 100644 index 0000000..23a398b --- /dev/null +++ b/src/Yii2/Driver.php @@ -0,0 +1,46 @@ + 'string', + 'HTTP_WORKERS' => 'int', + 'HTTP_RELOAD' => 'bool', + 'HTTP_SANDBOX' => 'bool', + 'HTTP_ISOLATION' => 'bool', + ]; +} diff --git a/src/Laravel/Coroutine/Database/Factory.php b/src/Yii2/Request.php similarity index 54% rename from src/Laravel/Coroutine/Database/Factory.php rename to src/Yii2/Request.php index 5c86b9e..9780332 100644 --- a/src/Laravel/Coroutine/Database/Factory.php +++ b/src/Yii2/Request.php @@ -32,58 +32,69 @@ * 由于软件或软件的使用或其他交易而引起的任何索赔、损害或其他责任承担责任。 */ -namespace Psc\Drive\Laravel\Coroutine\Database; +namespace Psc\Drive\Yii2; -use Closure; -use Illuminate\Database\Connection; -use Illuminate\Database\Connectors\ConnectionFactory; -use Illuminate\Database\Connectors\ConnectorInterface; -use Illuminate\Database\MariaDbConnection; -use Illuminate\Database\MySqlConnection; -use Illuminate\Database\PostgresConnection; -use Illuminate\Database\SQLiteConnection; -use Illuminate\Database\SqlServerConnection; -use PDO; -use Psc\Drive\Laravel\Coroutine\Database\MySQL\Connector; +use yii\web\CookieCollection; +use yii\web\HeaderCollection; -class Factory extends ConnectionFactory +class Request extends \yii\web\Request { + /*** @var \yii\web\HeaderCollection */ + protected HeaderCollection $headers; + + /*** @var \yii\web\CookieCollection */ + protected CookieCollection $cookies; + + /*** @var string|null */ + protected string|null $csrfToken = null; + + /*** @var string */ + protected string $method = 'GET'; + + public function getMethod(): string + { + return $this->method; + } + /** - * @param array $config + * @param string $value * - * @return ConnectorInterface + * @return void */ - public function createConnector(array $config): ConnectorInterface + public function setMethod(string $value): void { - return match ($config['driver']) { - // Coroutine MySQL connector - 'mysql-amp' => new Connector(), - - // Coroutine MySQL connector - default => parent::createConnector($config) - }; + $this->method = $value; } /** - * Create a new connection instance. + * @param \yii\web\HeaderCollection $headerCollection * - * @param string $driver - * @param PDO|Closure $connection - * @param string $database - * @param string $prefix - * @param array $config - * - * @return SQLiteConnection|MariaDbConnection|MySqlConnection|PostgresConnection|SqlServerConnection|Connection + * @return void + */ + public function setHeaders(HeaderCollection $headerCollection): void + { + $this->headers = $headerCollection; + } + + /** + * @param \yii\web\CookieCollection $cookieCollection * + * @return void */ - protected function createConnection($driver, $connection, $database, $prefix = '', array $config = []): SQLiteConnection|MariaDbConnection|MySqlConnection|PostgresConnection|SqlServerConnection|Connection + public function setCookies(CookieCollection $cookieCollection): void { - return match ($driver) { - // Coroutine MySQL connection - 'mysql-amp' => new MySQL\Connection($connection, $database, $prefix, $config), + $this->cookies = $cookieCollection; + } - // Native database connection - default => parent::createConnection($driver, $connection, $database, $prefix, $config) - }; + /** + * @param string|null $csrfToken + * + * @return void + */ + public function setCsrfToken(string|null $csrfToken): void + { + if ($csrfToken !== null) { + $this->csrfToken = $csrfToken; + } } } diff --git a/src/Yii2/RippleController.php b/src/Yii2/RippleController.php new file mode 100644 index 0000000..f082c70 --- /dev/null +++ b/src/Yii2/RippleController.php @@ -0,0 +1,54 @@ +addWorker($worker); + $manager->run(); + } +} diff --git a/src/Yii2/Worker.php b/src/Yii2/Worker.php new file mode 100644 index 0000000..06c8cfd --- /dev/null +++ b/src/Yii2/Worker.php @@ -0,0 +1,176 @@ +name = 'http-server'; + $this->count = $count; + try { + $this->rootPath = Yii::getAlias('@app'); + } catch (InvalidParamException) { + Output::error('Yii2 root path not found'); + exit(1); + } + + defined('YII_DEBUG') or define('YII_DEBUG', true); + defined('YII_ENV') or define('YII_ENV', 'dev'); + } + + /** + * @Author cclilshy + * @Date 2024/8/16 23:34 + * + * @param Manager $manager + * + * @return void + * @throws Throwable + */ + public function register(Manager $manager): void + { + cli_set_process_title('yii2-guard'); + + /*** output worker*/ + fwrite(STDOUT, $this->formatRow(['Worker', $this->getName()])); + + /*** output env*/ + fwrite(STDOUT, $this->formatRow(["- Conf"])); + foreach (Driver::DECLARE_OPTIONS as $key => $type) { + fwrite(STDOUT, $this->formatRow([ + $key, + '', + ])); + } + + /*** output logs*/ + fwrite(STDOUT, $this->formatRow(["- Logs"])); + + $server = Net::Http()->server($this->address, [ + 'socket' => [ + 'so_reuseport' => 1, + 'so_reuseaddr' => 1 + ] + ]); + + if (!$server) { + Output::error('Server not supported'); + exit(1); + } + + $this->server = $server; + $config = require $this->rootPath . '/config/web.php'; + $this->application = new Application($config); + + if (Config::value2bool(1)) { + $monitor = IO::File()->watch(); + $monitor->add($this->rootPath . ('/commands')); + $monitor->add($this->rootPath . ('/controllers')); + $monitor->add($this->rootPath . ('/mail')); + $monitor->add($this->rootPath . ('/models')); + $monitor->add($this->rootPath . ('/views')); + $monitor->add($this->rootPath . ('/web')); + $monitor->add($this->rootPath . ('/widgets')); + $monitor->add($this->rootPath . ('/vagrant')); + $monitor->add($this->rootPath . ('/mail')); + $monitor->add($this->rootPath . ('/config')); + if (file_exists($this->rootPath . ('/.env'))) { + $monitor->add($this->rootPath . ('/.env')); + } + + Guard::relevance($manager, $this, $monitor); + } + } + + /** + * @Author cclilshy + * @Date 2024/8/17 11:08 + * @return void + */ + public function boot(): void + { + $this->server->onRequest(function (Request $request) { + $application = clone $this->application; + $application->rippleDispatch($request); + }); + $this->server->listen(); + } +} diff --git a/src/helpers.php b/src/helpers.php index 4a0189b..62ee796 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -1,4 +1,5 @@ getFileName(), 3); if (false) { @@ -57,12 +58,14 @@ function app(string $abstract = null, array $parameters = []): mixed /** * 快速获取容器中的实例 支持依赖注入 * @template T + * * @param string|class-string $name 类名或标识 默认获取当前应用实例 * @param array $args 参数 * @param bool $newInstance 是否每次创建新的实例 + * * @return T|object|\think\App */ - function app(string $name = '', array $args = [], bool $newInstance = false) + function app(string $name = '', array $args = [], bool $newInstance = false): mixed { return AppMap::app($name, $args, $newInstance); }