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 协议》,转载必须注明作者和本文链接
本帖由系统于 10个月前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 8

写得好,多写点儿!

11个月前 评论
cccdz (楼主) 11个月前
goStruct

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

11个月前 评论
cccdz (楼主) 11个月前
cccdz (楼主) 11个月前
porygonCN 11个月前
meows 10个月前
meows 10个月前

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