Files
kiri-http-server/ServerCommand.php
T
2026-07-20 11:15:46 +08:00

206 lines
5.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace Kiri\Server;
use Exception;
use Kiri;
use Kiri\Server\Abstracts\FileWatcher;
use Kiri\Server\Events\OnWorkerStart;
use Kiri\Events\EventProvider;
use Kiri\Events\EventDispatch;
use Kiri\Router\Router;
use Kiri\Server\Abstracts\AsyncServer;
use Kiri\Server\Events\OnShutdown;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use function config;
use Kiri\Server\Abstracts\HotReload;
use Kiri\Di\Inject\Container;
defined('ROUTER_TYPE_HTTP') or define('ROUTER_TYPE_HTTP', 'http');
defined('PID_PATH') or define('PID_PATH', APP_PATH . 'storage/server.pid');
/**
* Class Command
* @package Http
*/
class ServerCommand extends Command
{
#[Container(State::class)]
public State $state;
/**
* @var ContainerInterface
*/
#[Container(ContainerInterface::class)]
public ContainerInterface $container;
/**
* @var AsyncServer
*/
#[Container(AsyncServer::class)]
public AsyncServer $asyncServer;
/**
* @var EventDispatch
*/
#[Container(EventDispatch::class)]
public EventDispatch $eventDispatch;
/**
* @var EventProvider
*/
#[Container(EventProvider::class)]
public EventProvider $eventProvider;
/**
* @return void
*/
protected function configure(): void
{
$this->setName('sw:server')
->setDescription('server start|stop|reload|restart')
->addArgument('action', InputArgument::OPTIONAL, 'run action', 'start')
->addOption('daemon', 'd', InputOption::VALUE_NONE, 'is run daemonize');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
* @throws
*/
public function execute(InputInterface $input, OutputInterface $output): int
{
return match ($input->getArgument('action')) {
'restart' => $this->restart($input),
'stop' => $this->stop(),
'start' => $this->start($input),
default =>
throw new Exception('I don\'t know what I want to do.')
};
}
/**
* @param InputInterface $input
* @return int
* @throws
*/
protected function restart(InputInterface $input): int
{
$this->stop();
$this->start($input);
return 1;
}
/**
* @return int
* @throws
*/
protected function stop(): int
{
$configs = config('servers.server', []);
$instances = $this->asyncServer->sortService($configs['ports'] ?? []);
foreach ($instances as $config) {
$this->state->exit($config->port);
}
$this->eventDispatch->dispatch(new OnShutdown());
return 1;
}
/**
* @param InputInterface $input
* @return int
* @throws
*/
protected function start(InputInterface $input): int
{
$this->asyncServer->addProcess(config('process', []));
$hotReload = \config('servers.reload.hot', false) === true;
if ($hotReload && !$this->buildHotReloadArtifacts()) {
throw new Exception('Hot reload artifact build failed.');
}
if ($hotReload) {
$this->asyncServer->addProcess([FileWatcher::class]);
}
if (!$hotReload) {
// 非热更模式可在 Master 进程预扫描,Worker 通过 fork 继承扫描结果。
// 热更模式必须让新 Worker 自己加载业务类,避免继承旧类定义后无法重新声明。
di(Router::class)->scan_build_route();
}
$this->asyncServer->initCoreServers(config('servers.server', []), (int)$input->getOption('daemon'));
$this->asyncServer->start();
return 1;
}
/**
* 热更模式下不要在 Master 进程扫描业务类;先用独立 CLI 进程构建路由 artifact
* Worker 启动/重载时直接导入 artifact,避免多个 Worker 同时全量扫描导致 OOM。
* @return bool
*/
private function buildHotReloadArtifacts(): bool
{
$entry = $this->detectConsoleEntry();
if ($entry === null) {
\Kiri::getLogger()->println('hot reload build failed: entry script not found');
return false;
}
$php = PHP_BINARY ?: 'php';
$command = escapeshellarg($php) . ' ' . escapeshellarg($entry) . ' sw:file-build';
$output = [];
$returnCode = 0;
exec($command . ' 2>&1', $output, $returnCode);
foreach ($output as $line) {
if ($line !== '') {
\Kiri::getLogger()->println('[file-build] ' . $line);
}
}
return $returnCode === 0;
}
/**
* @return string|null
*/
private function detectConsoleEntry(): ?string
{
$candidates = [
$_SERVER['SCRIPT_FILENAME'] ?? '',
defined('APP_PATH') ? APP_PATH . 'kiri.php' : '',
defined('APP_PATH') ? APP_PATH . 'bin/kiri' : '',
defined('APP_PATH') ? APP_PATH . 'artisan' : '',
];
foreach ($candidates as $candidate) {
if (is_string($candidate) && $candidate !== '' && is_file($candidate)) {
return $candidate;
}
}
return null;
}
}