Laravel Octane 源码简单分析

php artisan octane:start 基于swoole模式来分析

运行这个命令之后,会执行下图中的 handle 方法,最终会执行startSwooleServer方法

octane 源码分析

这个方法中调用了octane:swoolecommand命令 运行 handle方法

public function handle(
        ServerProcessInspector $inspector,
        ServerStateFile $serverStateFile,
        SwooleExtension $extension
    ) {
        //检测是否有swoole扩展
        if (! $extension->isInstalled()) {
            $this->error('The Swoole extension is missing.');

            return 1;
        }
        //检测服务是否正在运行
        if ($inspector->serverIsRunning()) {
            $this->error('Server is already running.');

            return 1;
        }

        if (config('octane.swoole.ssl', false) === true && ! defined('SWOOLE_SSL')) {
            $this->error('You must configure Swoole with `--enable-openssl` to support ssl.');

            return 1;
        }
        //服务状态写入文件
        $this->writeServerStateFile($serverStateFile, $extension);
        //清除环境变量
        $this->forgetEnvironmentVariables();
        //启动子进程 
        $server = tap(new Process([
            (new PhpExecutableFinder)->find(),
            ...config('octane.swoole.php_options', []),
            config('octane.swoole.command', 'swoole-server'),
            $serverStateFile->path(),
        ], realpath(__DIR__.'/../../bin'), [
            'APP_ENV' => app()->environment(),
            'APP_BASE_PATH' => base_path(),
            'LARAVEL_OCTANE' => 1,
        ]))->start();
        //这里是监听子进程运行状态
        return $this->runServer($server, $inspector, 'swoole');
    }

子进程是执行swoole-server这个文件
只分析 workerstartrequest这两个监听

require_once __DIR__.'/WorkerState.php';

$workerState = new WorkerState;

$workerState->cacheTable = require __DIR__.'/createSwooleCacheTable.php';
$workerState->timerTable = $timerTable;
$workerState->tables = require __DIR__.'/createSwooleTables.php';

$server->on('workerstart', fn (Server $server, $workerId) =>
    (fn ($basePath) => (new OnWorkerStart(
        new SwooleExtension, $basePath, $serverState, $workerState
    ))($server, $workerId))($bootstrap($serverState))
);

在这个workerstart这个监听中 有 实例化OnWorkerStart类,在这个类中给$workerState设置了worker属性

public function __invoke($server, int $workerId)
    {
        $this->clearOpcodeCache();

        $this->workerState->server = $server;
        $this->workerState->workerId = $workerId;
        $this->workerState->workerPid = posix_getpid();
        //这里是重点
        $this->workerState->worker = $this->bootWorker($server);

        $this->dispatchServerTickTaskEverySecond($server);
        $this->streamRequestsToConsole($server);

        if ($this->shouldSetProcessName) {
            $isTaskWorker = $workerId >= $server->setting['worker_num'];

            $this->extension->setProcessName(
                $this->serverState['appName'],
                $isTaskWorker ? 'task worker process' : 'worker process',
            );
        }
    }

bootWorker这个方法中

protected function bootWorker($server)
    {
        try {
            return tap(new Worker(
                new ApplicationFactory($this->basePath),
                $this->workerState->client = new SwooleClient
            ))->boot([
                'octane.cacheTable' => $this->workerState->cacheTable,
                Server::class => $server,
                WorkerState::class => $this->workerState,
            ]);
        } catch (Throwable $e) {
            Stream::shutdown($e);

            $server->shutdown();
        }
    }

在这里面会实例化 Worker类 并调用boot方法,这个方法做了以下操作

public function boot(array $initialInstances = []): void
    {
        //这里会初始化app容器         
        $this->app = $app = $this->appFactory->createApplication(
            array_merge(
                $initialInstances,
                [Client::class => $this->client],
            )
        );

        $this->dispatchEvent($app, new WorkerStarting($app));
    }

会执行ApplicationFactory类中的createApplication这个方法,这个方法做了以下操作

public function createApplication(array $initialInstances = []): Application
    {
        $paths = [
            $this->basePath.'/.laravel/app.php',
            $this->basePath.'/bootstrap/app.php',
        ];

        foreach ($paths as $path) {
            if (file_exists($path)) {
                //重点是这里    
                return $this->warm($this->bootstrap(require $path, $initialInstances));
            }
        }

        throw new RuntimeException("Application bootstrap file not found in 'bootstrap' or '.laravel' directory.");
    }

会给app容器预热(预加载)一些单例,会在拿octane.php配置文件中的warm数组
在上图bootstrap方法中,在这个方法中会执行 getBootstrappers方法

protected function getBootstrappers(Application $app): array
    {
        $method = (new ReflectionObject(
            $kernel = $app->make(HttpKernelContract::class)
        ))->getMethod('bootstrappers');

        $method->setAccessible(true);

        return $this->injectBootstrapperBefore(
            //拿到所有的Provider类,在app.php配置文件中的providers数组 和 自动注册的Provider类(5.5版本以后不用手动加到app.php中就是在这里做了处理)
            RegisterProviders::class,
            //创建Request对象到app容器
            SetRequestForConsole::class,
            //执行了Http\Kernel类中的 bootstrappers方法 拿到数组
            $method->invoke($kernel)
        );
    }

Http\Kernel类中的 bootstrappers方法

protected $bootstrappers = [//加载env配置文件
        \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
        //加载config配置文件
        \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
        //处理异常
        \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
        //注册静态代理
        \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
        //执行每个Provider的register方法
        \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
        //执行每个Provider的boot方法
        \Illuminate\Foundation\Bootstrap\BootProviders::class,
    ];

然后就会在bootstrapWith方法中make这些类去完成注册 然后标记了$this->hasBeenBootstrapped = true
workerstart监听就结束了
接下来就是request监听

$server->on('request', function ($request, $response) use ($server, $workerState, $serverState) {
    $workerState->lastRequestTime = microtime(true);

    if ($workerState->timerTable) {
        $workerState->timerTable->set($workerState->workerId, [
            'worker_pid' => $workerState->workerPid,
            'time' => time(),
            'fd' => $request->fd,
        ]);
    }
    //重点是这里
    $workerState->worker->handle(...$workerState->client->marshalRequest(new RequestContext([
        'swooleRequest' => $request,
        'swooleResponse' => $response,
        'publicPath' => $serverState['publicPath'],
        'octaneConfig' => $serverState['octaneConfig'],
    ])));

    if ($workerState->timerTable) {
        $workerState->timerTable->del($workerState->workerId);
    }
});

就是执行Worker类中的handle方法

public function handle(Request $request, RequestContext $context): void
    {
        //判断是不是静态文件
        if ($this->client instanceof ServesStaticFiles &&
            $this->client->canServeRequestAsStaticFile($request, $context)) {
            $this->client->serveStaticFile($request, $context);

            return;
        }

        //这个里面是给绑定容器实例给app 
        //然后Facade清除所有解析的实例  重新绑定app
        //clone是为了防止污染app容器 可以做到复用的效果
        CurrentApplication::set($sandbox = clone $this->app);

        $gateway = new ApplicationGateway($this->app, $sandbox);

        //下面的就是去找到控制器执行方法得到响应
        try {
            $responded = false;

            ob_start();

            $response = $gateway->handle($request);

            $output = ob_get_contents();

            ob_end_clean();

            $this->client->respond(
                $context,
                $octaneResponse = new OctaneResponse($response, $output),
            );

            $responded = true;

            $this->invokeRequestHandledCallbacks($request, $response, $sandbox);

            $gateway->terminate($request, $response);
        } catch (Throwable $e) {
            $this->handleWorkerError($e, $sandbox, $request, $context, $responded);
        } finally {
            $sandbox->flush();

            $this->app->make('view.engine.resolver')->forget('blade');
            $this->app->make('view.engine.resolver')->forget('php');

            // After the request handling process has completed we will unset some variables
            // plus reset the current application state back to its original state before
            // it was cloned. Then we will be ready for the next worker iteration loop.
            unset($gateway, $sandbox, $request, $response, $octaneResponse, $output);

            CurrentApplication::set($this->app);
        }
    }

简单分析一下这个 $response = $gateway->handle($request);

public function handle(Request $request): Response
    {
    //這個地方会触发事件 去flush已加载好的一些单例状态 防止被污染 
        $this->dispatchEvent($this->sandbox, new RequestReceived($this->app, $this->sandbox, $request));

    if (Octane::hasRouteFor($request->getMethod(), '/'.$request->path())) {
            return Octane::invokeRoute($request, $request->getMethod(), '/'.$request->path());
        }

        return tap($this->sandbox->make(Kernel::class)->handle($request), function ($response) use ($request) {
            $this->dispatchEvent($this->sandbox, new RequestHandled($this->sandbox, $request, $response));
        });
        });
    }

这里有又到了http\Kernel laravel自己的执行流程
在这个里面会执行sendRequestThroughRouter这个方法, 然后执行到这个bootstrap方法 会通过 hasBeenBootstrapped方法 判断是否已经加载 所以不会重复注册providers

大概就分析这些吧,第一次写博客,不怎么会写,有问题的地方请大家帮忙指正

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 1年前 自动加精
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 8

写得好,多写点儿!

1年前 评论
cccdz (楼主) 1年前
goStruct

这扩展说实在还是没解决阻塞的问题,能支持协程就好了。

1年前 评论
cccdz (楼主) 1年前
cccdz (楼主) 1年前
porygonCN 1年前
meows 1年前
meows 1年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!