惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Cisco Blogs
NISL@THU
NISL@THU
G
GRAHAM CLULEY
T
Threatpost
I
Intezer
D
Darknet – Hacking Tools, Hacker News & Cyber Security
P
Proofpoint News Feed
L
Lohrmann on Cybersecurity
Cisco Talos Blog
Cisco Talos Blog
P
Privacy & Cybersecurity Law Blog
Security Latest
Security Latest
P
Palo Alto Networks Blog
L
LINUX DO - 热门话题
Cyberwarzone
Cyberwarzone
AI
AI
Help Net Security
Help Net Security
Forbes - Security
Forbes - Security
T
The Exploit Database - CXSecurity.com
月光博客
月光博客
The GitHub Blog
The GitHub Blog
aimingoo的专栏
aimingoo的专栏
C
CERT Recently Published Vulnerability Notes
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
N
News and Events Feed by Topic
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Scott Helme
Scott Helme
A
About on SuperTechFans
N
Netflix TechBlog - Medium
TaoSecurity Blog
TaoSecurity Blog
V
V2EX
MongoDB | Blog
MongoDB | Blog
AWS News Blog
AWS News Blog
Google DeepMind News
Google DeepMind News
Google Online Security Blog
Google Online Security Blog
O
OpenAI News
Y
Y Combinator Blog
S
Securelist
GbyAI
GbyAI
D
Docker
SecWiki News
SecWiki News
The Hacker News
The Hacker News
有赞技术团队
有赞技术团队
T
Tenable Blog
WordPress大学
WordPress大学
S
SegmentFault 最新的问题
P
Privacy International News Feed
S
Security Affairs
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Hacker News - Newest:
Hacker News - Newest: "LLM"
H
Hackread – Cybersecurity News, Data Breaches, AI and More

流动

对口型视频合成方案对比:Wav2Lip、VideoReTalking 与 MuseTalk 2026 音色克隆方案对比:IndexTTS-2、CosyVoice、GPT-SoVITS、Fish Speech、VoxCPM 部署与实测 AI Agent折腾记(OpenClaw / Hermes Agent) 我的2025年 大连之行 回家收麦 六一儿童节爬长城 Golang database/sql 数据库断线自动重连机制解析 Golang默认Http Client导致的cannot assign requested address错误 清明踏春,爬山看海 购入小牛G400T电动车 北京的三月飞雪 wrenAI本地LLM模型部署 天津一日游 2024年终总结 停止使用staticfile.org服务 使用 ImageMagick 自动添加水印,保护图片版权 如何注册一个.sol域名 奥森公园半日游 昌平42公里骑行绿道打卡 十月一日爬慕田峪长城 当Hugo遇上AVIF,优化图片加载 博客被恶意镜像 Github Pages 部署流程解析 搭建个人锻炼页面 你好 Follow 中秋爬山 Google Adsense的审核之旅 让你的IPFS站点持久在线:接入Filebase的Names(IPNS)服务 一次简短的青岛之行 解决 "undeclared name: any (requires version go1.18 or later)" 编译错误 搭建自托管IPFS Gateway服务,替代Cloudflare的IPFS Gateway 302跳转的跨域问题(CORS) GORM增加sqlcommenter特性 源码分析:GORM是如何生成sql的 工银亚洲网银密码重置 加速Cloudflare访问 2023年终总结 2023年12月北京暴雪记录 使用Hugo实现响应式和优化的图片 加速Google Analytics 使用Google Indexing API加速博客收录 在Netlify上部署Twikoo评论系统 利用Github Actions定时抓取微博 北大口腔牙周刮治记录 故乡回忆之旅 解决Golang使用go get安装包后找不到可执行文件的问题 修正Hugo的JSON Feed格式 我的学车之路 将博客部署到星际文件系统(IPFS) 新冠疫情后的第一个春节 第一次清理键盘 2022年终总结 去掉Cloudflare烦人的email-decode.min.js请求 累计布局偏移修复方案改进 —— 自动生成图片宽高 优化博客的累计布局偏移(CLS)问题 将博客部署到Cloudflare Pages 奥林匹克公园向日葵之旅 记第二次洗牙 记录2022年海淀幼升小 Golang解析json的一个问题 疫情下的生活 整理下博客的一些调整 疫情下的五一假期 自己动手,更换thinkpad x1硬盘 二刷百望山 带娃游颐和园 博客架构说明 难得的清明假期 十一年的等待,终于拿到了liudon.com域名 被隔离的一周 mysql中字符串和整型自动转换的问题 一次惊心动魄的Mysql更新操作 如何在北京公积金网站上修改婚姻状况 PHP7.2编译安装后没有php.ini文件的问题 检测网站支持的SSL/TLS协议版本 记一次难忘的手术经历 十一假期经历 Swoft 框架运行分析(四) —— EventProcessor模块分析 一个git submodule update引发的问题 一个Curl的耗时长的问题 Swoft 框架运行分析(三) —— BeanProcessor模块分析 Swoft 框架运行分析(二) —— AnnotationProcessor模块分析 Swoft 框架运行分析(一) BCMath 与 科学计数 Flink Could Not Resolve Resourcemanager Address 解决Sublime Text安装包时"There Are No Packages Available for Installation"的报错 关于本站 2019,新开始
Swoft 框架运行分析(五) —— ConsoleProcessor模块分析
Liudon · 2019-09-26 · via 流动

这里以Swoft启动http server为例。

php bin/swoft http:start

执行上述命令,启动http server。

在前面第一篇文章的时候,提到了如何启动http服务。

今天我们就来看一下http服务是如何启动的,具体实现就在ConsoleProcess这个模块。

/**
    * Handle console
    * @return bool
    * @throws ReflectionException
    * @throws ContainerException
    */
public function handle(): bool
{
    if (!$this->application->beforeConsole()) {
        return false;
    }

    /** @var Router $router */
    $router = bean('cliRouter');

    // Register console routes
    CommandRegister::register($router);

    CLog::info(
        'Console command route registered (group %d, command %d)',
        $router->groupCount(),
        $router->count()
    );

    // Run console application
    bean('cliApp')->run();

    return $this->application->afterConsole();
}

这里调用了bean方法获取Bean实例,定义见swoft-component-2.0.5\src\bean\src\Helper\Functions.php

if (!function_exists('bean')) {
    /**
     * Get bean by name
     *
     * @param string $name Bean name Or alias Or class name
     *
     * @return object|string|mixed
     */
    function bean(string $name)
    {
        if (BeanFactory::isSingleton('config')) {
            return BeanFactory::getBean($name);
        }

        return sprintf('${%s}', $name);
    }
}

这里调用了BeanFactorygetBean方法。

/**
    * Get object by name
    *
    * @param string $name Bean name Or alias Or class name
    *
    * @return object|mixed
    */
public static function getBean(string $name)
{
    return Container::getInstance()->get($name);
}

最终调用的是Swoft\Bean\Container下的get方法。

/**
    * Finds an entry of the container by its identifier and returns it.
    *
    * @param string $id Bean name Or alias Or class name
    *
    * When class name will return all of instance for class name
    *
    * @return object
    * @throws InvalidArgumentException
    */
public function get($id)
{
    // It is singleton
    if (isset($this->singletonPool[$id])) {
        return $this->singletonPool[$id];
    }

    // Prototype by clone
    if (isset($this->prototypePool[$id])) {
        return clone $this->prototypePool[$id];
    }

    // Alias name
    $aliasId = $this->aliases[$id] ?? '';
    if ($aliasId) {
        return $this->get($aliasId);
    }

    // Class name
    $classNames = $this->classNames[$id] ?? [];
    if ($classNames) {
        $id = end($classNames);
        return $this->get($id);
    }

    // Interface
    if (interface_exists($id)) {
        $id = InterfaceRegister::getInterfaceInjectBean($id);
        return $this->get($id);
    }

    // Not defined
    if (!isset($this->objectDefinitions[$id])) {
        throw new InvalidArgumentException(sprintf('The bean of %s is not defined', $id));
    }

    /* @var ObjectDefinition $objectDefinition */
    $objectDefinition = $this->objectDefinitions[$id];

    // Prototype
    return $this->safeNewBean($objectDefinition->getName());
}

获取对应的ObjectDefinition实例,然后调用safeNewBean方法。

/**
    * Secure creation of beans
    *
    * @param string $beanName
    * @param string $id
    *
    * @return object|mixed
    */
private function safeNewBean(string $beanName, string $id = '')
{
    try {
        return $this->newBean($beanName, $id);
    } catch (Throwable $e) {
        throw new InvalidArgumentException($e->getMessage(), 500, $e);
    }
}

这里又调用了newBean方法,在上一篇文章里我们已经讲过这个方法,这里会返回实例化后的Bean类。

cliRouter对应的类是说明呢?这个定义在swoft-component-2.0.5\src\console\src\AutoLoader.php里。

/**
    * {@inheritDoc}
    */
public function beans(): array
{
    return [
        'cliApp'    => [
            'class'   => Application::class,
            'version' => '2.0.0'
        ],
        'cliRouter' => [
            'class' => Router::class,
        ],
        'cliDispatcher' => [
            'class' => ConsoleDispatcher::class,
        ],
    ];
}

所以$router = bean('cliRouter'),返回的是一个Swoft\Console\Router\Router类。

回到ConsoleProcessor类,接着看代码。

CommandRegister::register($router);

调用了CommandRegister类的register方法。


/**
    * @param Router $router
    * @throws ReflectionException
    */
public static function register(Router $router): void
{
    $maxLen  = 12;
    $groups  = [];
    $docOpts = [
        'allow' => ['example']
    ];
    $defInfo = [
        'example'     => '',
        'description' => 'No description message',
    ];

    foreach (self::$commands as $class => $mapping) {
        $names = [];
        $group = $mapping['group'];
        // Set ID aliases
        $router->setIdAliases($mapping['idAliases']);
        // Set group name aliases
        $router->setGroupAliases($group, $mapping['aliases']);

        $refInfo = Swoft::getReflection($class);
        $mhdInfo = $refInfo['methods'] ?? [];
        $grpOpts = $mapping['options'] ?? [];

        foreach ($mapping['commands'] as $method => $route) {
            // $method = $route['method'];
            $cmdDesc = $route['desc'];
            $command = $route['command'];

            $idLen = strlen($group . $command);
            if ($idLen > $maxLen) {
                $maxLen = $idLen;
            }

            $cmdExam = '';
            if (!empty($mhdInfo[$method]['comments'])) {
                $tagInfo = DocBlock::getTags($mhdInfo[$method]['comments'], $docOpts, $defInfo);
                $cmdDesc = $cmdDesc ?: Str::firstLine($tagInfo['description']);
                $cmdExam = $tagInfo['example'];
            }

            $route['group']   = $group;
            $route['desc']    = ucfirst($cmdDesc);
            $route['example'] = $cmdExam;
            $route['options'] = self::mergeOptions($grpOpts, $route['options']);
            // Append group option
            $route['enabled']   = $mapping['enabled'];
            $route['coroutine'] = $mapping['coroutine'];

            $router->map($group, $command, [$class, $method], $route);
            $names[] = $command;
        }

        $groupExam = '';
        $groupDesc = $mapping['desc'];
        if (!empty($refInfo['comments'])) {
            $tagInfo   = DocBlock::getTags($refInfo['comments'], $docOpts, $defInfo);
            $groupDesc = $groupDesc ?: Str::firstLine($tagInfo['description']);
            $groupExam = $tagInfo['example'];
        }

        $groups[$group] = [
            'names'   => $names,
            'desc'    => ucfirst($groupDesc),
            'class'   => $class,
            'alias'   => $mapping['alias'],
            'aliases' => $mapping['aliases'],
            'example' => $groupExam,
        ];
    }

    $router->setGroups($groups);
    // +1 because router->delimiter
    $router->setKeyWidth($maxLen + 1);
    // clear data
    self::$commands = [];
}

这里遍历了类属性$commands注册路由。

那么$commands这个属性是哪里来的呢?

既然开头我们说的是http服务是怎么启动的,这里我们就以http-server来举例,找到swoft-component-2.0.5\src\http-server\src\Command\HttpServerCommand.php文件。

<?php declare(strict_types=1);

namespace Swoft\Http\Server\Command;

use ReflectionException;
use Swoft;
use Swoft\Bean\Exception\ContainerException;
use Swoft\Console\Annotation\Mapping\Command;
use Swoft\Console\Annotation\Mapping\CommandMapping;
use Swoft\Console\Annotation\Mapping\CommandOption;
use Swoft\Console\Helper\Show;
use Swoft\Http\Server\HttpServer;
use Swoft\Server\Command\BaseServerCommand;
use Swoft\Server\Exception\ServerException;
use function bean;
use function input;
use function output;

/**
 * Provide some commands to manage the swoft HTTP server
 *
 * @since 2.0
 *
 * @Command("http", alias="httpsrv", coroutine=false)
 * @example
 *  {fullCmd}:start     Start the http server
 *  {fullCmd}:stop      Stop the http server
 */
class HttpServerCommand extends BaseServerCommand
{
    /**
     * Start the http server
     *
     * @CommandMapping(usage="{fullCommand} [-d|--daemon]")
     * @CommandOption("daemon", short="d", desc="Run server on the background", type="bool", default="false")
     *
     * @throws ReflectionException
     * @throws ContainerException
     * @throws ServerException
     * @example
     *   {fullCommand}
     *   {fullCommand} -d
     *
     */
    public function start(): void
    {
        $server = $this->createServer();

        // Check if it has started
        if ($server->isRunning()) {
            $masterPid = $server->getPid();
            output()->writeln("<error>The HTTP server have been running!(PID: {$masterPid})</error>");
            return;
        }

        // Startup settings
        $this->configStartOption($server);

        $settings = $server->getSetting();
        // Setting
        $workerNum = $settings['worker_num'];

        // Server startup parameters
        $mainHost = $server->getHost();
        $mainPort = $server->getPort();
        $modeName = $server->getModeName();
        $typeName = $server->getTypeName();

        // Http
        $panel = [
            'HTTP' => [
                'listen' => $mainHost . ':' . $mainPort,
                'type'   => $typeName,
                'mode'   => $modeName,
                'worker' => $workerNum,
            ],
        ];

        // Port Listeners
        $panel = $this->appendPortsToPanel($server, $panel);

        Show::panel($panel);

        output()->writeln('<success>HTTP server start success !</success>');

        // Start the server
        $server->start();
    }

    /**
     * Reload worker processes
     *
     * @CommandMapping(usage="{fullCommand} [-t]")
     * @CommandOption("t", desc="Only to reload task processes, default to reload worker and task")
     *
     * @throws ReflectionException
     * @throws ContainerException
     */
    public function reload(): void
    {
        $server = $this->createServer();
        $script = input()->getScript();

        // Check if it has started
        if (!$server->isRunning()) {
            output()->writeln('<error>The HTTP server is not running! cannot reload</error>');
            return;
        }

        output()->writef('<info>Server %s is reloading</info>', $script);

        if ($reloadTask = input()->hasOpt('t')) {
            Show::notice('Will only reload task worker');
        }

        if (!$server->reload($reloadTask)) {
            Show::error('The swoole server worker process reload fail!');
            return;
        }

        output()->writef('<success>HTTP server %s reload success</success>', $script);
    }

    /**
     * Stop the currently running server
     *
     * @CommandMapping()
     *
     * @throws ReflectionException
     * @throws ContainerException
     */
    public function stop(): void
    {
        $server = $this->createServer();

        // Check if it has started
        if (!$server->isRunning()) {
            output()->writeln('<error>The HTTP server is not running! cannot stop.</error>');
            return;
        }

        // Do stopping.
        $server->stop();
    }

    /**
     * Restart the http server
     *
     * @CommandMapping(usage="{fullCommand} [-d|--daemon]",)
     * @CommandOption("daemon", short="d", desc="Run server on the background")
     *
     * @throws ReflectionException
     * @throws ContainerException
     * @example
     *  {fullCommand}
     *  {fullCommand} -d
     */
    public function restart(): void
    {
        $server = $this->createServer();

        // Check if it has started
        if ($server->isRunning()) {
            $success = $server->stop();

            if (!$success) {
                output()->error('Stop the old server failed!');
                return;
            }
        }

        output()->writef('<success>Server HTTP restart success !</success>');
        $server->startWithDaemonize();
    }

    /**
     * @return HttpServer
     * @throws ReflectionException
     * @throws ContainerException
     */
    private function createServer(): HttpServer
    {
        $script  = input()->getScript();
        $command = $this->getFullCommand();

        /** @var HttpServer $server */
        $server = bean('httpServer');
        $server->setScriptFile(Swoft::app()->getPath($script));
        $server->setFullCommand($command);

        return $server;
    }
}

通过Swoft文档,我们可以看到这里分别使用了类注解和方法注解。

@Command("http", alias="httpsrv", coroutine=false)
@CommandMapping(usage="{fullCommand} [-d|--daemon]")
@CommandOption("daemon", short="d", desc="Run server on the background", type="bool", default="false")
...

通过第二篇文章分析,我们知道这里会自动实例化对应的注解类。

这里以Swoft\Console\Annotation\Mapping\CommandMapping这个注解为例,对应的注解解析类为Swoft\Console\Annotation\Parser\CommandMappingParser

<?php declare(strict_types=1);

namespace Swoft\Console\Annotation\Parser;

use Swoft\Annotation\Annotation\Mapping\AnnotationParser;
use Swoft\Annotation\Annotation\Parser\Parser;
use Swoft\Annotation\Exception\AnnotationException;
use Swoft\Console\Annotation\Mapping\CommandMapping;
use Swoft\Console\CommandRegister;

/**
 * Class CommandMappingParser
 *
 * @since 2.0
 * @AnnotationParser(CommandMapping::class)
 */
class CommandMappingParser extends Parser
{
    /**
     * Parse object
     *
     * @param int            $type Class or Method or Property
     * @param CommandMapping $annotation Annotation object
     *
     * @return array
     * Return empty array is nothing to do!
     * When class type return [$beanName, $className, $scope, $alias, $size] is to inject bean
     * When property type return [$propertyValue, $isRef] is to reference value
     */
    public function parse(int $type, $annotation): array
    {
        if ($type !== self::TYPE_METHOD) {
            throw new AnnotationException('`@CommandMapping` must be defined on class method!');
        }

        $method = $this->methodName;

        // add route info for controller action
        CommandRegister::addRoute($this->className, $method, [
            'command' => $annotation->getName() ?: $method,
            'method'  => $method,
            'alias'   => $annotation->getAlias(),
            'aliases' => $annotation->getAliases(),
            'desc'    => $annotation->getDesc(),
            'usage'   => $annotation->getUsage(),
            // 'example' => $annotation->getExample(),
        ]);

        return [];
    }
}

看到这里,你应该可以猜到CommandRegister类的$commands是怎么来的了吧。

我们看下CommandRegister类的addRoute方法,验证下想法。

/**
    * @param string $class
    * @param string $method
    * @param array  $route
    *
    * @throws AnnotationException
    */
public static function addRoute(string $class, string $method, array $route): void
{
    self::checkClass($class);

    // init some keys
    $route['options']   = [];
    $route['arguments'] = [];
    // save
    self::$commands[$class]['commands'][$method] = $route;
}

bingo,跟我们猜想的一模一样,这下我们也知道CommandMapping这个注解是用来注册终端的路由信息。

回到ConsoleProcessor类,接着看代码。

CLog::info(
    'Console command route registered (group %d, command %d)',
    $router->groupCount(),
    $router->count()
);

打印日志。

// Run console application
bean('cliApp')->run();

感觉到了重头戏。

根据前面的代码,我们知道cliApp这个Bean实例对应的类是Swoft\Console\Application

/**
    * @return void
    * @throws ContainerException
    */
public function run(): void
{
    try {
        Swoft::trigger(ConsoleEvent::RUN_BEFORE, $this);

        // Prepare
        $this->prepare();

        // Get input command
        $inputCommand = $this->input->getCommand();

        if (!$inputCommand) {
            $this->filterSpecialOption();
        } else {
            $this->doRun($inputCommand);
        }

        Swoft::trigger(ConsoleEvent::RUN_AFTER, $this, $inputCommand);
    } catch (Throwable $e) {
        /** @var ConsoleErrorDispatcher $errDispatcher */
        $errDispatcher = BeanFactory::getSingleton(ConsoleErrorDispatcher::class);

        // Handle request error
        $errDispatcher->run($e);
    }
}

通过Swoft::trigger,注册了ConsoleEvent::RUN_BEFOREConsoleEvent::RUN_AFTER两个事件。

protected function prepare(): void
{
    $this->input  = \input();
    $this->output = \output();

    // load builtin comments vars
    $this->setCommentsVars($this->commentsVars());
}

prepare比较简单,这里声明了输入和输出两个类。注意哈,这个后面会用到。

$inputCommand = $this->input->getCommand();
if (!$inputCommand) {
    $this->filterSpecialOption();
} else {
    $this->doRun($inputCommand);
}

获取终端命令行下的输入,如果有输入执行doRun方法。

/**
    * @param string $inputCmd
    *
    * @return void
    * @throws ReflectionException
    * @throws ContainerException
    * @throws Throwable
    */
protected function doRun(string $inputCmd): void
{
    $output = $this->output;
    /* @var Router $router */
    $router = Swoft::getBean('cliRouter');
    $result = $router->match($inputCmd);

    // Command not found
    if ($result[0] === Router::NOT_FOUND) {
        $names = $router->getAllNames();
        $output->liteError("The entered command '{$inputCmd}' is not exists!");

        // find similar command names by similar_text()
        if ($similar = Arr::findSimilar($inputCmd, $names)) {
            $output->writef("\nMaybe what you mean is:\n    <info>%s</info>", implode(', ', $similar));
        } else {
            $this->showApplicationHelp(false);
        }
        return;
    }

    $info = $result[1];

    // Only input a group name, display help for the group
    if ($result[0] === Router::ONLY_GROUP) {
        $this->showGroupHelp($info['group']);
        return;
    }

    // Display help for a command
    if ($this->input->getSameOpt(['h', 'help'])) {
        $this->showCommandHelp($info);
        return;
    }

    // Parse default options and arguments
    $this->bindCommandFlags($info);
    $this->input->setCommandId($info['cmdId']);

    Swoft::triggerByArray(ConsoleEvent::DISPATCH_BEFORE, $this, $info);

    // Call command handler
    /** @var ConsoleDispatcher $dispatcher */
    $dispatcher = Swoft::getSingleton('cliDispatcher');
    $dispatcher->dispatch($info);

    Swoft::triggerByArray(ConsoleEvent::DISPATCH_AFTER, $this, $info);
}
$router = Swoft::getBean('cliRouter');
$result = $router->match($inputCmd);

获取cliRouter实例,根据输入匹配路由操作类。

/**
    * Match route by input command
    *
    * @param array $params [$route]
    *
    * @return array
    *
    * [
    *  status, info(array)
    * ]
    */
public function match(...$params): array
{
    $delimiter = $this->delimiter;
    $inputCmd  = trim($params[0], "$delimiter ");
    $noSepChar = strpos($inputCmd, $delimiter) === false;

    // If use command ID alias
    if ($noSepChar && isset($this->idAliases[$inputCmd])) {
        $inputCmd = $this->idAliases[$inputCmd];
        // Must re-check
        $noSepChar = strpos($inputCmd, $delimiter) === false;
    }

    if ($noSepChar && in_array($inputCmd, $this->defaultCommands, true)) {
        $group   = $this->defaultGroup;
        $command = $this->resolveCommandAlias($inputCmd);

        // Only a group name
    } elseif ($noSepChar) {
        $group = $this->resolveGroupAlias($inputCmd);

        if (isset($this->groups[$group])) {
            return [self::ONLY_GROUP, ['group' => $group]];
        }

        return [self::NOT_FOUND];
    } else {
        $nameList = explode($delimiter, $inputCmd, 2);

        if (count($nameList) === 2) {
            [$group, $command] = $nameList;
            // resolve command alias
            $command = $this->resolveCommandAlias($command);
        } else {
            $command = '';
            // $command = $this->defaultCommand;
            $group = $nameList[0];
        }
    }

    $group = $this->resolveGroupAlias($group);
    // build command ID
    $commandID = $this->buildCommandID($group, $command);

    if (isset($this->routes[$commandID])) {
        $info = $this->routes[$commandID];
        // append some info
        $info['cmdId'] = $commandID;

        return [self::FOUND, $info];
    }

    if ($group && isset($this->groups[$group])) {
        return [self::ONLY_GROUP, ['group' => $group]];
    }

    return [self::NOT_FOUND];
}

这里会返回匹配后的路由信息。

回到doRun方法。

// Command not found
if ($result[0] === Router::NOT_FOUND) {
    $names = $router->getAllNames();
    $output->liteError("The entered command '{$inputCmd}' is not exists!");

    // find similar command names by similar_text()
    if ($similar = Arr::findSimilar($inputCmd, $names)) {
        $output->writef("\nMaybe what you mean is:\n    <info>%s</info>", implode(', ', $similar));
    } else {
        $this->showApplicationHelp(false);
    }
    return;
}

$info = $result[1];

// Only input a group name, display help for the group
if ($result[0] === Router::ONLY_GROUP) {
    $this->showGroupHelp($info['group']);
    return;
}

// Display help for a command
if ($this->input->getSameOpt(['h', 'help'])) {
    $this->showCommandHelp($info);
    return;
}

根据返回的路由信息进行不同的处理。

// Parse default options and arguments
$this->bindCommandFlags($info);
$this->input->setCommandId($info['cmdId']);

Swoft::triggerByArray(ConsoleEvent::DISPATCH_BEFORE, $this, $info);

绑定默认参数,注册ConsoleEvent::DISPATCH_BEFORE事件。

// Call command handler
/** @var ConsoleDispatcher $dispatcher */
$dispatcher = Swoft::getSingleton('cliDispatcher');
$dispatcher->dispatch($info);

获取cliDispatcherBean实例,对应Swoft\Console\ConsoleDispatcher类,调用dispatch方法。

/**
    * @param array $params
    *
    * @return void
    * @throws ReflectionException
    * @throws Throwable
    */
public function dispatch(...$params): void
{
    $route = $params[0];
    // Handler info
    [$className, $method] = $route['handler'];

    // Bind method params
    $params = $this->getBindParams($className, $method);
    $object = Swoft::getSingleton($className);

    // Blocking running
    if (!$route['coroutine']) {
        $this->before(get_parent_class($object), $method);
        PhpHelper::call([$object, $method], ...$params);
        $this->after($method);
        return;
    }

    // Hook php io function
    Runtime::enableCoroutine();

    // If in unit test env, has been in coroutine.
    if (\defined('PHPUNIT_COMPOSER_INSTALL')) {
        $this->executeByCo($object, $method, $params);
        return;
    }

    // Coroutine running
    srun(function () use ($object, $method, $params) {
        $this->executeByCo($object, $method, $params);
    });
}

获取路由对应的类和方法,通过Swoft::getSingleton($className);实例化对象。

如果未开启协程,则用PhpHelper::call([$object, $method], ...$params);调用对应的方法。

开启协程的话,使用$this->executeByCo($object, $method, $params);调用对应的方法。

我们前面启动命令是php bin/swoft http:start,这里对应的类就是Swoft\Http\Server\Command\HttpServerCommand,方法就是start

/**
    * Start the http server
    *
    * @CommandMapping(usage="{fullCommand} [-d|--daemon]")
    * @CommandOption("daemon", short="d", desc="Run server on the background", type="bool", default="false")
    *
    * @throws ReflectionException
    * @throws ContainerException
    * @throws ServerException
    * @example
    *   {fullCommand}
    *   {fullCommand} -d
    *
    */
public function start(): void
{
    $server = $this->createServer();

    // Check if it has started
    if ($server->isRunning()) {
        $masterPid = $server->getPid();
        output()->writeln("<error>The HTTP server have been running!(PID: {$masterPid})</error>");
        return;
    }

    // Startup settings
    $this->configStartOption($server);

    $settings = $server->getSetting();
    // Setting
    $workerNum = $settings['worker_num'];

    // Server startup parameters
    $mainHost = $server->getHost();
    $mainPort = $server->getPort();
    $modeName = $server->getModeName();
    $typeName = $server->getTypeName();

    // Http
    $panel = [
        'HTTP' => [
            'listen' => $mainHost . ':' . $mainPort,
            'type'   => $typeName,
            'mode'   => $modeName,
            'worker' => $workerNum,
        ],
    ];

    // Port Listeners
    $panel = $this->appendPortsToPanel($server, $panel);

    Show::panel($panel);

    output()->writeln('<success>HTTP server start success !</success>');

    // Start the server
    $server->start();
}

这里先调用了createServer方法。

/**
    * @return HttpServer
    * @throws ReflectionException
    * @throws ContainerException
    */
private function createServer(): HttpServer
{
    $script  = input()->getScript();
    $command = $this->getFullCommand();

    /** @var HttpServer $server */
    $server = bean('httpServer');
    $server->setScriptFile(Swoft::app()->getPath($script));
    $server->setFullCommand($command);

    return $server;
}

获取httpServerBean实例。

框架定义在swoft-component-2.0.5\src\http-server\src\AutoLoader.php,这里声明了onRequest回调事件。

'httpServer'      => [
    'on' => [
        SwooleEvent::REQUEST => bean(RequestListener::class)
    ]
],

业务定义在swoft-2.0.5\app\bean.php

'httpServer'        => [
    'class'    => HttpServer::class,
    'port'     => 18306,
    'listener' => [
        'rpc' => bean('rpcServer')
    ],
    'process'  => [
//            'monitor' => bean(MonitorProcess::class)
//            'crontab' => bean(CrontabProcess::class)
    ],
    'on'       => [
//            SwooleEvent::TASK   => bean(SyncTaskListener::class),  // Enable sync task
        SwooleEvent::TASK   => bean(TaskListener::class),  // Enable task must task and finish event
        SwooleEvent::FINISH => bean(FinishListener::class)
    ],
    /* @see HttpServer::$setting */
    'setting'  => [
        'task_worker_num'       => 12,
        'task_enable_coroutine' => true
    ]
],

createServer返回的是一个Swoft\Http\Server\HttpServer实例。

回到HttpServerCommand类的start方法。

// Start the server
$server->start();

调用Swoft\Http\Server\HttpServer类的start方法。

/**
    * Start server
    *
    * @throws ServerException
    * @throws ContainerException
    */
public function start(): void
{
    $this->swooleServer = new \Swoole\Http\Server($this->host, $this->port, $this->mode, $this->type);
    $this->startSwoole();
}

声明Swoole\Http\Server对象,调用startSwoole方法。

Swoft\Http\Server\HttpServer类继承自Swoft\Server\Server类,startSwoole方法定义在这个类。

/**
    * Bind swoole event and start swoole server
    *
    * @throws ServerException
    * @throws Swoft\Bean\Exception\ContainerException
    */
protected function startSwoole(): void
{
    if (!$this->swooleServer) {
        throw new ServerException('You must to new server before start swoole!');
    }

    // Always enable coroutine hook on server
    CLog::info('Swoole\Runtime::enableCoroutine');
    Runtime::enableCoroutine();

    Swoft::trigger(ServerEvent::BEFORE_SETTING, $this);

    // Set settings
    $this->swooleServer->set($this->setting);
    // Update setting property
    // $this->setSetting($this->swooleServer->setting);

    // Before Add event
    Swoft::trigger(ServerEvent::BEFORE_ADDED_EVENT, $this);

    // Register events
    $defaultEvents = $this->defaultEvents();
    $swooleEvents  = array_merge($defaultEvents, $this->on);

    // Add events
    $this->addEvent($this->swooleServer, $swooleEvents, $defaultEvents);

    //After add event
    Swoft::trigger(ServerEvent::AFTER_ADDED_EVENT, $this);

    // Before listener
    Swoft::trigger(ServerEvent::BEFORE_ADDED_LISTENER, $this);

    // Add port listener
    $this->addListener();

    // Before bind process
    Swoft::trigger(ServerEvent::BEFORE_ADDED_PROCESS, $this);

    // Add Process
    Swoft::trigger(ServerEvent::ADDED_PROCESS, $this);

    // After bind process
    Swoft::trigger(ServerEvent::AFTER_ADDED_PROCESS, $this);

    // Trigger event
    Swoft::trigger(ServerEvent::BEFORE_START, $this, array_keys($swooleEvents));

    // Storage server instance
    self::$server = $this;

    // Start swoole server
    $this->swooleServer->start();
}
$this->swooleServer->set($this->setting);

设置Swoole运行配置。

// Register events
$defaultEvents = $this->defaultEvents();
$swooleEvents  = array_merge($defaultEvents, $this->on);

// Add events
$this->addEvent($this->swooleServer, $swooleEvents, $defaultEvents);

添加Swoole回调事件。

// Add port listener
$this->addListener();

监听端口。

// Start swoole server
$this->swooleServer->start();

启动Swoole\Http\Server服务。

现在服务已经启动了,那http请求是怎么被处理的呢?

这个我们下一篇再继续讲。