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,我们可选的包有这些:
- swooletw/laravel-swoole
- hhxsv5/laravel-s
但一般来说,项目中需要常驻容器的服务与每次均需重新构建的服务并不一样,所以我才剑走偏锋。
起步
我们需要将 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 查询) 。
注,此处给出的代码可以借鉴,但未经长期验证。且不同项目实际用到的包不同,需要在调试过程中 debug
容器中的服务提供者,和追踪代码来调优。
已知问题
- flash 闪存数据以及表单验证错误的展示有问题
- pdo 会报 cannot execute queries while other unbuffered queries are active symfony/symfony…
- throttle 的 ip 获取设定默认会产生问题
更多学习内容请访问:
腾讯t3-t4标准精品php架构师教程目录大全,只要你看完保证薪资上升一个台阶(持续更新)