hyperf2.2 的 http-server 启动源码分析

入口文件 bin/hyperf.php

设置错误信息提示,内存使用限制和常量

ini_set('display_errors', 'on');
ini_set('display_startup_errors', 'on');
ini_set('memory_limit', '1G');
error_reporting(E_ALL);

! defined('BASE_PATH') && define('BASE_PATH', dirname(__DIR__, 1));
! defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', SWOOLE_HOOK_ALL);

调用 composer 自动加载

require BASE_PATH . '/vendor/autoload.php';

初始化所有的容器对象;

// vender/Hyperf/Di/ClassLoader.php
Hyperf\Di\ClassLoader::init()

1. 设定代理类所在位置

 if (! $proxyFileDirPath) {
    // This dir is the default proxy file dir path of Hyperf
    $proxyFileDirPath = BASE_PATH . '/runtime/container/proxy/';
 }

2. 设定配置目录

 if (! $configDir) {
     // This dir is the default proxy file dir path of Hyperf
     $configDir = BASE_PATH . '/config/';
 }

3. 设置扫描类

if (!$handler) {
    $handler = new PcntlScanHandler();
}

4. 注册自动加载

获取composer原来注册的所有自动加载函数spl_autoload_functions(),使用到注解的自动加载回调函数中$composerClassLoader->findFile($class),并替换 composer 中 ClassLoader类的原有注册函数,改为使用自身类的loadClass 方法,取消原来所有的注册,并在重写后重新注册

$loaders = spl_autoload_functions();

// Proxy the composer class loader
foreach ($loaders as &$loader) {
    $unregisterLoader = $loader;
    if (is_array($loader) && $loader[0] instanceof ComposerClassLoader) {
        /** @var ComposerClassLoader $composerClassLoader */
        $composerClassLoader = $loader[0];
        AnnotationRegistry::registerLoader(function ($class) use ($composerClassLoader) {
            return (bool) $composerClassLoader->findFile($class);
        });
        $loader[0] = new static($composerClassLoader, $proxyFileDirPath, $configDir, $handler);
    }
    spl_autoload_unregister($unregisterLoader);
}

unset($loader);

// Re-register the loaders
foreach ($loaders as $loader) {
    spl_autoload_register($loader);
}
4.1 替换原有 composer 中 ClassLoader 类,执行构造方法
public function __construct(ComposerClassLoader $classLoader, string $proxyFileDir, string $configDir, ScanHandlerInterface $handler)
{
    $this->setComposerClassLoader($classLoader);
    if (file_exists(BASE_PATH . '/.env')) {
        $this->loadDotenv();
    }

    // Scan by ScanConfig to generate the reflection class map
    $config = ScanConfig::instance($configDir);
    $classLoader->addClassMap($config->getClassMap());

    $scanner = new Scanner($this, $config, $handler);

    $this->proxies = $scanner->scan($this->getComposerClassLoader()->getClassMap(), $proxyFileDir);
}
4.1.1 加载 .env 配置
if (file_exists(BASE_PATH . '/.env')) {
    $this->loadDotenv();
}
4.1.2 初始化扫描配置
// ClassLoader.php
$config = ScanConfig::instance($configDir); /** $configDir = BASE_PATH . '/config/'; */
$classLoader->addClassMap($config->getClassMap()); // 在 composer 自动加载中添加额外的 classmap
  • 获取各个配置项
// ScanConfig.php
public static function instance(string $configDir): self
{
    if (self::$instance) {
        return self::$instance;
    }

    $configDir = rtrim($configDir, '/');

    [$config, $serverDependencies, $cacheable] = static::initConfigByFile($configDir); // 获取配置参数

    return self::$instance = new self(
        $cacheable,
        $configDir,
        $config['paths'] ?? [],
        $serverDependencies ?? [],
        $config['ignore_annotations'] ?? [],
        $config['global_imports'] ?? [],
        $config['collectors'] ?? [],
        $config['class_map'] ?? []
    );
}

initConfigByFile方法内部,会读取 composer.lock 文件,获取所有packagespackages-devextra->hyperf->config 字段,根据类名实例化各个ConfigProvider,执行__invoke方法,对执行结果的返回数组按照类别进行合并获取所有配置

  • extra 格式
"extra": {
    "branch-alias": {
        "dev-master": "2.2-dev"
    },
    "hyperf": {
        "config": "Hyperf\\Di\\ConfigProvider"
    }
}
  • $configFromProviders 合并后数据格式
// ScanConfig.php
if (class_exists(ProviderConfig::class)) {
    $configFromProviders = ProviderConfig::load();
}
[
    "dependencies" => [
        "Psr\SimpleCache\CacheInterface" => "Hyperf\Cache\Cache",
    ],
    "listeners" => [
        "Hyperf\Cache\Listener\DeleteListener",
    ],
    "annotations" => [
        "scan" => [
            "paths" => [
                "/mnt/d/work/docker/hyperf-skeleton/vendor/hyperf/cache/src",
            ]
        ],
        "collectors" => [
            "Hyperf\Cache\CacheListenerCollector",
        ]
    ],
    "ignore_annotations" => [
         "mixin"
    ]
]

提取配置项中的 dependencies数据,然后读取 $configDir . '/autoload/dependencies.php' 来替换原有的依赖, 最终生成 $serverDependencies

// ScanConfig.php
$serverDependencies = $configFromProviders['dependencies'] ?? [];
if (file_exists($configDir . '/autoload/dependencies.php')) {
    $definitions = include $configDir . '/autoload/dependencies.php';
    $serverDependencies = array_replace($serverDependencies, $definitions ?? []);
}

提取配置项中的 annotations数据,合并数据,然后读取 $configDir . '/autoload/annotations.php',再次合并数据,生成 $config

$config = static::allocateConfigValue($configFromProviders['annotations'] ?? [], $config);

// Load the config/autoload/annotations.php and merge the config
if (file_exists($configDir . '/autoload/annotations.php')) {
    $annotations = include $configDir . '/autoload/annotations.php';
    $config = static::allocateConfigValue($annotations, $config);
}

读取$configDir . '/config.php',合并annotations$config,并获取 $cacheable

// Load the config/config.php and merge the config
if (file_exists($configDir . '/config.php')) {
    $configContent = include $configDir . '/config.php';
    $appEnv = $configContent['app_env'] ?? 'dev';
    $cacheable = value($configContent['scan_cacheable'] ?? $appEnv === 'prod');
    if (isset($configContent['annotations'])) {
        $config = static::allocateConfigValue($configContent['annotations'], $config);
    }
}
4.1.3 获取注解扫描仪,开始扫描
$scanner = new Scanner($this, $config, $handler);
$this->proxies = $scanner->scan($this->getComposerClassLoader()->getClassMap(), $proxyFileDir);
  • 执行逻辑
  $scanned = $this->handler->scan(); // 进行fork,子进程是 false,父进程是true,子进程逻辑处理完之后再执行父进程逻辑
  if ($scanned->isScanned()) {
  return $this->deserializeCachedScanData($collectors);
  }
  // 下面的是子进程逻辑,之后的 exit 会触发 pcntl_wait($status),执行上面的父进程逻辑;
  ...
  exit;
  • pnctl fork 进程
public function scan(): Scanned
{
    $pid = pcntl_fork();
    // 父进程和子进程都会执行下面代码
    if ($pid == -1) {
        throw new Exception('The process fork failed');
    }
    if ($pid) {
        // 父进程执行逻辑
        pcntl_wait($status); // 挂起等待子进程完成后,才继续执行
        return new Scanned(true);
    }
    // 子进程执行逻辑
    return new Scanned(false);
}

5. 初始化懒加载配置

根据AST生成代理文件,讲这些代理类的自动加载以 prepend 参数调整到队列头

LazyLoader::bootstrap($configDir);

获取容器,获取 application 对象,启动程序

$container = require BASE_PATH . '/config/container.php';
/**
 * @var \Symfony\Component\Console\Application
 */
$application = $container->get(Hyperf\Contract\ApplicationInterface::class);
$application->run();

container 的获取对象方式

containerget方法用于获取对象,根据dependenciesclassmap 寻找对应的类,如果已经实例化直接返回,没有时调用 make 方法进行实例化,缓存后返回。

  • 实例化方法
// vendor\hyperf\di\src\Resolver\ObjectResolver.php
private function createInstance(ObjectDefinition $definition, array $parameters)
{
    ...
    try {
        $className = $definition->getClassName();
        $classReflection = ReflectionManager::reflectClass($className); // 获取类的反射
        $constructorInjection = $definition->getConstructorInjection();

        $args = $this->parameterResolver->resolveParameters($constructorInjection, $classReflection->getConstructor(), $parameters);
        $object = new $className(...$args); // 实例化类
    }
    ...
}
// vendor\hyperf\di\src\ReflectionManager.php
public static function reflectClass(string $className): ReflectionClass
{
    if (!isset(static::$container['class'][$className])) {
        if (!class_exists($className) && !interface_exists($className) && !trait_exists($className)) {
            throw new InvalidArgumentException("Class {$className} not exist");
        }
        static::$container['class'][$className] = new ReflectionClass($className);
    }
    return static::$container['class'][$className];
}
  • 装载扫描配置文件后获取到的所有依赖配置dependencies到容器中
// config\container.php
$container = new Container((new DefinitionSourceFactory(true))());

application 对象

默认为\Symfony\Component\Console\Application 对象, 获取所有的命令添加事件监听

// vendor\hyperf\framework\src\ApplicationFactory.php
$config = $container->get(ConfigInterface::class);
$commands = $config->get('commands', []); // 从所有组件的 `ConfigProvider.php` 中获取
// Append commands that defined by annotation.
$annotationCommands = [];
if (class_exists(AnnotationCollector::class) && class_exists(Command::class)) {
    $annotationCommands = AnnotationCollector::getClassesByAnnotation(Command::class);
    $annotationCommands = array_keys($annotationCommands);
}

$commands = array_unique(array_merge($commands, $annotationCommands));
$application = new Application();

if (isset($eventDispatcher) && class_exists(SymfonyEventDispatcher::class)) {
    $application->setDispatcher(new SymfonyEventDispatcher($eventDispatcher));
}

foreach ($commands as $command) {
    $application->add($container->get($command));
}
return $application;

php bin/hyperf.php start 命令

命令来自于 vendor\hyperf\server\src\Command\StartServer.php 文件

  • 检查 swoole 环境
$this->checkEnvironment($output);
  • 获取 server 对象,从配置中获取所有 server config,初始化 SwooleServer,如果有多个server时,添加额外的监听
// vendor\hyperf\server\src\Server.php
protected function initServers(ServerConfig $config)
{
    $servers = $this->sortServers($config->getServers());

    foreach ($servers as $server) {
        $name = $server->getName();
        $type = $server->getType();
        $host = $server->getHost();
        $port = $server->getPort();
        $sockType = $server->getSockType();
        $callbacks = $server->getCallbacks();

        if (! $this->server instanceof SwooleServer) {
            $this->server = $this->makeServer($type, $host, $port, $config->getMode(), $sockType);
            $callbacks = array_replace($this->defaultCallbacks(), $config->getCallbacks(), $callbacks);
            $this->registerSwooleEvents($this->server, $callbacks, $name);
            $this->server->set(array_replace($config->getSettings(), $server->getSettings()));
            ServerManager::add($name, [$type, current($this->server->ports)]);

            if (class_exists(BeforeMainServerStart::class)) {
                // Trigger BeforeMainServerStart event, this event only trigger once before main server start.
                $this->eventDispatcher->dispatch(new BeforeMainServerStart($this->server, $config->toArray()));
            }
        } else {
            /** @var bool|\Swoole\Server\Port $slaveServer */
            $slaveServer = $this->server->addlistener($host, $port, $sockType);
            if (! $slaveServer) {
                throw new \RuntimeException("Failed to listen server port [{$host}:{$port}]");
            }
            $server->getSettings() && $slaveServer->set(array_replace($config->getSettings(), $server->getSettings()));
            $this->registerSwooleEvents($slaveServer, $callbacks, $name);
            ServerManager::add($name, [$type, $slaveServer]);
        }

        // Trigger beforeStart event.
        if (isset($callbacks[Event::ON_BEFORE_START])) {
            [$class, $method] = $callbacks[Event::ON_BEFORE_START];
            if ($this->container->has($class)) {
                $this->container->get($class)->{$method}();
            }
        }

        if (class_exists(BeforeServerStart::class)) {
            // Trigger BeforeServerStart event.
            $this->eventDispatcher->dispatch(new BeforeServerStart($name));
        }
    }
}
  • 最后启动 Server
// vendor\hyperf\server\src\Server.php
public function start()
{
    $this->server->start();
}

默认 http server 回调 Hyperf\HttpServer\ServeronRequest

'servers' => [
    [
        'name' => 'http',
        'type' => Server::SERVER_HTTP,
        'host' => '0.0.0.0',
        'port' => 9501,
        'sock_type' => SWOOLE_SOCK_TCP,
        'callbacks' => [
            Event::ON_REQUEST => [Hyperf\HttpServer\Server::class, 'onRequest'],
        ],
    ],
],

在 server 初始化时,会进行 Hyperf\HttpServer\Server 的实例化和初始化

// vendor\hyperf\server\src\Server.php
protected function registerSwooleEvents($server, array $events, string $serverName): void
{
    foreach ($events as $event => $callback) {
        ...
        if (is_array($callback)) {
            ...

            $this->onRequestCallbacks[$className . $method] = $serverName;
            $class = $this->container->get($className); // 获取实例
            if (method_exists($class, 'setServerName')) {
                // Override the server name.
                $class->setServerName($serverName);
            }
            if ($class instanceof MiddlewareInitializerInterface) {
                $class->initCoreMiddleware($serverName); // 初始化
            }
            $callback = [$class, $method];
        }
        $server->on($event, $callback);
    }
}
  • 创建核心路由器和路由配置
// vendor\hyperf\http-server\src\Server.php
public function initCoreMiddleware(string $serverName): void
{
    ...
    $this->coreMiddleware = $this->createCoreMiddleware();
    $this->routerDispatcher = $this->createDispatcher($serverName);
    ...
}
  • 核心路由器中负责分发路由
// vendor\hyperf\http-server\src\CoreMiddleware.php
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
    $request = Context::set(ServerRequestInterface::class, $request);

    /** @var Dispatched $dispatched */
    $dispatched = $request->getAttribute(Dispatched::class);

    if (! $dispatched instanceof Dispatched) {
        throw new ServerException(sprintf('The dispatched object is not a %s object.', Dispatched::class));
    }

    $response = null;
    switch ($dispatched->status) {
        case Dispatcher::NOT_FOUND:
            $response = $this->handleNotFound($request);
            break;
        case Dispatcher::METHOD_NOT_ALLOWED:
            $response = $this->handleMethodNotAllowed($dispatched->params, $request);
            break;
        case Dispatcher::FOUND:
            $response = $this->handleFound($dispatched, $request);
            break;
    }
    if (! $response instanceof ResponseInterface) {
        $response = $this->transferToResponse($response, $request);
    }
    return $response->withAddedHeader('Server', 'Hyperf');
}
  • 路由的逻辑处理
// vendor\hyperf\http-server\src\CoreMiddleware.php
protected function handleFound(Dispatched $dispatched, ServerRequestInterface $request)
{
    if ($dispatched->handler->callback instanceof Closure) {
        $parameters = $this->parseClosureParameters($dispatched->handler->callback, $dispatched->params);
        $response = call($dispatched->handler->callback, $parameters);
    } else {
        [$controller, $action] = $this->prepareHandler($dispatched->handler->callback);
        $controllerInstance = $this->container->get($controller);
        if (! method_exists($controllerInstance, $action)) {
            // Route found, but the handler does not exist.
            throw new ServerErrorHttpException('Method of class does not exist.');
        }
        $parameters = $this->parseMethodParameters($controller, $action, $dispatched->params);
        $response = $controllerInstance->{$action}(...$parameters);
    }
    return $response;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容