From 5187396f85504011adb7e7b547d71b9556a47b03 Mon Sep 17 00:00:00 2001 From: k911 Date: Mon, 17 Sep 2018 22:15:52 +0200 Subject: [PATCH] refactor(swoole): Simplify booting services at runtime Swoole server can affect configuration, and it has to be created at runtime due to host/port binding, after successful server boot, some services also can be reconfigured to allow simple configuration substitution via cli --- Bridge/Doctrine/ORM/EntityManagerHandler.php | 10 +- .../SetRequestRuntimeConfiguration.php | 27 +++ .../TrustAllProxiesRequestHandler.php | 16 +- .../DebugHttpKernelRequestHandler.php | 10 +- .../HttpKernel/HttpKernelRequestHandler.php | 13 +- Command/ServerProfileCommand.php | 135 +++++++++----- Command/ServerRunCommand.php | 117 ++++++++---- .../Compiler/BootManagerPass.php | 31 ++++ DependencyInjection/SwooleExtension.php | 24 ++- Resources/config/services.yaml | 34 +++- Server/Config/Socket.php | 168 ++++++++++++++++++ Server/Configurator/ConfiguratorInterface.php | 15 ++ .../WithHttpServerConfiguration.php | 33 ++++ .../WithLimitedRequestHandler.php | 30 ++++ Server/Configurator/WithRequestHandler.php | 30 ++++ Server/HttpServer.php | 48 ++--- Server/HttpServerConfiguration.php | 160 +++++------------ Server/HttpServerFactory.php | 39 +++- .../AdvancedStaticFilesServer.php | 6 +- .../LimitedRequestHandler.php | 13 +- .../RequestHandlerInterface.php | 9 +- Server/Runtime/BootManager.php | 50 ++++++ Server/Runtime/BootableInterface.php | 15 ++ SwooleBundle.php | 8 + 24 files changed, 741 insertions(+), 300 deletions(-) create mode 100644 Bridge/Symfony/HttpFoundation/SetRequestRuntimeConfiguration.php create mode 100644 DependencyInjection/Compiler/BootManagerPass.php create mode 100644 Server/Config/Socket.php create mode 100644 Server/Configurator/ConfiguratorInterface.php create mode 100644 Server/Configurator/WithHttpServerConfiguration.php create mode 100644 Server/Configurator/WithLimitedRequestHandler.php create mode 100644 Server/Configurator/WithRequestHandler.php rename Server/{ => RequestHandler}/AdvancedStaticFilesServer.php (96%) rename Server/{ => RequestHandler}/LimitedRequestHandler.php (88%) rename Server/{ => RequestHandler}/RequestHandlerInterface.php (63%) create mode 100644 Server/Runtime/BootManager.php create mode 100644 Server/Runtime/BootableInterface.php diff --git a/Bridge/Doctrine/ORM/EntityManagerHandler.php b/Bridge/Doctrine/ORM/EntityManagerHandler.php index 233fb4b9..2fed5884 100644 --- a/Bridge/Doctrine/ORM/EntityManagerHandler.php +++ b/Bridge/Doctrine/ORM/EntityManagerHandler.php @@ -4,7 +4,7 @@ namespace App\Bundle\SwooleBundle\Bridge\Doctrine\ORM; -use App\Bundle\SwooleBundle\Server\RequestHandlerInterface; +use App\Bundle\SwooleBundle\Server\RequestHandler\RequestHandlerInterface; use Doctrine\ORM\EntityManagerInterface; use Swoole\Http\Request; use Swoole\Http\Response; @@ -22,14 +22,6 @@ public function __construct(RequestHandlerInterface $decorated, EntityManagerInt $this->connection = $entityManager->getConnection(); } - /** - * {@inheritdoc} - */ - public function boot(array $runtimeConfiguration = []): void - { - $this->decorated->boot($runtimeConfiguration); - } - /** * {@inheritdoc} */ diff --git a/Bridge/Symfony/HttpFoundation/SetRequestRuntimeConfiguration.php b/Bridge/Symfony/HttpFoundation/SetRequestRuntimeConfiguration.php new file mode 100644 index 00000000..c49d7a34 --- /dev/null +++ b/Bridge/Symfony/HttpFoundation/SetRequestRuntimeConfiguration.php @@ -0,0 +1,27 @@ +decorated = $decorated; - $this->trustAllProxies = false; + $this->trustAllProxies = $trustAllProxies; } /** @@ -25,14 +26,9 @@ public function __construct(RequestHandlerInterface $decorated) */ public function boot(array $runtimeConfiguration = []): void { - if (isset($runtimeConfiguration['trustedProxies']) && \in_array('*', $runtimeConfiguration['trustedProxies'], true)) { + if (isset($runtimeConfiguration['trustAllProxies']) && true === $runtimeConfiguration['trustAllProxies']) { $this->trustAllProxies = true; - $runtimeConfiguration['trustedProxies'] = \array_filter($runtimeConfiguration['trustedProxies'], function (string $trustedProxy) { - return '*' !== $trustedProxy; - }); } - - $this->decorated->boot($runtimeConfiguration); } /** diff --git a/Bridge/Symfony/HttpKernel/DebugHttpKernelRequestHandler.php b/Bridge/Symfony/HttpKernel/DebugHttpKernelRequestHandler.php index d6702854..4e61ba83 100644 --- a/Bridge/Symfony/HttpKernel/DebugHttpKernelRequestHandler.php +++ b/Bridge/Symfony/HttpKernel/DebugHttpKernelRequestHandler.php @@ -5,7 +5,7 @@ namespace App\Bundle\SwooleBundle\Bridge\Symfony\HttpKernel; use App\Bundle\SwooleBundle\Functions\ServerUtils; -use App\Bundle\SwooleBundle\Server\RequestHandlerInterface; +use App\Bundle\SwooleBundle\Server\RequestHandler\RequestHandlerInterface; use Swoole\Http\Request; use Swoole\Http\Response; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -24,14 +24,6 @@ public function __construct(RequestHandlerInterface $decorated, KernelInterface $this->kernel = $kernel; } - /** - * {@inheritdoc} - */ - public function boot(array $runtimeConfiguration = []): void - { - $this->decorated->boot($runtimeConfiguration); - } - /** * {@inheritdoc} */ diff --git a/Bridge/Symfony/HttpKernel/HttpKernelRequestHandler.php b/Bridge/Symfony/HttpKernel/HttpKernelRequestHandler.php index 0da4b8a3..e5e73175 100644 --- a/Bridge/Symfony/HttpKernel/HttpKernelRequestHandler.php +++ b/Bridge/Symfony/HttpKernel/HttpKernelRequestHandler.php @@ -6,14 +6,14 @@ use App\Bundle\SwooleBundle\Bridge\Symfony\HttpFoundation\RequestFactoryInterface; use App\Bundle\SwooleBundle\Bridge\Symfony\HttpFoundation\ResponseProcessorInterface; -use App\Bundle\SwooleBundle\Server\RequestHandlerInterface; +use App\Bundle\SwooleBundle\Server\RequestHandler\RequestHandlerInterface; +use App\Bundle\SwooleBundle\Server\Runtime\BootableInterface; use Swoole\Http\Request as SwooleRequest; use Swoole\Http\Response as SwooleResponse; -use Symfony\Component\HttpFoundation\Request as SymfonyRequest; use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\HttpKernel\TerminableInterface; -final class HttpKernelRequestHandler implements RequestHandlerInterface +final class HttpKernelRequestHandler implements RequestHandlerInterface, BootableInterface { private $kernel; private $requestFactory; @@ -31,13 +31,6 @@ public function __construct(KernelInterface $kernel, RequestFactoryInterface $re */ public function boot(array $runtimeConfiguration = []): void { - if (\array_key_exists('trustedHosts', $runtimeConfiguration)) { - SymfonyRequest::setTrustedHosts($runtimeConfiguration['trustedHosts']); - } - if (\array_key_exists('trustedProxies', $runtimeConfiguration)) { - SymfonyRequest::setTrustedProxies($runtimeConfiguration['trustedProxies'], $runtimeConfiguration['trustedHeaderSet'] ?? SymfonyRequest::HEADER_X_FORWARDED_ALL); - } - $this->kernel->boot(); } diff --git a/Command/ServerProfileCommand.php b/Command/ServerProfileCommand.php index 9241c74f..8e60e5f2 100644 --- a/Command/ServerProfileCommand.php +++ b/Command/ServerProfileCommand.php @@ -7,7 +7,8 @@ use App\Bundle\SwooleBundle\Functions\ServerUtils; use App\Bundle\SwooleBundle\Server\HttpServer; use App\Bundle\SwooleBundle\Server\HttpServerConfiguration; -use App\Bundle\SwooleBundle\Server\RequestHandlerInterface; +use App\Bundle\SwooleBundle\Server\HttpServerFactory; +use App\Bundle\SwooleBundle\Server\Runtime\BootManager; use Composer\XdebugHandler\XdebugHandler; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\InvalidArgumentException; @@ -20,23 +21,31 @@ final class ServerProfileCommand extends Command { - private $kernel; private $server; - private $configuration; - private $driver; + private $serverFactory; + private $serverConfiguration; + private $kernel; + private $bootManager; /** - * @param KernelInterface $kernel * @param HttpServer $server - * @param HttpServerConfiguration $configuration - * @param RequestHandlerInterface $driver + * @param HttpServerFactory $serverFactory + * @param HttpServerConfiguration $serverConfiguration + * @param KernelInterface $kernel + * @param BootManager $bootManager */ - public function __construct(KernelInterface $kernel, HttpServer $server, HttpServerConfiguration $configuration, RequestHandlerInterface $driver) - { - $this->kernel = $kernel; + public function __construct( + HttpServer $server, + HttpServerFactory $serverFactory, + HttpServerConfiguration $serverConfiguration, + KernelInterface $kernel, + BootManager $bootManager + ) { $this->server = $server; - $this->driver = $driver; - $this->configuration = $configuration; + $this->serverFactory = $serverFactory; + $this->serverConfiguration = $serverConfiguration; + $this->kernel = $kernel; + $this->bootManager = $bootManager; parent::__construct(); } @@ -48,7 +57,7 @@ public function __construct(KernelInterface $kernel, HttpServer $server, HttpSer */ private function getDefaultPublicDir(): string { - return $this->configuration->hasPublicDir() ? $this->configuration->getPublicDir() : \dirname($this->kernel->getRootDir()).'/public'; + return $this->serverConfiguration->hasPublicDir() ? $this->serverConfiguration->getPublicDir() : \dirname($this->kernel->getRootDir()).'/public'; } /** @@ -79,19 +88,19 @@ protected function configure(): void */ protected function execute(InputInterface $input, OutputInterface $output): void { - $xdebug = new XdebugHandler('swoole'); - $xdebug->check(); - unset($xdebug); + $this->ensureXdebugDisabled(); $io = new SymfonyStyle($input, $output); - $this->configuration->changeSocket( - (string) ($input->getOption('host') ?? $this->configuration->getHost()), - (int) ($input->getOption('port') ?? $this->configuration->getPort()) - ); + $socket = $this->serverConfiguration->getDefaultSocket(); + $socket = $socket + ->withPort((int) ($input->getOption('port') ?? $socket->port())) + ->withHost((string) ($input->getOption('host') ?? $socket->host())); + + $this->serverConfiguration->changeDefaultSocket($socket); if (\filter_var($input->getOption('serve-static'), FILTER_VALIDATE_BOOLEAN)) { - $this->configuration->enableServingStaticFiles($input->getOption('public-dir')); + $this->serverConfiguration->enableServingStaticFiles($input->getOption('public-dir')); } $requestLimit = (int) $input->getArgument('requests'); @@ -99,44 +108,78 @@ protected function execute(InputInterface $input, OutputInterface $output): void throw new InvalidArgumentException('Request limit must be greater than 0'); } - $trustedHosts = ServerUtils::decodeStringAsSet($_SERVER['APP_TRUSTED_HOSTS']); - $trustedProxies = ServerUtils::decodeStringAsSet($_SERVER['APP_TRUSTED_PROXIES']); - - $this->driver->boot([ + $runtimeConfiguration = [ 'symfonyStyle' => $io, 'requestLimit' => $requestLimit, - 'trustedHosts' => $trustedHosts, - 'trustedProxies' => $trustedProxies, - ]); - - $this->server->setup($this->configuration); - - $rows = [ - ['env', $this->kernel->getEnvironment()], - ['debug', \var_export($this->kernel->isDebug(), true)], - ['worker_count', $this->configuration->getWorkerCount()], - ['memory_limit', ServerUtils::formatBytes(ServerUtils::getMaxMemory())], - ['request_limit', $requestLimit > 0 ? $requestLimit : -1], - ['trusted_hosts', \implode(', ', $trustedHosts)], - ['trusted_proxies', \implode(', ', $trustedProxies)], + 'trustedHosts' => ServerUtils::decodeStringAsSet($_SERVER['APP_TRUSTED_HOSTS']), + 'trustedProxies' => ServerUtils::decodeStringAsSet($_SERVER['APP_TRUSTED_PROXIES']), ]; - if ($this->configuration->hasPublicDir()) { - $rows[] = ['public_dir', $this->configuration->getPublicDir()]; + if (\in_array('*', $runtimeConfiguration['trustedProxies'], true)) { + $runtimeConfiguration['trustAllProxies'] = true; + $runtimeConfiguration['trustedProxies'] = \array_filter($runtimeConfiguration['trustedProxies'], function (string $trustedProxy): bool { + return '*' !== $trustedProxy; + }); } - $io->success(\sprintf('Swoole HTTP Server started on http://%s:%d', $this->configuration->getHost(), $this->configuration->getPort())); - $io->table(['Configuration', 'Values'], $rows); + $this->server->attach($this->serverFactory->make( + $this->serverConfiguration->getDefaultSocket(), + $this->serverConfiguration->getRunningMode() + )); + $this->bootManager->boot($runtimeConfiguration); + + $io->success(\sprintf('Swoole HTTP Server started on http://%s', $this->serverConfiguration->getDefaultSocket()->addressPort())); + $this->printServerConfiguration($io, $runtimeConfiguration); + // TODO: Remove or improve before release if ($this->kernel->isDebug()) { - dump($this->configuration); - dump($this->configuration->getSwooleSettings()); + dump($this->serverConfiguration, $this->serverConfiguration->getSwooleSettings()); } - if ($this->server->start($this->driver)) { + if ($this->server->start()) { $io->success('Swoole HTTP Server has been successfully shutdown.'); } else { $io->error('Failure during starting Swoole HTTP Server.'); } } + + /** + * Xdebug must be disabled when using swoole due to possibility of core dump. + */ + private function ensureXdebugDisabled(): void + { + $xdebug = new XdebugHandler('swoole'); + $xdebug->check(); + unset($xdebug); + } + + /** + * @param SymfonyStyle $io + * @param array $runtimeConfiguration + * + * @throws \Assert\AssertionFailedException + */ + private function printServerConfiguration(SymfonyStyle $io, array $runtimeConfiguration): void + { + $rows = [ + ['env', $this->kernel->getEnvironment()], + ['debug', \var_export($this->kernel->isDebug(), true)], + ['worker_count', $this->serverConfiguration->getWorkerCount()], + ['memory_limit', ServerUtils::formatBytes(ServerUtils::getMaxMemory())], + ['request_limit', $runtimeConfiguration['requestLimit'] > 0 ? $runtimeConfiguration['requestLimit'] : -1], + ['trusted_hosts', \implode(', ', $runtimeConfiguration['trustedHosts'])], + ]; + + if (isset($runtimeConfiguration['trustAllProxies'])) { + $rows[] = ['trusted_proxies', '*']; + } else { + $rows[] = ['trusted_proxies', \implode(', ', $runtimeConfiguration['trustedProxies'])]; + } + + if ($this->serverConfiguration->hasPublicDir()) { + $rows[] = ['public_dir', $this->serverConfiguration->getPublicDir()]; + } + + $io->table(['Configuration', 'Values'], $rows); + } } diff --git a/Command/ServerRunCommand.php b/Command/ServerRunCommand.php index 738406db..bcb82cea 100644 --- a/Command/ServerRunCommand.php +++ b/Command/ServerRunCommand.php @@ -7,7 +7,8 @@ use App\Bundle\SwooleBundle\Functions\ServerUtils; use App\Bundle\SwooleBundle\Server\HttpServer; use App\Bundle\SwooleBundle\Server\HttpServerConfiguration; -use App\Bundle\SwooleBundle\Server\RequestHandlerInterface; +use App\Bundle\SwooleBundle\Server\HttpServerFactory; +use App\Bundle\SwooleBundle\Server\Runtime\BootManager; use Composer\XdebugHandler\XdebugHandler; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -18,23 +19,31 @@ final class ServerRunCommand extends Command { - private $kernel; private $server; - private $configuration; - private $driver; + private $serverFactory; + private $serverConfiguration; + private $kernel; + private $bootManager; /** - * @param KernelInterface $kernel * @param HttpServer $server - * @param HttpServerConfiguration $configuration - * @param RequestHandlerInterface $driver + * @param HttpServerFactory $serverFactory + * @param HttpServerConfiguration $serverConfiguration + * @param KernelInterface $kernel + * @param BootManager $bootManager */ - public function __construct(KernelInterface $kernel, HttpServer $server, HttpServerConfiguration $configuration, RequestHandlerInterface $driver) - { - $this->kernel = $kernel; + public function __construct( + HttpServer $server, + HttpServerFactory $serverFactory, + HttpServerConfiguration $serverConfiguration, + KernelInterface $kernel, + BootManager $bootManager + ) { $this->server = $server; - $this->driver = $driver; - $this->configuration = $configuration; + $this->serverFactory = $serverFactory; + $this->serverConfiguration = $serverConfiguration; + $this->kernel = $kernel; + $this->bootManager = $bootManager; parent::__construct(); } @@ -46,7 +55,7 @@ public function __construct(KernelInterface $kernel, HttpServer $server, HttpSer */ private function getDefaultPublicDir(): string { - return $this->configuration->hasPublicDir() ? $this->configuration->getPublicDir() : \dirname($this->kernel->getRootDir()).'/public'; + return $this->serverConfiguration->hasPublicDir() ? $this->serverConfiguration->getPublicDir() : \dirname($this->kernel->getRootDir()).'/public'; } /** @@ -75,46 +84,86 @@ protected function configure(): void */ protected function execute(InputInterface $input, OutputInterface $output): void { - $xdebug = new XdebugHandler('swoole'); - $xdebug->check(); - unset($xdebug); + $this->ensureXdebugDisabled(); $io = new SymfonyStyle($input, $output); - $this->configuration->changeSocket( - (string) ($input->getOption('host') ?? $this->configuration->getHost()), - (int) ($input->getOption('port') ?? $this->configuration->getPort()) - ); + $socket = $this->serverConfiguration->getDefaultSocket(); + $socket = $socket + ->withPort((int) ($input->getOption('port') ?? $socket->port())) + ->withHost((string) ($input->getOption('host') ?? $socket->host())); + + $this->serverConfiguration->changeDefaultSocket($socket); if (\filter_var($input->getOption('serve-static'), FILTER_VALIDATE_BOOLEAN)) { - $this->configuration->enableServingStaticFiles($input->getOption('public-dir')); + $this->serverConfiguration->enableServingStaticFiles($input->getOption('public-dir')); } - $this->driver->boot([ + $runtimeConfiguration = [ + 'symfonyStyle' => $io, 'trustedHosts' => ServerUtils::decodeStringAsSet($_SERVER['APP_TRUSTED_HOSTS']), 'trustedProxies' => ServerUtils::decodeStringAsSet($_SERVER['APP_TRUSTED_PROXIES']), - ]); + ]; + + if (\in_array('*', $runtimeConfiguration['trustedProxies'], true)) { + $runtimeConfiguration['trustAllProxies'] = true; + $runtimeConfiguration['trustedProxies'] = \array_filter($runtimeConfiguration['trustedProxies'], function (string $trustedProxy): bool { + return '*' !== $trustedProxy; + }); + } + + $this->server->attach($this->serverFactory->make( + $this->serverConfiguration->getDefaultSocket(), + $this->serverConfiguration->getRunningMode() + )); + $this->bootManager->boot($runtimeConfiguration); + + $io->success(\sprintf('Swoole HTTP Server started on http://%s', $this->serverConfiguration->getDefaultSocket()->addressPort())); + $this->printServerConfiguration($io, $runtimeConfiguration); + + if ($this->server->start()) { + $io->success('Swoole HTTP Server has been successfully shutdown.'); + } else { + $io->error('Failure during starting Swoole HTTP Server.'); + } + } - $this->server->setup($this->configuration); + /** + * Xdebug must be disabled when using swoole due to possibility of core dump. + */ + private function ensureXdebugDisabled(): void + { + $xdebug = new XdebugHandler('swoole'); + $xdebug->check(); + unset($xdebug); + } + /** + * @param SymfonyStyle $io + * @param array $runtimeConfiguration + * + * @throws \Assert\AssertionFailedException + */ + private function printServerConfiguration(SymfonyStyle $io, array $runtimeConfiguration): void + { $rows = [ ['env', $this->kernel->getEnvironment()], ['debug', \var_export($this->kernel->isDebug(), true)], - ['worker_count', $this->configuration->getWorkerCount()], + ['worker_count', $this->serverConfiguration->getWorkerCount()], ['memory_limit', ServerUtils::formatBytes(ServerUtils::getMaxMemory())], + ['trusted_hosts', \implode(', ', $runtimeConfiguration['trustedHosts'])], ]; - if ($this->configuration->hasPublicDir()) { - $rows[] = ['public_dir', $this->configuration->getPublicDir()]; + if (isset($runtimeConfiguration['trustAllProxies'])) { + $rows[] = ['trusted_proxies', '*']; + } else { + $rows[] = ['trusted_proxies', \implode(', ', $runtimeConfiguration['trustedProxies'])]; } - $io->success(\sprintf('Swoole HTTP Server started on http://%s:%d', $this->configuration->getHost(), $this->configuration->getPort())); - $io->table(['Configuration', 'Values'], $rows); - - if ($this->server->start($this->driver)) { - $io->success('Swoole HTTP Server has been successfully shutdown.'); - } else { - $io->error('Failure during starting Swoole HTTP Server.'); + if ($this->serverConfiguration->hasPublicDir()) { + $rows[] = ['public_dir', $this->serverConfiguration->getPublicDir()]; } + + $io->table(['Configuration', 'Values'], $rows); } } diff --git a/DependencyInjection/Compiler/BootManagerPass.php b/DependencyInjection/Compiler/BootManagerPass.php new file mode 100644 index 00000000..79e028ca --- /dev/null +++ b/DependencyInjection/Compiler/BootManagerPass.php @@ -0,0 +1,31 @@ +has(BootManager::class)) { + return; + } + + $definition = $container->findDefinition(BootManager::class); + $taggedServices = $container->findTaggedServiceIds('swoole.bootable_service'); + + foreach ($taggedServices as $id => $tags) { + dump($id); + $definition->addMethodCall('addService', [new Reference($id)]); + } + } +} diff --git a/DependencyInjection/SwooleExtension.php b/DependencyInjection/SwooleExtension.php index 50e57959..996eaa57 100644 --- a/DependencyInjection/SwooleExtension.php +++ b/DependencyInjection/SwooleExtension.php @@ -9,13 +9,16 @@ use App\Bundle\SwooleBundle\Bridge\Symfony\HttpFoundation\RequestFactoryInterface; use App\Bundle\SwooleBundle\Bridge\Symfony\HttpFoundation\TrustAllProxiesRequestHandler; use App\Bundle\SwooleBundle\Bridge\Symfony\HttpKernel\DebugHttpKernelRequestHandler; -use App\Bundle\SwooleBundle\Server\AdvancedStaticFilesServer; +use App\Bundle\SwooleBundle\Server\Config\Socket; use App\Bundle\SwooleBundle\Server\HttpServerConfiguration; -use App\Bundle\SwooleBundle\Server\RequestHandlerInterface; +use App\Bundle\SwooleBundle\Server\RequestHandler\AdvancedStaticFilesServer; +use App\Bundle\SwooleBundle\Server\RequestHandler\RequestHandlerInterface; +use App\Bundle\SwooleBundle\Server\Runtime\BootableInterface; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\DependencyInjection\Reference; @@ -37,10 +40,14 @@ public function prepend(ContainerBuilder $container): void */ public function load(array $configs, ContainerBuilder $container): void { + $configuration = Configuration::fromTreeBuilder(); + $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('services.yaml'); - $configuration = Configuration::fromTreeBuilder(); + $container->registerForAutoconfiguration(BootableInterface::class) + ->addTag('swoole.bootable_service'); + $config = $this->processConfiguration($configuration, $configs); $this->registerHttpServer($config['http_server'], $container); @@ -81,6 +88,7 @@ private function registerHttpServerConfiguration(array $config, ContainerBuilder $container->register(AdvancedStaticFilesServer::class) ->addArgument(new Reference(AdvancedStaticFilesServer::class.'.inner')) ->setAutowired(true) + ->setAutoconfigured(true) ->setPublic(false) ->setDecoratedService(RequestHandlerInterface::class, null, -60); } @@ -92,12 +100,10 @@ private function registerHttpServerConfiguration(array $config, ContainerBuilder $settings['log_level'] = $container->getParameter('kernel.debug') ? 'debug' : 'notice'; } + $defaultSocket = new Definition(Socket::class, [$host, $port, $socketType, $sslEnabled]); $container->getDefinition(HttpServerConfiguration::class) - ->addArgument($host) - ->addArgument($port) + ->addArgument($defaultSocket) ->addArgument($runningMode) - ->addArgument($socketType) - ->addArgument($sslEnabled) ->addArgument($settings); } @@ -115,6 +121,7 @@ private function registerHttpServerServices(array $config, ContainerBuilder $con $container->register(CloudFrontRequestFactory::class) ->addArgument(new Reference(CloudFrontRequestFactory::class.'.inner')) ->setAutowired(true) + ->setAutoconfigured(true) ->setPublic(false) ->setDecoratedService(RequestFactoryInterface::class, null, -10); } @@ -125,6 +132,7 @@ private function registerHttpServerServices(array $config, ContainerBuilder $con $container->register(TrustAllProxiesRequestHandler::class) ->addArgument(new Reference(TrustAllProxiesRequestHandler::class.'.inner')) ->setAutowired(true) + ->setAutoconfigured(true) ->setPublic(false) ->setDecoratedService(RequestHandlerInterface::class, null, -10); } @@ -133,6 +141,7 @@ private function registerHttpServerServices(array $config, ContainerBuilder $con $container->register(EntityManagerHandler::class) ->addArgument(new Reference(EntityManagerHandler::class.'.inner')) ->setAutowired(true) + ->setAutoconfigured(true) ->setPublic(false) ->setDecoratedService(RequestHandlerInterface::class, null, -20); } @@ -141,6 +150,7 @@ private function registerHttpServerServices(array $config, ContainerBuilder $con $container->register(DebugHttpKernelRequestHandler::class) ->addArgument(new Reference(DebugHttpKernelRequestHandler::class.'.inner')) ->setAutowired(true) + ->setAutoconfigured(true) ->setPublic(false) ->setDecoratedService(RequestHandlerInterface::class, null, -50); } diff --git a/Resources/config/services.yaml b/Resources/config/services.yaml index 655639c1..8016ac01 100644 --- a/Resources/config/services.yaml +++ b/Resources/config/services.yaml @@ -7,29 +7,49 @@ services: 'App\Bundle\SwooleBundle\Component\AtomicCounter': factory: ['App\Bundle\SwooleBundle\Component\AtomicCounter', fromZero] + 'App\Bundle\SwooleBundle\Bridge\Symfony\HttpFoundation\SetRequestRuntimeConfiguration': + 'App\Bundle\SwooleBundle\Bridge\Symfony\HttpFoundation\RequestFactoryInterface': class: App\Bundle\SwooleBundle\Bridge\Symfony\HttpFoundation\RequestFactory 'App\Bundle\SwooleBundle\Bridge\Symfony\HttpFoundation\ResponseProcessorInterface': class: App\Bundle\SwooleBundle\Bridge\Symfony\HttpFoundation\ResponseProcessor - 'App\Bundle\SwooleBundle\Server\RequestHandlerInterface': + 'App\Bundle\SwooleBundle\Server\RequestHandler\RequestHandlerInterface': class: App\Bundle\SwooleBundle\Bridge\Symfony\HttpKernel\HttpKernelRequestHandler - 'App\Bundle\SwooleBundle\Server\LimitedRequestHandler': + 'App\Bundle\SwooleBundle\Server\RequestHandler\LimitedRequestHandler': + + 'App\Bundle\SwooleBundle\Server\Runtime\BootManager': + autoconfigure: false + + 'App\Bundle\SwooleBundle\Server\HttpServer': 'App\Bundle\SwooleBundle\Server\HttpServerConfiguration': - 'app.swoole.server.http_server.factory': - class: App\Bundle\SwooleBundle\Server\HttpServerFactory + 'App\Bundle\SwooleBundle\Server\Configurator\ConfiguratorInterface': + class: App\Bundle\SwooleBundle\Server\Configurator\WithHttpServerConfiguration - 'App\Bundle\SwooleBundle\Server\HttpServer': - arguments: ['@app.swoole.server.http_server.factory'] + 'App\Bundle\SwooleBundle\Server\Configurator\WithLimitedRequestHandler': + + 'App\Bundle\SwooleBundle\Server\Configurator\WithRequestHandler': + + 'app.swoole.server.http_server.normal_factory': + class: App\Bundle\SwooleBundle\Server\HttpServerFactory + arguments: + $configurator: '@App\Bundle\SwooleBundle\Server\Configurator\WithRequestHandler' 'App\Bundle\SwooleBundle\Command\ServerRunCommand': tags: ['console.command'] + arguments: + $serverFactory: '@app.swoole.server.http_server.normal_factory' + + 'app.swoole.server.http_server.limited_factory': + class: App\Bundle\SwooleBundle\Server\HttpServerFactory + arguments: + $configurator: '@App\Bundle\SwooleBundle\Server\Configurator\WithLimitedRequestHandler' 'App\Bundle\SwooleBundle\Command\ServerProfileCommand': tags: ['console.command'] arguments: - $driver: '@App\Bundle\SwooleBundle\Server\LimitedRequestHandler' + $serverFactory: '@app.swoole.server.http_server.limited_factory' diff --git a/Server/Config/Socket.php b/Server/Config/Socket.php new file mode 100644 index 00000000..c93c5d39 --- /dev/null +++ b/Server/Config/Socket.php @@ -0,0 +1,168 @@ + SWOOLE_SOCK_TCP, + 'tcp_ipv6' => SWOOLE_SOCK_TCP6, + 'udp' => SWOOLE_SOCK_UDP, + 'udp_ipv6' => SWOOLE_SOCK_UDP6, + 'unix_dgram' => SWOOLE_SOCK_UNIX_DGRAM, + 'unix_stream' => SWOOLE_SOCK_UNIX_STREAM, + ]; + + /** + * @var string + */ + private $host; + + /** + * @var int + */ + private $port; + + /** + * @var string + */ + private $type; + + /** + * @var bool + */ + private $ssl; + + /** + * @param string $host + * @param int $port + * @param string $type + * @param bool $ssl + * + * @throws \Assert\AssertionFailedException + */ + public function __construct(string $host = 'localhost', int $port = 9501, string $type = 'tcp', bool $ssl = false) + { + $this->setHost($host); + $this->setPort($port); + + if ($ssl) { + Assertion::defined('SWOOLE_SSL', self::CONSTANT_SWOOLE_SSL_IS_NOT_DEFINED_ERROR_MESSAGE); + } + + $this->type = $type; + $this->ssl = $ssl; + } + + /** + * @param string $addressPort + * + * @return array values: + * - string host + * - int port + */ + private static function splitAddressPort(string $addressPort): array + { + $pos = \mb_strrpos($addressPort, ':'); + + if (false !== $pos) { + $host = \mb_substr($addressPort, 0, $pos); + if ('*' === $host) { + $host = '0.0.0.0'; + } + $port = \mb_substr($addressPort, $pos + 1); + } elseif (\ctype_digit($addressPort)) { + $host = '127.0.0.1'; + $port = $addressPort; + } else { + $host = $addressPort; + $port = 9501; + } + + return [$host, (int) $port]; + } + + /** + * @param string $addressPort + * @param string $socketType + * @param bool $enableSsl + * + * @throws \Assert\AssertionFailedException + * + * @return Socket + */ + public static function fromAddressPort(string $addressPort = '127.0.0.1:9501', string $socketType = 'tcp', bool $enableSsl = false): self + { + [$host, $port] = self::splitAddressPort($addressPort); + + return new self($host, $port, $socketType, $enableSsl); + } + + public function addressPort(): string + { + return \sprintf('%s:%d', $this->host, $this->port); + } + + public function host(): string + { + return $this->host; + } + + public function port(): int + { + return $this->port; + } + + public function type(): int + { + $resolvedSocketType = self::SWOOLE_SOCKET_TYPE[$this->type]; + + if ($this->ssl) { + if (!\defined('SWOOLE_SSL')) { + throw new InvalidArgumentException(self::CONSTANT_SWOOLE_SSL_IS_NOT_DEFINED_ERROR_MESSAGE); + } + $resolvedSocketType |= SWOOLE_SSL; + } + + return $resolvedSocketType; + } + + public function ssl(): bool + { + return $this->ssl; + } + + private function setPort(int $port): void + { + Assertion::between($port, 0, 65535, 'Port must be an integer between 0 and 65535'); + $this->port = $port; + } + + public function withPort(int $port): self + { + $self = clone $this; + $self->setPort($port); + $self->port = $port; + + return $self; + } + + public function withHost(string $host): self + { + $self = clone $this; + $self->setHost($host); + + return $self; + } + + private function setHost(string $host): void + { + $this->host = $host; + } +} diff --git a/Server/Configurator/ConfiguratorInterface.php b/Server/Configurator/ConfiguratorInterface.php new file mode 100644 index 00000000..c5f7e9e7 --- /dev/null +++ b/Server/Configurator/ConfiguratorInterface.php @@ -0,0 +1,15 @@ +configuration = $configuration; + } + + /** + * {@inheritdoc} + */ + public function configure(Server $server): void + { + $server->set($this->configuration->getSwooleSettings()); + + $defaultSocket = $this->configuration->getDefaultSocket(); + if (0 === $defaultSocket->port()) { + $this->configuration->changeDefaultSocket($defaultSocket->withPort($server->port)); + } + + // @todo $this->configuration->lock(); + } +} diff --git a/Server/Configurator/WithLimitedRequestHandler.php b/Server/Configurator/WithLimitedRequestHandler.php new file mode 100644 index 00000000..baff5274 --- /dev/null +++ b/Server/Configurator/WithLimitedRequestHandler.php @@ -0,0 +1,30 @@ +decorated = $decorated; + $this->requestHandler = $requestHandler; + } + + /** + * {@inheritdoc} + */ + public function configure(Server $server): void + { + $this->decorated->configure($server); + + $server->on('request', [$this->requestHandler, 'handle']); + } +} diff --git a/Server/Configurator/WithRequestHandler.php b/Server/Configurator/WithRequestHandler.php new file mode 100644 index 00000000..26020850 --- /dev/null +++ b/Server/Configurator/WithRequestHandler.php @@ -0,0 +1,30 @@ +decorated = $decorated; + $this->requestHandler = $requestHandler; + } + + /** + * {@inheritdoc} + */ + public function configure(Server $server): void + { + $this->decorated->configure($server); + + $server->on('request', [$this->requestHandler, 'handle']); + } +} diff --git a/Server/HttpServer.php b/Server/HttpServer.php index e4eacd39..3681159a 100644 --- a/Server/HttpServer.php +++ b/Server/HttpServer.php @@ -9,54 +9,46 @@ 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 */ private $server; - /** - * @var HttpServerFactory - */ - private $serverFactory; + private $running; + private $configuration; - public function __construct(HttpServerFactory $serverFactory) + public function __construct(HttpServerConfiguration $configuration, bool $running = false) { - $this->serverFactory = $serverFactory; + $this->running = $running; + $this->configuration = $configuration; } /** - * @param HttpServerConfiguration $configuration + * Attach already configured Swoole HTTP server instance. + * + * @param Server $server * * @throws \Assert\AssertionFailedException */ - public function setup(HttpServerConfiguration $configuration): void + public function attach(Server $server): void { - Assertion::null($this->server, 'Cannot setup swoole http server multiple times.'); - $server = $this->serverFactory->make($configuration); - - $server->set($configuration->getSwooleSettings()); - - if (0 === $configuration->getPort()) { - $configuration->changePort($server->port); - } + Assertion::null($this->server, 'Cannot attach Swoole HTTP server multiple times.'); $this->server = $server; } /** - * @param RequestHandlerInterface $driver - * * @throws \Assert\AssertionFailedException * * @return bool */ - public function start(RequestHandlerInterface $driver): bool + public function start(): bool { - Assertion::isInstanceOf($this->server, Server::class, 'Swoole HTTP Server has not been setup yet. Please use setup() method.'); + Assertion::isInstanceOf($this->server, Server::class, self::SWOOLE_HTTP_SERVER_HAS_NOT_BEEN_INITIALIZED_MESSAGE); - $this->server->on('request', [$driver, 'handle']); - - return $this->server->start(); + return $this->running = $this->server->start(); } /** @@ -64,8 +56,16 @@ public function start(RequestHandlerInterface $driver): bool */ public function shutdown(): void { - Assertion::isInstanceOf($this->server, Server::class, 'Swoole HTTP Server has not been setup yet. Please use setup() method.'); + Assertion::isInstanceOf($this->server, Server::class, self::SWOOLE_HTTP_SERVER_HAS_NOT_BEEN_INITIALIZED_MESSAGE); $this->server->shutdown(); } + + /** + * @return bool + */ + public function isRunning(): bool + { + return $this->running || $this->configuration->existsPidFile(); + } } diff --git a/Server/HttpServerConfiguration.php b/Server/HttpServerConfiguration.php index 4f19031d..5787551a 100644 --- a/Server/HttpServerConfiguration.php +++ b/Server/HttpServerConfiguration.php @@ -4,9 +4,8 @@ namespace App\Bundle\SwooleBundle\Server; +use App\Bundle\SwooleBundle\Server\Config\Socket; use Assert\Assertion; -use DomainException; -use InvalidArgumentException; final class HttpServerConfiguration { @@ -41,114 +40,41 @@ final class HttpServerConfiguration 'error' => SWOOLE_LOG_ERROR, ]; - private const SWOOLE_RUNNING_MODE = [ - 'reactor' => SWOOLE_BASE, - 'thread' => SWOOLE_THREAD, - 'process' => SWOOLE_PROCESS, - ]; - - private const SWOOLE_SOCKET_TYPE = [ - 'tcp' => SWOOLE_SOCK_TCP, - 'tcp_ipv6' => SWOOLE_SOCK_TCP6, - 'udp' => SWOOLE_SOCK_UDP, - 'udp_ipv6' => SWOOLE_SOCK_UDP6, - 'unix_dgram' => SWOOLE_SOCK_UNIX_DGRAM, - 'unix_stream' => SWOOLE_SOCK_UNIX_STREAM, - ]; - - private const PORT_MAX_VALUE = 65535; - private const PORT_MIN_VALUE = 0; - - /** - * @see https://github.com/swoole/swoole-docs/blob/master/modules/swoole-server/methods/construct.md#parameter - * - * @var string - * @var int $port - * @var string $runningMode - * @var string $socketType - * @var bool $sslEnabled - */ - private $host; - private $port; + private $defaultSocket; private $runningMode; - private $socketType; - private $sslEnabled; - - // Container for SWOOLE_HTTP_SERVER_CONFIGURATION values private $settings; /** - * @param string $host - * @param int $port + * @param Socket $defaultSocket * @param string $runningMode - * @param string $socketType - * @param bool $sslEnabled - * @param array $settings settings available: - * - reactor_count (default: number of cpu cores) - * - worker_count (default: 2 * number of cpu cores) - * - serve_static_files (default: false) - * - public_dir (default: '%kernel.root_dir%/public') + * @param array $settings settings available: + * - reactor_count (default: number of cpu cores) + * - worker_count (default: 2 * number of cpu cores) + * - serve_static_files (default: false) + * - public_dir (default: '%kernel.root_dir%/public') * * @throws \Assert\AssertionFailedException */ - public function __construct( - string $host = 'localhost', - int $port = 9501, - string $runningMode = 'process', - string $socketType = 'sock_tcp', - bool $sslEnabled = false, - array $settings = [] - ) { + public function __construct(Socket $defaultSocket, string $runningMode = 'process', array $settings = []) + { + $this->changeRunningMode($runningMode); + $this->changeDefaultSocket($defaultSocket); $this->initializeSettings($settings); - $this->changeSocket($host, $port, $runningMode, $socketType, $sslEnabled); } - /** - * @param string $host - * @param int $port - * @param null|string $runningMode - * @param null|string $socketType - * @param bool|null $sslEnabled - * - * @throws \Assert\AssertionFailedException - */ - public function changeSocket(string $host, int $port, ?string $runningMode = null, ?string $socketType = null, ?bool $sslEnabled = null): void + public function changeRunningMode(string $runningMode): void { - if (null === $runningMode) { - $runningMode = $this->runningMode ?? 'process'; - } - - if (null === $socketType) { - $socketType = $this->socketType ?? 'tcp'; - } - - if (null === $sslEnabled) { - $sslEnabled = $this->sslEnabled ?? false; - } - - Assertion::notBlank($host, 'Host cannot be blank.'); - Assertion::between($port, self::PORT_MIN_VALUE, self::PORT_MAX_VALUE, 'Provided port value "%s" is not between 0 and 65535.'); - Assertion::inArray($runningMode, \array_keys(self::SWOOLE_RUNNING_MODE)); - Assertion::inArray($socketType, \array_keys(self::SWOOLE_SOCKET_TYPE)); - - if ($sslEnabled) { - Assertion::defined('SWOOLE_SSL', 'Swoole SSL support is disabled. You must install php extension with SSL support enabled.'); - } + Assertion::inArray($runningMode, ['process', 'reactor', 'thread']); - $this->host = $host; $this->runningMode = $runningMode; - $this->port = $port; - $this->socketType = $socketType; - $this->sslEnabled = $sslEnabled; } - public function changePort(int $port): void + /** + * @param Socket $socket + */ + public function changeDefaultSocket(Socket $socket): void { - if (0 !== $this->port || $port <= self::PORT_MIN_VALUE || $port > self::PORT_MAX_VALUE) { - throw new DomainException('Method changePort() can be used directly, only if port originally was set to 0, which means random available port. Use changeSocket() instead.'); - } - - $this->port = $port; + $this->defaultSocket = $socket; } /** @@ -247,47 +173,36 @@ private function setSettings(array $settings): void } } - public function getHost(): string + public function getRunningMode(): string { - return $this->host; + return $this->runningMode; } - public function getPort(): int + public function hasPublicDir(): bool { - return $this->port; + return isset($this->settings['public_dir']); } - public function getSwooleRunningMode(): int + public function hasPidFile(): bool { - return self::SWOOLE_RUNNING_MODE[$this->runningMode]; + return isset($this->settings['public_dir']); } - /** - * @return int - */ - public function getSwooleSocketType(): int + public function existsPidFile(): bool { - $type = self::SWOOLE_SOCKET_TYPE[$this->socketType]; - - if (!$this->isSslEnabled()) { - return $type; - } - - if (!\defined('SWOOLE_SSL')) { - throw new InvalidArgumentException('Swoole SSL support is disabled. You must install php extension with SSL support enabled.'); - } - - return $type | SWOOLE_SSL; + return $this->hasPidFile() && \file_exists($this->getPidFile()); } - public function isSslEnabled(): bool + /** + * @throws \Assert\AssertionFailedException + * + * @return string + */ + public function getPidFile(): string { - return $this->sslEnabled; - } + Assertion::keyIsset($this->settings, 'pid_file', 'Setting "%s" is not set.'); - public function hasPublicDir(): bool - { - return isset($this->settings['public_dir']); + return $this->settings['pid_file']; } public function getWorkerCount(): int @@ -300,6 +215,11 @@ public function getReactorCount(): int return $this->settings['worker_count']; } + public function getDefaultSocket(): Socket + { + return $this->defaultSocket; + } + /** * @throws \Assert\AssertionFailedException * diff --git a/Server/HttpServerFactory.php b/Server/HttpServerFactory.php index d7641477..6143a43d 100644 --- a/Server/HttpServerFactory.php +++ b/Server/HttpServerFactory.php @@ -4,17 +4,42 @@ namespace App\Bundle\SwooleBundle\Server; +use App\Bundle\SwooleBundle\Server\Config\Socket; +use App\Bundle\SwooleBundle\Server\Configurator\ConfiguratorInterface; +use Assert\Assertion; use Swoole\Http\Server; class HttpServerFactory { - public function make(HttpServerConfiguration $configuration): Server + private const SWOOLE_RUNNING_MODE = [ + 'process' => SWOOLE_PROCESS, + 'reactor' => SWOOLE_BASE, +// 'thread' => SWOOLE_THREAD, + ]; + + private $configurator; + + public function __construct(ConfiguratorInterface $configurator) + { + $this->configurator = $configurator; + } + + /** + * @param Socket $socket + * @param string $runningMode + * + * @return Server + * + * @see https://github.com/swoole/swoole-docs/blob/master/modules/swoole-server/methods/construct.md#parameter + */ + public function make(Socket $socket, string $runningMode = 'process'): Server { - return new Server( - $configuration->getHost(), - $configuration->getPort(), - $configuration->getSwooleRunningMode(), - $configuration->getSwooleSocketType() - ); + Assertion::inArray($runningMode, \array_keys(self::SWOOLE_RUNNING_MODE)); + + $server = new Server($socket->host(), $socket->port(), self::SWOOLE_RUNNING_MODE[$runningMode], $socket->type()); + + $this->configurator->configure($server); + + return $server; } } diff --git a/Server/AdvancedStaticFilesServer.php b/Server/RequestHandler/AdvancedStaticFilesServer.php similarity index 96% rename from Server/AdvancedStaticFilesServer.php rename to Server/RequestHandler/AdvancedStaticFilesServer.php index 2dfad43b..6e01bde2 100644 --- a/Server/AdvancedStaticFilesServer.php +++ b/Server/RequestHandler/AdvancedStaticFilesServer.php @@ -2,8 +2,10 @@ declare(strict_types=1); -namespace App\Bundle\SwooleBundle\Server; +namespace App\Bundle\SwooleBundle\Server\RequestHandler; +use App\Bundle\SwooleBundle\Server\HttpServerConfiguration; +use App\Bundle\SwooleBundle\Server\Runtime\BootableInterface; use RuntimeException; use Swoole\Http\Request; use Swoole\Http\Response; @@ -15,7 +17,7 @@ * * @see https://github.com/zendframework/zend-expressive-swoole/blob/8b33edb50732961cce9e980c10a5948636b98e4e/src/RequestHandlerSwooleRunner.php */ -final class AdvancedStaticFilesServer implements RequestHandlerInterface +final class AdvancedStaticFilesServer implements RequestHandlerInterface, BootableInterface { /** * Default static file extensions supported. diff --git a/Server/LimitedRequestHandler.php b/Server/RequestHandler/LimitedRequestHandler.php similarity index 88% rename from Server/LimitedRequestHandler.php rename to Server/RequestHandler/LimitedRequestHandler.php index 7f227243..5c2cc92d 100644 --- a/Server/LimitedRequestHandler.php +++ b/Server/RequestHandler/LimitedRequestHandler.php @@ -2,15 +2,17 @@ declare(strict_types=1); -namespace App\Bundle\SwooleBundle\Server; +namespace App\Bundle\SwooleBundle\Server\RequestHandler; use App\Bundle\SwooleBundle\Component\AtomicCounter; +use App\Bundle\SwooleBundle\Server\HttpServer; +use App\Bundle\SwooleBundle\Server\Runtime\BootableInterface; use InvalidArgumentException; use Swoole\Http\Request; use Swoole\Http\Response; use Symfony\Component\Console\Style\SymfonyStyle; -final class LimitedRequestHandler implements RequestHandlerInterface +final class LimitedRequestHandler implements RequestHandlerInterface, BootableInterface { private $requestLimit; private $server; @@ -32,15 +34,12 @@ public function boot(array $runtimeConfiguration = []): void { $this->requestLimit = (int) ($runtimeConfiguration['requestLimit'] ?? -1); $this->symfonyStyle = $runtimeConfiguration['symfonyStyle'] ?? null; - - $this->decorated->boot($runtimeConfiguration); } /** - * Handles swoole request and modifies swoole response accordingly. + * {@inheritdoc} * - * @param \Swoole\Http\Request $request - * @param \Swoole\Http\Response $response + * @throws \Assert\AssertionFailedException */ public function handle(Request $request, Response $response): void { diff --git a/Server/RequestHandlerInterface.php b/Server/RequestHandler/RequestHandlerInterface.php similarity index 63% rename from Server/RequestHandlerInterface.php rename to Server/RequestHandler/RequestHandlerInterface.php index e6f2b742..4f889187 100644 --- a/Server/RequestHandlerInterface.php +++ b/Server/RequestHandler/RequestHandlerInterface.php @@ -2,20 +2,13 @@ declare(strict_types=1); -namespace App\Bundle\SwooleBundle\Server; +namespace App\Bundle\SwooleBundle\Server\RequestHandler; use Swoole\Http\Request; use Swoole\Http\Response; interface RequestHandlerInterface { - /** - * Override driver configuration at runtime. - * - * @param array $runtimeConfiguration - */ - public function boot(array $runtimeConfiguration = []): void; - /** * Handles swoole request and modifies swoole response accordingly. * diff --git a/Server/Runtime/BootManager.php b/Server/Runtime/BootManager.php new file mode 100644 index 00000000..9b343c96 --- /dev/null +++ b/Server/Runtime/BootManager.php @@ -0,0 +1,50 @@ +booted = $booted; + foreach ($services as $service) { + $this->addService($service); + } + } + + /** + * {@inheritdoc} + * + * @throws \Assert\AssertionFailedException When already booted + */ + public function boot(array $runtimeConfiguration = []): void + { + Assertion::false($this->booted, 'Boot method has already been called. Cannot boot services multiple times.'); + $this->booted = true; + + $booted = []; + + /** + * @var int + * @var BootableInterface $service + */ + foreach ($this->services as $id => $service) { + if (!isset($booted[$id])) { + $service->boot($runtimeConfiguration); + $booted[$id] = true; + } + } + } + + public function addService(BootableInterface $service): void + { + $this->services[\spl_object_id($service)] = $service; + } +} diff --git a/Server/Runtime/BootableInterface.php b/Server/Runtime/BootableInterface.php new file mode 100644 index 00000000..e1931305 --- /dev/null +++ b/Server/Runtime/BootableInterface.php @@ -0,0 +1,15 @@ +addCompilerPass(new BootManagerPass()); + } }