From 5e3bb3a263e4c959f6db5ae0d8958241a2a7e05e Mon Sep 17 00:00:00 2001 From: k911 Date: Mon, 1 Oct 2018 00:05:44 +0200 Subject: [PATCH] feat(daemon-mode): Daemonize Swoole HTTP server See commands: ```bash $ bin/console swoole:server:start # starts swoole http server in background $ bin/console swoole:server:stop # stops swoole http server running in background $ bin/console swoole:server:reload # reloads swoole http server's workers and project classes (not infrastructure/vendor ones) ``` --- .gitignore | 1 + composer.lock | 180 +++++++++--------- .../Command/AbstractServerStartCommand.php | 32 +++- .../Bundle/Command/ServerReloadCommand.php | 65 +++++++ .../Bundle/Command/ServerRunCommand.php | 2 +- .../Bundle/Command/ServerStartCommand.php | 69 +++++++ .../Bundle/Command/ServerStopCommand.php | 65 +++++++ .../Bundle/Resources/config/services.yaml | 11 ++ src/Server/HttpServer.php | 43 ++++- src/Server/HttpServerConfiguration.php | 52 ++++- src/functions.php | 27 +-- tests/Fixtures/Symfony/app/config/monolog.php | 16 +- 12 files changed, 436 insertions(+), 127 deletions(-) create mode 100644 src/Bridge/Symfony/Bundle/Command/ServerReloadCommand.php create mode 100644 src/Bridge/Symfony/Bundle/Command/ServerStartCommand.php create mode 100644 src/Bridge/Symfony/Bundle/Command/ServerStopCommand.php diff --git a/.gitignore b/.gitignore index 4af9a921..7d3f4b60 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /vendor +swoole.pid .php_cs.cache clover.xml diff --git a/composer.lock b/composer.lock index 9e95df67..5e26a591 100644 --- a/composer.lock +++ b/composer.lock @@ -204,7 +204,7 @@ }, { "name": "symfony/config", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/config.git", @@ -267,16 +267,16 @@ }, { "name": "symfony/console", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "d3dbe91fd5b8b11ecb73508c844bc6a490de15b4" + "reference": "dc7122fe5f6113cfaba3b3de575d31112c9aa60b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/d3dbe91fd5b8b11ecb73508c844bc6a490de15b4", - "reference": "d3dbe91fd5b8b11ecb73508c844bc6a490de15b4", + "url": "https://api.github.com/repos/symfony/console/zipball/dc7122fe5f6113cfaba3b3de575d31112c9aa60b", + "reference": "dc7122fe5f6113cfaba3b3de575d31112c9aa60b", "shasum": "" }, "require": { @@ -331,20 +331,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-09-30T03:38:13+00:00" + "time": "2018-10-03T08:15:46+00:00" }, { "name": "symfony/debug", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "b4a0b67dee59e2cae4449a8f8eabc508d622fd33" + "reference": "e3f76ce6198f81994e019bb2b4e533e9de1b9b90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/b4a0b67dee59e2cae4449a8f8eabc508d622fd33", - "reference": "b4a0b67dee59e2cae4449a8f8eabc508d622fd33", + "url": "https://api.github.com/repos/symfony/debug/zipball/e3f76ce6198f81994e019bb2b4e533e9de1b9b90", + "reference": "e3f76ce6198f81994e019bb2b4e533e9de1b9b90", "shasum": "" }, "require": { @@ -387,20 +387,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-09-22T19:04:12+00:00" + "time": "2018-10-02T16:36:10+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "985ebee0d4cadaadef4d81aaccf0018443cf2560" + "reference": "f6b9d893ad28aefd8942dc0469c8397e2216fe30" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/985ebee0d4cadaadef4d81aaccf0018443cf2560", - "reference": "985ebee0d4cadaadef4d81aaccf0018443cf2560", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f6b9d893ad28aefd8942dc0469c8397e2216fe30", + "reference": "f6b9d893ad28aefd8942dc0469c8397e2216fe30", "shasum": "" }, "require": { @@ -458,11 +458,11 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2018-09-21T12:49:42+00:00" + "time": "2018-10-02T12:40:59+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -525,16 +525,16 @@ }, { "name": "symfony/filesystem", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "a10ae719b02c47ecba5c684ca2b505f3a49bf397" + "reference": "596d12b40624055c300c8b619755b748ca5cf0b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/a10ae719b02c47ecba5c684ca2b505f3a49bf397", - "reference": "a10ae719b02c47ecba5c684ca2b505f3a49bf397", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/596d12b40624055c300c8b619755b748ca5cf0b5", + "reference": "596d12b40624055c300c8b619755b748ca5cf0b5", "shasum": "" }, "require": { @@ -571,20 +571,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-09-30T03:38:13+00:00" + "time": "2018-10-02T12:40:59+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "2ce66353d0a6ea96bc54bc9ecf8bcea4eaf5896c" + "reference": "d528136617ff24f530e70df9605acc1b788b08d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/2ce66353d0a6ea96bc54bc9ecf8bcea4eaf5896c", - "reference": "2ce66353d0a6ea96bc54bc9ecf8bcea4eaf5896c", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d528136617ff24f530e70df9605acc1b788b08d4", + "reference": "d528136617ff24f530e70df9605acc1b788b08d4", "shasum": "" }, "require": { @@ -625,20 +625,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2018-09-30T03:47:35+00:00" + "time": "2018-10-03T08:48:45+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "74b1d37bf9a1cddc38093530c0a931a310994ea5" + "reference": "f5e7c15a5d010be0e16ce798594c5960451d4220" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/74b1d37bf9a1cddc38093530c0a931a310994ea5", - "reference": "74b1d37bf9a1cddc38093530c0a931a310994ea5", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f5e7c15a5d010be0e16ce798594c5960451d4220", + "reference": "f5e7c15a5d010be0e16ce798594c5960451d4220", "shasum": "" }, "require": { @@ -712,7 +712,7 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2018-09-30T05:05:39+00:00" + "time": "2018-10-03T12:53:38+00:00" }, { "name": "symfony/polyfill-ctype", @@ -3309,16 +3309,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "6.0.7", + "version": "6.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "865662550c384bc1db7e51d29aeda1c2c161d69a" + "reference": "848f78b3309780fef7ec8c4666b7ab4e6b09b22f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/865662550c384bc1db7e51d29aeda1c2c161d69a", - "reference": "865662550c384bc1db7e51d29aeda1c2c161d69a", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/848f78b3309780fef7ec8c4666b7ab4e6b09b22f", + "reference": "848f78b3309780fef7ec8c4666b7ab4e6b09b22f", "shasum": "" }, "require": { @@ -3368,7 +3368,7 @@ "testing", "xunit" ], - "time": "2018-06-01T07:51:50+00:00" + "time": "2018-10-04T03:41:23+00:00" }, { "name": "phpunit/php-file-iterator", @@ -3561,16 +3561,16 @@ }, { "name": "phpunit/phpunit", - "version": "7.3.5", + "version": "7.4.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "7b331efabbb628c518c408fdfcaf571156775de2" + "reference": "f3837fa1e07758057ae06e8ddec6d06ba183f126" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7b331efabbb628c518c408fdfcaf571156775de2", - "reference": "7b331efabbb628c518c408fdfcaf571156775de2", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3837fa1e07758057ae06e8ddec6d06ba183f126", + "reference": "f3837fa1e07758057ae06e8ddec6d06ba183f126", "shasum": "" }, "require": { @@ -3595,7 +3595,7 @@ "sebastian/exporter": "^3.1", "sebastian/global-state": "^2.0", "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^1.0", + "sebastian/resource-operations": "^2.0", "sebastian/version": "^2.0.1" }, "conflict": { @@ -3615,7 +3615,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "7.3-dev" + "dev-master": "7.4-dev" } }, "autoload": { @@ -3641,7 +3641,7 @@ "testing", "xunit" ], - "time": "2018-09-08T15:14:29+00:00" + "time": "2018-10-05T04:05:24+00:00" }, { "name": "psr/cache", @@ -4217,25 +4217,25 @@ }, { "name": "sebastian/resource-operations", - "version": "1.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", "shasum": "" }, "require": { - "php": ">=5.6.0" + "php": "^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -4255,7 +4255,7 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28T20:34:47+00:00" + "time": "2018-10-04T04:07:39+00:00" }, { "name": "sebastian/version", @@ -4302,7 +4302,7 @@ }, { "name": "symfony/cache", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", @@ -4371,16 +4371,16 @@ }, { "name": "symfony/finder", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "f0b042d445c155501793e7b8007457f9f5bb1c8c" + "reference": "1f17195b44543017a9c9b2d437c670627e96ad06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/f0b042d445c155501793e7b8007457f9f5bb1c8c", - "reference": "f0b042d445c155501793e7b8007457f9f5bb1c8c", + "url": "https://api.github.com/repos/symfony/finder/zipball/1f17195b44543017a9c9b2d437c670627e96ad06", + "reference": "1f17195b44543017a9c9b2d437c670627e96ad06", "shasum": "" }, "require": { @@ -4416,20 +4416,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-09-21T12:49:42+00:00" + "time": "2018-10-03T08:47:56+00:00" }, { "name": "symfony/framework-bundle", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "462c6acc8c7eeff5066b94d9943422fccdf11fd8" + "reference": "3a0f2ec035c6ecc0f751fda1a76b02310bc9bbfe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/462c6acc8c7eeff5066b94d9943422fccdf11fd8", - "reference": "462c6acc8c7eeff5066b94d9943422fccdf11fd8", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/3a0f2ec035c6ecc0f751fda1a76b02310bc9bbfe", + "reference": "3a0f2ec035c6ecc0f751fda1a76b02310bc9bbfe", "shasum": "" }, "require": { @@ -4533,11 +4533,11 @@ ], "description": "Symfony FrameworkBundle", "homepage": "https://symfony.com", - "time": "2018-09-30T03:38:13+00:00" + "time": "2018-10-03T08:47:56+00:00" }, { "name": "symfony/monolog-bridge", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bridge.git", @@ -4667,7 +4667,7 @@ }, { "name": "symfony/options-resolver", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", @@ -4835,16 +4835,16 @@ }, { "name": "symfony/process", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "c64647828bc7733ba9427f1eeb1b542588635427" + "reference": "ee33c0322a8fee0855afcc11fff81e6b1011b529" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/c64647828bc7733ba9427f1eeb1b542588635427", - "reference": "c64647828bc7733ba9427f1eeb1b542588635427", + "url": "https://api.github.com/repos/symfony/process/zipball/ee33c0322a8fee0855afcc11fff81e6b1011b529", + "reference": "ee33c0322a8fee0855afcc11fff81e6b1011b529", "shasum": "" }, "require": { @@ -4880,20 +4880,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-09-08T13:24:10+00:00" + "time": "2018-10-02T12:40:59+00:00" }, { "name": "symfony/routing", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "d998113cf6db1e8262fdd8d5db9774c9a7be33b0" + "reference": "537803f0bdfede36b9acef052d2e4d447d9fa0e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/d998113cf6db1e8262fdd8d5db9774c9a7be33b0", - "reference": "d998113cf6db1e8262fdd8d5db9774c9a7be33b0", + "url": "https://api.github.com/repos/symfony/routing/zipball/537803f0bdfede36b9acef052d2e4d447d9fa0e9", + "reference": "537803f0bdfede36b9acef052d2e4d447d9fa0e9", "shasum": "" }, "require": { @@ -4957,20 +4957,20 @@ "uri", "url" ], - "time": "2018-09-08T13:24:10+00:00" + "time": "2018-10-02T12:40:59+00:00" }, { "name": "symfony/stopwatch", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "966c982df3cca41324253dc0c7ffe76b6076b705" + "reference": "5bfc064125b73ff81229e19381ce1c34d3416f4b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/966c982df3cca41324253dc0c7ffe76b6076b705", - "reference": "966c982df3cca41324253dc0c7ffe76b6076b705", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5bfc064125b73ff81229e19381ce1c34d3416f4b", + "reference": "5bfc064125b73ff81229e19381ce1c34d3416f4b", "shasum": "" }, "require": { @@ -5006,20 +5006,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2018-07-26T11:00:49+00:00" + "time": "2018-10-02T12:40:59+00:00" }, { "name": "symfony/twig-bridge", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "4a8426ab5e00c34ac8a7bf1a2bfd5a75165edadb" + "reference": "7af4d9e7c38c1eb8c0d4e2528c33e6d23728ff7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/4a8426ab5e00c34ac8a7bf1a2bfd5a75165edadb", - "reference": "4a8426ab5e00c34ac8a7bf1a2bfd5a75165edadb", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/7af4d9e7c38c1eb8c0d4e2528c33e6d23728ff7e", + "reference": "7af4d9e7c38c1eb8c0d4e2528c33e6d23728ff7e", "shasum": "" }, "require": { @@ -5096,11 +5096,11 @@ ], "description": "Symfony Twig Bridge", "homepage": "https://symfony.com", - "time": "2018-09-18T16:38:01+00:00" + "time": "2018-10-02T12:40:59+00:00" }, { "name": "symfony/twig-bundle", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", @@ -5174,16 +5174,16 @@ }, { "name": "symfony/yaml", - "version": "v4.1.5", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "ac5af7c14c4f8abf0f77419e8397eff7a370df19" + "reference": "367e689b2fdc19965be435337b50bc8adf2746c9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/ac5af7c14c4f8abf0f77419e8397eff7a370df19", - "reference": "ac5af7c14c4f8abf0f77419e8397eff7a370df19", + "url": "https://api.github.com/repos/symfony/yaml/zipball/367e689b2fdc19965be435337b50bc8adf2746c9", + "reference": "367e689b2fdc19965be435337b50bc8adf2746c9", "shasum": "" }, "require": { @@ -5229,7 +5229,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-09-30T03:38:13+00:00" + "time": "2018-10-02T16:36:10+00:00" }, { "name": "theseer/tokenizer", diff --git a/src/Bridge/Symfony/Bundle/Command/AbstractServerStartCommand.php b/src/Bridge/Symfony/Bundle/Command/AbstractServerStartCommand.php index 98fb9d8f..b4282bdf 100644 --- a/src/Bridge/Symfony/Bundle/Command/AbstractServerStartCommand.php +++ b/src/Bridge/Symfony/Bundle/Command/AbstractServerStartCommand.php @@ -26,7 +26,7 @@ abstract class AbstractServerStartCommand extends Command private $serverFactory; private $serverConfiguration; private $bootManager; - private $parameterBag; + protected $parameterBag; /** * @param HttpServer $server @@ -75,8 +75,7 @@ protected function configure(): void ->addOption('serve-static', 's', InputOption::VALUE_NONE, 'Enables serving static content from public directory.') ->addOption('public-dir', null, InputOption::VALUE_REQUIRED, 'Public directory', $this->getDefaultPublicDir()) ->addOption('trusted-hosts', null, InputOption::VALUE_REQUIRED, 'Trusted hosts', $this->parameterBag->get('swoole.http_server.trusted_hosts')) - ->addOption('trusted-proxies', null, InputOption::VALUE_REQUIRED, 'Trusted proxies', $this->parameterBag->get('swoole.http_server.trusted_proxies')) - ; + ->addOption('trusted-proxies', null, InputOption::VALUE_REQUIRED, 'Trusted proxies', $this->parameterBag->get('swoole.http_server.trusted_proxies')); } /** @@ -95,6 +94,11 @@ final protected function execute(InputInterface $input, OutputInterface $output) $this->prepareServerConfiguration($this->serverConfiguration, $input); + if ($this->server->isRunning()) { + $io->error('Swoole HTTP Server is already running'); + exit(1); + } + $this->server->attach($this->serverFactory->make( $this->serverConfiguration->getDefaultSocket(), $this->serverConfiguration->getRunningMode() @@ -109,11 +113,7 @@ final protected function execute(InputInterface $input, OutputInterface $output) $io->success(\sprintf('Swoole HTTP Server started on http://%s', $this->serverConfiguration->getDefaultSocket()->addressPort())); $io->table(['Configuration', 'Values'], $this->prepareConfigurationRowsToPrint($this->serverConfiguration, $runtimeConfiguration)); - if ($this->server->start()) { - $io->success('Swoole HTTP Server has been successfully shutdown.'); - } else { - $io->error('Failure during starting Swoole HTTP Server.'); - } + $this->startServer($this->serverConfiguration, $this->server, $io); } /** @@ -233,4 +233,20 @@ protected function prepareConfigurationRowsToPrint(HttpServerConfiguration $serv return $rows; } + + /** + * @param HttpServerConfiguration $serverConfiguration + * @param HttpServer $server + * @param SymfonyStyle $io + * + * @throws \Assert\AssertionFailedException + */ + protected function startServer(HttpServerConfiguration $serverConfiguration, HttpServer $server, SymfonyStyle $io): void + { + if ($server->start()) { + $io->success('Swoole HTTP Server has been successfully shutdown.'); + } else { + $io->error('Failure during starting Swoole HTTP Server.'); + } + } } diff --git a/src/Bridge/Symfony/Bundle/Command/ServerReloadCommand.php b/src/Bridge/Symfony/Bundle/Command/ServerReloadCommand.php new file mode 100644 index 00000000..65e25b1c --- /dev/null +++ b/src/Bridge/Symfony/Bundle/Command/ServerReloadCommand.php @@ -0,0 +1,65 @@ +server = $server; + $this->serverConfiguration = $serverConfiguration; + $this->parameterBag = $parameterBag; + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + protected function configure(): void + { + $this->setName('swoole:server:reload') + ->setDescription("Reloads a local Swoole HTTP server's workers running in background. It will reload only classes not loaded before server initialization.") + ->addOption('pid_file', null, InputOption::VALUE_REQUIRED, 'Pid file', $this->parameterBag->get('kernel.project_dir').'/var/swoole.pid'); + } + + /** + * {@inheritdoc} + * + * @throws \Assert\AssertionFailedException + */ + protected function execute(InputInterface $input, OutputInterface $output): void + { + $io = new SymfonyStyle($input, $output); + + $this->serverConfiguration->daemonize($input->getOption('pid_file')); + + try { + $this->server->reload(); + } catch (Throwable $ex) { + $io->error($ex->getMessage()); + exit(1); + } + + $io->success('Swoole HTTP Server\'s workers reloaded successfully'); + } +} diff --git a/src/Bridge/Symfony/Bundle/Command/ServerRunCommand.php b/src/Bridge/Symfony/Bundle/Command/ServerRunCommand.php index 18391ff2..267da2d7 100644 --- a/src/Bridge/Symfony/Bundle/Command/ServerRunCommand.php +++ b/src/Bridge/Symfony/Bundle/Command/ServerRunCommand.php @@ -12,7 +12,7 @@ final class ServerRunCommand extends AbstractServerStartCommand protected function configure(): void { $this->setName('swoole:server:run') - ->setDescription('Runs a local swoole http server'); + ->setDescription('Runs a local Swoole HTTP server.'); parent::configure(); } diff --git a/src/Bridge/Symfony/Bundle/Command/ServerStartCommand.php b/src/Bridge/Symfony/Bundle/Command/ServerStartCommand.php new file mode 100644 index 00000000..552b2070 --- /dev/null +++ b/src/Bridge/Symfony/Bundle/Command/ServerStartCommand.php @@ -0,0 +1,69 @@ +setName('swoole:server:start') + ->setDescription('Runs a local Swoole HTTP server in background.') + ->addOption('pid_file', null, InputOption::VALUE_REQUIRED, 'Pid file', $this->parameterBag->get('kernel.project_dir').'/var/swoole.pid'); + + parent::configure(); + } + + /** + * {@inheritdoc} + */ + protected function prepareServerConfiguration(HttpServerConfiguration $serverConfiguration, InputInterface $input): void + { + /** @var string|null $pidFile */ + $pidFile = $input->getOption('pid_file'); + $serverConfiguration->daemonize($pidFile); + + parent::prepareServerConfiguration($serverConfiguration, $input); + } + + /** + * {@inheritdoc} + */ + protected function startServer(HttpServerConfiguration $serverConfiguration, HttpServer $server, SymfonyStyle $io): void + { + if (!$serverConfiguration->existsPidFile() && !\touch($serverConfiguration->getPidFile())) { + throw new RuntimeException(\sprintf('Could not create pid file "%s".', $serverConfiguration->getPid())); + } + + // Output stream `php://stdout` must be closed + $this->forceCloseOutputStream($io); + + $server->start(); + } + + private function forceCloseOutputStream(SymfonyStyle $io): void + { + /** @var ConsoleOutput $consoleOutput */ + $consoleOutput = &get_object_property($io, 'output', OutputStyle::class); + + /** @var resource $stream */ + $stream = &get_object_property($consoleOutput, 'stream', StreamOutput::class); + + \fclose($stream); + } +} diff --git a/src/Bridge/Symfony/Bundle/Command/ServerStopCommand.php b/src/Bridge/Symfony/Bundle/Command/ServerStopCommand.php new file mode 100644 index 00000000..a05153d2 --- /dev/null +++ b/src/Bridge/Symfony/Bundle/Command/ServerStopCommand.php @@ -0,0 +1,65 @@ +server = $server; + $this->serverConfiguration = $serverConfiguration; + $this->parameterBag = $parameterBag; + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + protected function configure(): void + { + $this->setName('swoole:server:stop') + ->setDescription('Stops a local Swoole HTTP server running in background') + ->addOption('pid_file', null, InputOption::VALUE_REQUIRED, 'Pid file', $this->parameterBag->get('kernel.project_dir').'/var/swoole.pid'); + } + + /** + * {@inheritdoc} + * + * @throws \Assert\AssertionFailedException + */ + protected function execute(InputInterface $input, OutputInterface $output): void + { + $io = new SymfonyStyle($input, $output); + + $this->serverConfiguration->daemonize($input->getOption('pid_file')); + + try { + $this->server->shutdown(); + } catch (Throwable $ex) { + $io->error($ex->getMessage()); + exit(1); + } + + $io->success('Swoole server shutdown successfully'); + } +} diff --git a/src/Bridge/Symfony/Bundle/Resources/config/services.yaml b/src/Bridge/Symfony/Bundle/Resources/config/services.yaml index 19322313..4b9036ef 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/services.yaml +++ b/src/Bridge/Symfony/Bundle/Resources/config/services.yaml @@ -39,6 +39,17 @@ services: arguments: $configurator: '@K911\Swoole\Server\Configurator\WithRequestHandler' + 'K911\Swoole\Bridge\Symfony\Bundle\Command\ServerStartCommand': + tags: ['console.command'] + arguments: + $serverFactory: '@swoole_bundle.server.http_server.factory' + + 'K911\Swoole\Bridge\Symfony\Bundle\Command\ServerStopCommand': + tags: ['console.command'] + + 'K911\Swoole\Bridge\Symfony\Bundle\Command\ServerReloadCommand': + tags: ['console.command'] + 'K911\Swoole\Bridge\Symfony\Bundle\Command\ServerRunCommand': tags: ['console.command'] arguments: diff --git a/src/Server/HttpServer.php b/src/Server/HttpServer.php index 8175009e..79b1b732 100644 --- a/src/Server/HttpServer.php +++ b/src/Server/HttpServer.php @@ -5,12 +5,13 @@ namespace K911\Swoole\Server; use Assert\Assertion; +use RuntimeException; use Swoole\Http\Server; +use Swoole\Process; +use Throwable; final class HttpServer { - private const SWOOLE_HTTP_SERVER_HAS_NOT_BEEN_INITIALIZED_MESSAGE = 'Swoole HTTP Server has not been setup yet. Please use setup or attach method.'; - /** * @var Server|null */ @@ -46,7 +47,7 @@ public function attach(Server $server): void */ public function start(): bool { - Assertion::isInstanceOf($this->server, Server::class, self::SWOOLE_HTTP_SERVER_HAS_NOT_BEEN_INITIALIZED_MESSAGE); + Assertion::isInstanceOf($this->server, Server::class, 'Swoole HTTP Server has not been setup yet. Please use attach method.'); return $this->running = $this->server->start(); } @@ -56,9 +57,27 @@ public function start(): bool */ public function shutdown(): void { - Assertion::isInstanceOf($this->server, Server::class, self::SWOOLE_HTTP_SERVER_HAS_NOT_BEEN_INITIALIZED_MESSAGE); + if ($this->server instanceof Server) { + $this->server->shutdown(); + } elseif ($this->isRunningInBackground()) { + Process::kill($this->configuration->getPid(), 15); // SIGTERM + } else { + throw new RuntimeException('Swoole HTTP Server has not been running.'); + } + } - $this->server->shutdown(); + /** + * @throws \Assert\AssertionFailedException + */ + public function reload(): void + { + if ($this->server instanceof Server) { + $this->server->reload(); + } elseif ($this->isRunningInBackground()) { + Process::kill($this->configuration->getPid(), 10); // SIGUSR1 + } else { + throw new RuntimeException('Swoole HTTP Server has not been running.'); + } } /** @@ -66,6 +85,18 @@ public function shutdown(): void */ public function isRunning(): bool { - return $this->running || $this->configuration->existsPidFile(); + return $this->running || $this->isRunningInBackground(); + } + + /** + * @return bool + */ + private function isRunningInBackground(): bool + { + try { + return Process::kill($this->configuration->getPid(), 0); + } catch (Throwable $ex) { + return false; + } } } diff --git a/src/Server/HttpServerConfiguration.php b/src/Server/HttpServerConfiguration.php index beb17973..68abd25d 100644 --- a/src/Server/HttpServerConfiguration.php +++ b/src/Server/HttpServerConfiguration.php @@ -17,6 +17,7 @@ final class HttpServerConfiguration */ private const SWOOLE_HTTP_SERVER_CONFIGURATION = [ 'reactor_count' => 'reactor_num', + 'daemonize' => 'daemonize', 'worker_count' => 'worker_num', 'serve_static' => 'enable_static_handler', 'public_dir' => 'document_root', @@ -109,6 +110,10 @@ private function validateSetting(string $key, $value): void Assertion::inArray($value, \array_keys(self::SWOOLE_SERVE_STATIC)); } + if ('daemonize' === $key) { + Assertion::boolean($value); + } + if ('public_dir' === $key) { Assertion::directory($value, 'Public directory does not exists. Tried "%s".'); } @@ -158,7 +163,8 @@ private function setSettings(array $settings): void } } - Assertion::false(isset($settings['serve_static']) && 'off' !== $settings['serve_static'] && !isset($settings['public_dir']), 'Enabling static files serving requires providing "public_dir" setting.'); + Assertion::false($this->isDaemon() && !$this->hasPidFile(), 'Pid file is required when using daemon mode'); + Assertion::false($this->servingStaticContent() && !$this->hasPublicDir(), 'Enabling static files serving requires providing "public_dir" setting.'); } public function getRunningMode(): string @@ -173,7 +179,12 @@ public function hasPublicDir(): bool public function hasPidFile(): bool { - return isset($this->settings['public_dir']); + return isset($this->settings['pid_file']); + } + + public function servingStaticContent(): bool + { + return isset($this->settings['serve_static']) && 'off' !== $this->settings['serve_static']; } public function existsPidFile(): bool @@ -181,6 +192,22 @@ public function existsPidFile(): bool return $this->hasPidFile() && \file_exists($this->getPidFile()); } + /** + * @throws \Assert\AssertionFailedException + * + * @return int + */ + public function getPid(): int + { + Assertion::true($this->existsPidFile(), 'Could not get pid file. It does not exists or server is not running in background.'); + + /** @var string $contents */ + $contents = \file_get_contents($this->getPidFile()); + Assertion::numeric($contents, 'Contents in pid file is not an integer or it is empty'); + + return (int) $contents; + } + /** * @throws \Assert\AssertionFailedException * @@ -276,4 +303,25 @@ public function getSwooleDocumentRoot(): ?string { return 'default' === $this->settings['serve_static'] ? $this->settings['public_dir'] : null; } + + public function isDaemon(): bool + { + return isset($this->settings['daemonize']); + } + + /** + * @param null|string $pidFile + * + * @throws \Assert\AssertionFailedException + */ + public function daemonize(?string $pidFile = null): void + { + $settings = ['daemonize' => true]; + + if (null !== $pidFile) { + $settings['pid_file'] = $pidFile; + } + + $this->setSettings($settings); + } } diff --git a/src/functions.php b/src/functions.php index 8ca972ef..5dc42815 100644 --- a/src/functions.php +++ b/src/functions.php @@ -4,37 +4,40 @@ namespace K911\Swoole; +use Closure; use OutOfRangeException; /** * Replaces object property with provided value. * Property may not be public. * - * @param object $obj - * @param string $propertyName - * @param mixed $newValue + * @param object $obj + * @param string $propertyName + * @param mixed $newValue + * @param null|string $scope class scope useful when property is inherited */ -function replace_object_property(object $obj, string $propertyName, $newValue): void +function replace_object_property(object $obj, string $propertyName, $newValue, ?string $scope = null): void { - (function (string $propertyName, $newValue): void { + Closure::bind(function (string $propertyName, $newValue): void { $this->$propertyName = $newValue; - })->call($obj, $propertyName, $newValue); + }, $obj, $scope ?? $obj)($propertyName, $newValue); } /** - * Get object property. + * Get object property (even by reference). * Property may not be public. * - * @param object $obj - * @param string $propertyName + * @param object $obj + * @param string $propertyName + * @param null|string $scope class scope useful when property is inherited * * @return mixed */ -function get_object_property(object $obj, string $propertyName) +function &get_object_property(object $obj, string $propertyName, ?string $scope = null) { - return (function (string $propertyName) { + return Closure::bind(function &(string $propertyName) { return $this->$propertyName; - })->call($obj, $propertyName); + }, $obj, $scope ?? $obj)($propertyName); } /** diff --git a/tests/Fixtures/Symfony/app/config/monolog.php b/tests/Fixtures/Symfony/app/config/monolog.php index 238321df..ce617573 100644 --- a/tests/Fixtures/Symfony/app/config/monolog.php +++ b/tests/Fixtures/Symfony/app/config/monolog.php @@ -14,14 +14,14 @@ ], ]; -$container->addResource(new ClassExistenceResource(Application::class)); -if (\class_exists(Application::class)) { - $handlers['console'] = [ - 'type' => 'console', - 'process_psr_3_messages' => false, - 'channels' => ['!event', '!console'], - ]; -} +//$container->addResource(new ClassExistenceResource(Application::class)); +//if (\class_exists(Application::class)) { +// $handlers['console'] = [ +// 'type' => 'console', +// 'process_psr_3_messages' => false, +// 'channels' => ['!event', '!console'], +// ]; +//} $container->loadFromExtension('monolog', [ 'handlers' => $handlers,