欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

不用三方包 给 Laravel 开启 Swoole

程序员文章站 2022-03-25 16:30:00
Swoole 是一款优秀的 PHP 扩展,利用其可以实现原生 PHP 很难做到的常驻服务和异步。正好我有个 Laravel 项目可以折腾,就研究了下。 Laravel 项目是基于 composer 的,所以我先帖下我的 composer.json 中的 require 声明: { "require" ......

swoole 是一款优秀的 php 扩展,利用其可以实现原生 php 很难做到的常驻服务和异步。正好我有个 laravel 项目可以折腾,就研究了下。

laravel 项目是基于 composer 的,所以我先帖下我的 composer.json 中的 require 声明:

 

{
    "require": {
        "php": "^7.1.3",
        "cybercog/laravel-love": "^5.1",
        "dingo/api": "~v2.0.0-alpha2",
        "doctrine/dbal": "^2.8",
        "fideloper/proxy": "^4.0",
        "guzzlehttp/guzzle": "^6.3",
        "infyomlabs/adminlte-templates": "5.6.x-dev",
        "infyomlabs/laravel-generator": "5.6.x-dev",
        "jeroennoten/laravel-adminlte": "^1.23",
        "laravel/framework": "5.6.*",
        "laravel/tinker": "^1.0",
        "laravelcollective/html": "^5.6.0",
        "lshorz/luocaptcha": "^1.0",
        "overtrue/laravel-lang": "v3.0.08",
        "overtrue/laravel-wechat": "^4.0",
        "predis/predis": "^1.1",
        "spatie/laravel-permission": "^2.17",
        "tymon/jwt-auth": "~1.0.0-rc.2",
        "yajra/laravel-datatables-buttons": "^4.0",
        "yajra/laravel-datatables-oracle": "^8.7"
    }
}

  

如果我们要开启 swoole,我们可选的包有这些:

但一般来说,项目中需要常驻容器的服务与每次均需重新构建的服务并不一样,所以我才剑走偏锋。

起步

我们需要将 public/index.php 替换成如下

<?php

use illuminate\http\request;
use illuminate\http\response;

define('laravel_start', microtime(true));
require __dir__ . '/../vendor/autoload.php';
$app = require_once __dir__ . '/../bootstrap/app.php';

class laravel
{
    /**
     * illuminate\foundation\application
     *
     * @var \illuminate\foundation\application
     */
    public $app;

    /**
     * app\http\kernel
     *
     * @var \app\http\kernel
     */
    public $kernel;

    /**
     * app\http\requests\request
     *
     * @var \app\http\requests\request
     */
    public $request;

    /**
     * illuminate\http\jsonresponse
     *
     * @var \illuminate\http\jsonresponse
     */
    public $response;

    /**
     * 构造
     *
     * @param \illuminate\foundation\application $app
     */
    public function __construct(\illuminate\foundation\application $app)
    {
        $this->app = $app;
    }

    /**
     * run
     *
     * @return void
     */
    public function run()
    {
        \swoole\runtime::enablecoroutine(true);

        $http = new swoole_http_server('127.0.0.1', '80');

        $http->set([
            'document_root' => public_path('/'),
            'enable_static_handler' => true,
        ]);

        $http->on('request', function ($req, $res) {
            try {
                $kernel = $this->app->make(illuminate\contracts\http\kernel::class);

                $get = $req->get ?? [];
                $post = $req->post ?? [];
                $input = array_merge($get, $post);
                $cookie = $req->cookie ?? [];
                $files = $req->files ?? [];
                $server = $req->server ?? [];

                $request = request::create($req->server['request_uri'], $req->server['request_method'], $input, $cookie, $files, $server);

                if (isset($req->header['x-requested-with']) && $req->header['x-requested-with'] == "xmlhttprequest") {
                    $request->headers->set('x-requested-with', "xmlhttprequest", true);
                }
                if (isset($req->header['accept']) && $req->header['accept']) {
                    $request->headers->set('accept', $req->header['accept'], true);
                }

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

                $res->status($response->getstatuscode());

                foreach ($response->headers->allpreservecasewithoutcookies() as $name => $values) {
                    foreach ($values as $value) {
                        $res->header($name, $value, false);
                    }
                }

                foreach ($response->headers->getcookies() as $cookie) {
                    $res->header('set-cookie', $cookie->getname() . strstr($cookie, '='), false);
                }
                dump(time());

                $res->end($response->getcontent());
                $this->app->forgetinstance('request');
            } catch (\throwable $e) {
                echo $e->getmessage();
                echo php_eol;
                echo $e->getfile();
                echo php_eol;
                echo $e->getline();
                echo php_eol;
            }
        });
        $http->start();
    }
}

(new laravel($app))->run();

  

运行时发现大多数页面均没有问题,只有几个用了 infyomlabs/laravel-generator 产生的列表页,ajax 拉取 json 时却返回了 html。

排查

在有问题页面的 controller 代码中,找到如下

 /**
     * display a listing of the star.
     *
     * @param stardatatable $stardatatable
     * @return response
     */
    public function index(stardatatable $stardatatable)
    {
        return $stardatatable->render('stars.index');
    }
定位 stardatatable::render() 到了

  /**
     * process datatables needed render output.
     *
     * @param string $view
     * @param array $data
     * @param array $mergedata
     * @return mixed
     */
    public function render($view, $data = [], $mergedata = [])
    {
        if ($this->request()->ajax() && $this->request()->wantsjson()) {
            return app()->call([$this, 'ajax']);
        }
        ...
    }

  

这是判断 $this->request() 是不是 xhr 请求,且 accept 请求头声明了 application/json

而 $this->request() 实现如下

    /**
     * get datatables request instance.
     *
     * @return \yajra\datatables\utilities\request
     */
    public function request()
    {
        return $this->request ?: $this->request = resolve('datatables.request');
    }

  

不难看出,如果第一次构建,会走到

$this->request = resolve('datatables.request');

  

而 resolve 的实现是啥?

if (! function_exists('resolve')) {
    /**
     * resolve a service from the container.
     *
     * @param  string  $name
     * @return mixed
     */
    function resolve($name)
    {
        return app($name);
    }
}

  

就是从容器中取出 datatables.request 的过程。

所以我们只需让每次请求结束,$app 容器忘掉 datatables.request 就好了

改进

增加遗忘 datatables.request

    $res->end($response->getcontent());
    $this->app->forgetinstance('request');
    $this->app->forgetinstance('datatables.request');
    $this->app->forgetinstance(\dingo\api\http\middleware\request::class);

  

完整最终版:

<?php

use illuminate\http\request;
use illuminate\http\response;

define('laravel_start', microtime(true));
require __dir__ . '/../vendor/autoload.php';
$app = require_once __dir__ . '/../bootstrap/app.php';

class laravel
{
    /**
     * illuminate\foundation\application
     *
     * @var \illuminate\foundation\application
     */
    public $app;

    /**
     * app\http\kernel
     *
     * @var \app\http\kernel
     */
    public $kernel;

    /**
     * app\http\requests\request
     *
     * @var \app\http\requests\request
     */
    public $request;

    /**
     * illuminate\http\jsonresponse
     *
     * @var \illuminate\http\jsonresponse
     */
    public $response;

    /**
     * 构造
     *
     * @param \illuminate\foundation\application $app
     */
    public function __construct(\illuminate\foundation\application $app)
    {
        $this->app = $app;
    }

    /**
     * run
     *
     * @return void
     */
    public function run()
    {
        \swoole\runtime::enablecoroutine(true);

        $http = new swoole_http_server('127.0.0.1', '80');

        $http->set([
            'document_root' => public_path('/'),
            'enable_static_handler' => true,
        ]);

        $http->on('request', function ($req, $res) {
            try {
                $kernel = $this->app->make(illuminate\contracts\http\kernel::class);

                $get = $req->get ?? [];
                $post = $req->post ?? [];
                $input = array_merge($get, $post);
                $cookie = $req->cookie ?? [];
                $files = $req->files ?? [];
                $server = $req->server ?? [];

                $request = request::create($req->server['request_uri'], $req->server['request_method'], $input, $cookie, $files, $server);

                if (isset($req->header['x-requested-with']) && $req->header['x-requested-with'] == "xmlhttprequest") {
                    $request->headers->set('x-requested-with', "xmlhttprequest", true);
                }
                if (isset($req->header['accept']) && $req->header['accept']) {
                    $request->headers->set('accept', $req->header['accept'], true);
                }

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

                $res->status($response->getstatuscode());

                foreach ($response->headers->allpreservecasewithoutcookies() as $name => $values) {
                    foreach ($values as $value) {
                        $res->header($name, $value, false);
                    }
                }

                foreach ($response->headers->getcookies() as $cookie) {
                    $res->header('set-cookie', $cookie->getname() . strstr($cookie, '='), false);
                }
                dump(time());

                $res->end($response->getcontent());
                $this->app->forgetinstance('request');
                //$this->app->forgetinstance('session');
                //$this->app->forgetinstance('session.store');
                //$this->app->forgetinstance('cookie');
                $this->app->forgetinstance('datatables.request');
                $this->app->forgetinstance(\dingo\api\http\middleware\request::class);
                //$kernel->terminate($request, $response);
            } catch (\throwable $e) {
                echo $e->getmessage();
                echo php_eol;
                echo $e->getfile();
                echo php_eol;
                echo $e->getline();
                echo php_eol;
            }
        });
        $http->start();
    }
}

(new laravel($app))->run();

  

测试

比原生 laravel 确实快不少(这还有 4 句 sql 查询) 。

不用三方包 给 Laravel 开启 Swoole

注,此处给出的代码可以借鉴,但未经长期验证。且不同项目实际用到的包不同,需要在调试过程中 debug 容器中的服务提供者,和追踪代码来调优。

已知问题

  • flash 闪存数据以及表单验证错误的展示有问题
  • pdo 会报 cannot execute queries while other unbuffered queries are active symfony/symfony...
  • throttle 的 ip 获取设定默认会产生问题

更多学习内容请访问:

腾讯t3-t4标准精品php架构师教程目录大全,只要你看完保证薪资上升一个台阶(持续更新)