不用三方包 给 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 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架构师教程目录大全,只要你看完保证薪资上升一个台阶(持续更新)
上一篇: 关于C语言宏定义的技巧:'#'和'##'