Skip to content
This repository has been archived by the owner on Jan 17, 2022. It is now read-only.

Commit

Permalink
feat(daemon-mode): Daemonize Swoole HTTP server
Browse files Browse the repository at this point in the history
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)
```
  • Loading branch information
k911 committed Oct 7, 2018
1 parent 32f9776 commit 5e3bb3a
Show file tree
Hide file tree
Showing 12 changed files with 436 additions and 127 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/vendor
swoole.pid
.php_cs.cache
clover.xml
180 changes: 90 additions & 90 deletions composer.lock

Large diffs are not rendered by default.

32 changes: 24 additions & 8 deletions src/Bridge/Symfony/Bundle/Command/AbstractServerStartCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ abstract class AbstractServerStartCommand extends Command
private $serverFactory;
private $serverConfiguration;
private $bootManager;
private $parameterBag;
protected $parameterBag;

/**
* @param HttpServer $server
Expand Down Expand Up @@ -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'));
}

/**
Expand All @@ -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()
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -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.');
}
}
}
65 changes: 65 additions & 0 deletions src/Bridge/Symfony/Bundle/Command/ServerReloadCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace K911\Swoole\Bridge\Symfony\Bundle\Command;

use K911\Swoole\Server\HttpServer;
use K911\Swoole\Server\HttpServerConfiguration;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Throwable;

final class ServerReloadCommand extends Command
{
private $server;
private $serverConfiguration;
private $parameterBag;

public function __construct(
HttpServer $server,
HttpServerConfiguration $serverConfiguration,
ParameterBagInterface $parameterBag)
{
$this->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');
}
}
2 changes: 1 addition & 1 deletion src/Bridge/Symfony/Bundle/Command/ServerRunCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
69 changes: 69 additions & 0 deletions src/Bridge/Symfony/Bundle/Command/ServerStartCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

namespace K911\Swoole\Bridge\Symfony\Bundle\Command;

use K911\Swoole\Server\HttpServer;
use K911\Swoole\Server\HttpServerConfiguration;
use RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Component\Console\Style\OutputStyle;
use Symfony\Component\Console\Style\SymfonyStyle;
use function K911\Swoole\get_object_property;

final class ServerStartCommand extends AbstractServerStartCommand
{
/**
* {@inheritdoc}
*/
protected function configure(): void
{
$this->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);
}
}
65 changes: 65 additions & 0 deletions src/Bridge/Symfony/Bundle/Command/ServerStopCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace K911\Swoole\Bridge\Symfony\Bundle\Command;

use K911\Swoole\Server\HttpServer;
use K911\Swoole\Server\HttpServerConfiguration;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Throwable;

final class ServerStopCommand extends Command
{
private $server;
private $serverConfiguration;
private $parameterBag;

public function __construct(
HttpServer $server,
HttpServerConfiguration $serverConfiguration,
ParameterBagInterface $parameterBag)
{
$this->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');
}
}
11 changes: 11 additions & 0 deletions src/Bridge/Symfony/Bundle/Resources/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
43 changes: 37 additions & 6 deletions src/Server/HttpServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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();
}
Expand All @@ -56,16 +57,46 @@ 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.');
}
}

/**
* @return bool
*/
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;
}
}
}
Loading

0 comments on commit 5e3bb3a

Please sign in to comment.