hyperf2.2 的 http-server 啟動(dòng)源碼分析

入口文件 bin/hyperf.php

設(shè)置錯(cuò)誤信息提示,內(nèi)存使用限制和常量

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);

調(diào)用 composer 自動(dòng)加載

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

初始化所有的容器對(duì)象;

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

1. 設(shè)定代理類所在位置

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

2. 設(shè)定配置目錄

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

3. 設(shè)置掃描類

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

4. 注冊(cè)自動(dòng)加載

獲取composer原來注冊(cè)的所有自動(dòng)加載函數(shù)spl_autoload_functions(),使用到注解的自動(dòng)加載回調(diào)函數(shù)中$composerClassLoader->findFile($class),并替換 composer 中 ClassLoader類的原有注冊(cè)函數(shù),改為使用自身類的loadClass 方法,取消原來所有的注冊(cè),并在重寫后重新注冊(cè)

$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 類,執(zhí)行構(gòu)造方法
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 自動(dòng)加載中添加額外的 classmap
  • 獲取各個(gè)配置項(xiàng)
// ScanConfig.php
public static function instance(string $configDir): self
{
    if (self::$instance) {
        return self::$instance;
    }

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

    [$config, $serverDependencies, $cacheable] = static::initConfigByFile($configDir); // 獲取配置參數(shù)

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

initConfigByFile方法內(nèi)部,會(huì)讀取 composer.lock 文件,獲取所有packagespackages-devextra->hyperf->config 字段,根據(jù)類名實(shí)例化各個(gè)ConfigProvider,執(zhí)行__invoke方法,對(duì)執(zhí)行結(jié)果的返回?cái)?shù)組按照類別進(jìn)行合并獲取所有配置

  • extra 格式
"extra": {
    "branch-alias": {
        "dev-master": "2.2-dev"
    },
    "hyperf": {
        "config": "Hyperf\\Di\\ConfigProvider"
    }
}
  • $configFromProviders 合并后數(shù)據(jù)格式
// 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"
    ]
]

提取配置項(xiàng)中的 dependencies數(shù)據(jù),然后讀取 $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 ?? []);
}

提取配置項(xiàng)中的 annotations數(shù)據(jù),合并數(shù)據(jù),然后讀取 $configDir . '/autoload/annotations.php',再次合并數(shù)據(jù),生成 $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);
  • 執(zhí)行邏輯
  $scanned = $this->handler->scan(); // 進(jìn)行fork,子進(jìn)程是 false,父進(jìn)程是true,子進(jìn)程邏輯處理完之后再執(zhí)行父進(jìn)程邏輯
  if ($scanned->isScanned()) {
  return $this->deserializeCachedScanData($collectors);
  }
  // 下面的是子進(jìn)程邏輯,之后的 exit 會(huì)觸發(fā) pcntl_wait($status),執(zhí)行上面的父進(jìn)程邏輯;
  ...
  exit;
  • pnctl fork 進(jìn)程
public function scan(): Scanned
{
    $pid = pcntl_fork();
    // 父進(jìn)程和子進(jìn)程都會(huì)執(zhí)行下面代碼
    if ($pid == -1) {
        throw new Exception('The process fork failed');
    }
    if ($pid) {
        // 父進(jìn)程執(zhí)行邏輯
        pcntl_wait($status); // 掛起等待子進(jìn)程完成后,才繼續(xù)執(zhí)行
        return new Scanned(true);
    }
    // 子進(jìn)程執(zhí)行邏輯
    return new Scanned(false);
}

5. 初始化懶加載配置

根據(jù)AST生成代理文件,講這些代理類的自動(dòng)加載以 prepend 參數(shù)調(diào)整到隊(duì)列頭

LazyLoader::bootstrap($configDir);

獲取容器,獲取 application 對(duì)象,啟動(dòng)程序

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

container 的獲取對(duì)象方式

containerget方法用于獲取對(duì)象,根據(jù)dependenciesclassmap 尋找對(duì)應(yīng)的類,如果已經(jīng)實(shí)例化直接返回,沒有時(shí)調(diào)用 make 方法進(jìn)行實(shí)例化,緩存后返回。

  • 實(shí)例化方法
// 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); // 實(shí)例化類
    }
    ...
}
// 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 對(duì)象

默認(rèn)為\Symfony\Component\Console\Application 對(duì)象, 獲取所有的命令添加事件監(jiān)聽

// 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 環(huán)境
$this->checkEnvironment($output);
  • 獲取 server 對(duì)象,從配置中獲取所有 server config,初始化 SwooleServer,如果有多個(gè)server時(shí),添加額外的監(jiān)聽
// 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));
        }
    }
}
  • 最后啟動(dòng) Server
// vendor\hyperf\server\src\Server.php
public function start()
{
    $this->server->start();
}

默認(rèn) http server 回調(diào) 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 初始化時(shí),會(huì)進(jìn)行 Hyperf\HttpServer\Server 的實(shí)例化和初始化

// 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); // 獲取實(shí)例
            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);
    }
}
  • 創(chuàng)建核心路由器和路由配置
// vendor\hyperf\http-server\src\Server.php
public function initCoreMiddleware(string $serverName): void
{
    ...
    $this->coreMiddleware = $this->createCoreMiddleware();
    $this->routerDispatcher = $this->createDispatcher($serverName);
    ...
}
  • 核心路由器中負(fù)責(zé)分發(fā)路由
// 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;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • date: 2019-08-19 16:47:27title: hyperf| hyperf 源碼解讀 1: 啟動(dòng)...
    daydaygo閱讀 8,164評(píng)論 5 7
  • 你已經(jīng)學(xué)會(huì)了如何使用命令行界面做一些事情。本章將向你介紹所有可用的命令。 為了從命令行獲得幫助信息,請(qǐng)運(yùn)行comp...
    括兒之家閱讀 451評(píng)論 0 0
  • Laravel 學(xué)習(xí)交流 QQ 群:375462817 本文檔前言Laravel 文檔寫的很好,只是新手看起來會(huì)有...
    Leonzai閱讀 8,721評(píng)論 2 12
  • 安裝ThinkPHP 怎么安裝,我就不細(xì)說了,官方文檔-安裝ThinkPHP說的很全了,可以通過Composer、...
    也許會(huì)了閱讀 1,425評(píng)論 0 0
  • 這里是用 thinkphp 3.2.0 版本的框架來做分析的 入口文件為 index.php,是在項(xiàng)目的根目錄下。...
    imjcw閱讀 1,870評(píng)論 0 2

友情鏈接更多精彩內(nèi)容