zoukankan      html  css  js  c++  java
  • 不用三方包 给 Laravel 开启 Swoole

    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 IlluminateHttpRequest;
    use IlluminateHttpResponse;
    
    define('LARAVEL_START', microtime(true));
    require __DIR__ . '/../vendor/autoload.php';
    $app = require_once __DIR__ . '/../bootstrap/app.php';
    
    class Laravel
    {
        /**
         * IlluminateFoundationApplication
         *
         * @var IlluminateFoundationApplication
         */
        public $app;
    
        /**
         * AppHttpKernel
         *
         * @var AppHttpKernel
         */
        public $kernel;
    
        /**
         * AppHttpRequestsRequest
         *
         * @var AppHttpRequestsRequest
         */
        public $request;
    
        /**
         * IlluminateHttpJsonResponse
         *
         * @var IlluminateHttpJsonResponse
         */
        public $response;
    
        /**
         * 构造
         *
         * @param IlluminateFoundationApplication $app
         */
        public function __construct(IlluminateFoundationApplication $app)
        {
            $this->app = $app;
        }
    
        /**
         * RUN
         *
         * @return void
         */
        public function run()
        {
            SwooleRuntime::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(IlluminateContractsHttpKernel::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 (	hrowable $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 YajraDataTablesUtilitiesRequest
         */
        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(DingoApiHttpMiddlewareRequest::class);
    

      

    完整最终版:

    <?php
    
    use IlluminateHttpRequest;
    use IlluminateHttpResponse;
    
    define('LARAVEL_START', microtime(true));
    require __DIR__ . '/../vendor/autoload.php';
    $app = require_once __DIR__ . '/../bootstrap/app.php';
    
    class Laravel
    {
        /**
         * IlluminateFoundationApplication
         *
         * @var IlluminateFoundationApplication
         */
        public $app;
    
        /**
         * AppHttpKernel
         *
         * @var AppHttpKernel
         */
        public $kernel;
    
        /**
         * AppHttpRequestsRequest
         *
         * @var AppHttpRequestsRequest
         */
        public $request;
    
        /**
         * IlluminateHttpJsonResponse
         *
         * @var IlluminateHttpJsonResponse
         */
        public $response;
    
        /**
         * 构造
         *
         * @param IlluminateFoundationApplication $app
         */
        public function __construct(IlluminateFoundationApplication $app)
        {
            $this->app = $app;
        }
    
        /**
         * RUN
         *
         * @return void
         */
        public function run()
        {
            SwooleRuntime::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(IlluminateContractsHttpKernel::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(DingoApiHttpMiddlewareRequest::class);
                    //$kernel->terminate($request, $response);
                } catch (	hrowable $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 查询) 。

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

    已知问题

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

    更多学习内容请访问:

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

  • 相关阅读:
    rt_thread studio结合cubmx进行stm32驱动开发学习
    rt_thread之时钟管理
    rt_thread线程间通讯
    使用jQuery开发iOS风格的页面导航菜单
    使用jQuery开发一个带有密码强度检验的超酷注册页面
    使用Javascript来创建一个响应式的超酷360度全景图片查看幻灯效果
    [英] 推荐 15 个 jQuery 选择框插件
    JavaScript封装Ajax(类JQuery中$.ajax()方法)
    阿里前端电话面试
    基于HTML5的Web跨设备超声波通信方案
  • 原文地址:https://www.cnblogs.com/a609251438/p/12692061.html
Copyright © 2011-2022 走看看