├── .gitignore ├── LICENSE ├── README.md ├── Server.php ├── composer.json ├── config.php ├── docs └── feature_test.md ├── feature_test ├── A.php ├── test_grab.php ├── test_resource.php └── test_stand_alone.php ├── instance └── .gitignore ├── log └── .gitignore ├── shutdown.php ├── startup.php ├── task_restart.php └── worker_restart.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | *~ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 蔡俊杰 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #Swoole & Laravel 2 | 以swoole_http_server为应用服务器,Laravel为业务框架的web后端方案 3 | 4 | ##依赖 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
php>=5.5.9
ext-swoole>=1.7.7
laravel/framework5.1.*
swala/installer~1.0
19 | 20 | ##安装 21 | 在laravel5.1项目目录下 22 | 23 | composer require swala/server:~1.0 24 | 25 | 安装后位于项目目录下的server/ 26 | 27 | ##配置 28 | server/config.php 29 | 30 | 默认host为127.0.0.1,port为9501 31 | 32 | 其余配置项对应swoole的可用配置项,[传送门](http://wiki.swoole.com/wiki/page/274.html) 33 | 34 | ##脚本 35 | 在server目录下 36 | 37 | * 启动 38 | 39 | php startup.php 40 | 41 | * 关闭 42 | 43 | php shutdown.php 44 | 45 | * 重启所有Worker 46 | 47 | php worker_restart.php 48 | 49 | * 重启所有Task 50 | 51 | php task_restart.php 52 | 53 | ##说明 54 | * instance/下存放服务器实例的PID记录,ini格式,它在swoole_http_server的start回调时创建,shutdown回调时销毁 55 | * log/下存放swoole_http_server的log 56 | * feature_test/下有三个测试脚本,如果想了解一些swoole_http_server相对于原生php所表现出来的行为特性,运行并阅读这三个脚本或许会有启发,详细说明[特性测试](docs/feature_test.md) 57 | * doc/为文档目录 58 | 59 | ##缺陷 60 | - 不支持文件上传 61 | - 不支持静态资源响应 62 | 63 | ##代码许可 64 | [MIT](LICENSE) 65 | 66 | 67 | 68 | > Written with [StackEdit](https://stackedit.io/). 69 | -------------------------------------------------------------------------------- /Server.php: -------------------------------------------------------------------------------- 1 | swoole_http_server = new swoole_http_server($host, $port); 14 | 15 | $this->swoole_http_server->set($setting); 16 | } 17 | 18 | public function start() 19 | { 20 | $this->swoole_http_server->on('start', array($this, 'onServerStart')); 21 | $this->swoole_http_server->on('shutdown', array($this, 'onServerShutdown')); 22 | $this->swoole_http_server->on('WorkerStart', array($this, 'onWorkerStart')); 23 | $this->swoole_http_server->on('request', array($this, 'onRequest')); 24 | 25 | $this->swoole_http_server->start(); 26 | } 27 | 28 | public function onServerStart($serv) 29 | { 30 | // 服务器启动后记录主进程与manager进程的PID,用于写shutdown脚本 31 | file_put_contents( 32 | __DIR__ . '/instance/' . $serv->master_pid, 33 | "master={$serv->master_pid}\nmanager={$serv->manager_pid}" 34 | ); 35 | } 36 | 37 | public function onServerShutdown($serv) 38 | { 39 | // 删除记录有服务器PID的文件 40 | unlink(__DIR__ . '/instance/' . $serv->master_pid); 41 | } 42 | 43 | public function onWorkerStart($serv, $worker_id) 44 | { 45 | // 创建laravel内核(把该逻辑放在此处,确保所有worker创建前父进程副本与laravel 46 | // 无关,令laravel具备热部署特性) 47 | require __DIR__ . '/../bootstrap/autoload.php'; 48 | $app = require __DIR__.'/../bootstrap/app.php'; 49 | $this->laravel_kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 50 | 51 | // 开启“方法欺骗”特性,与laravel5.1 LTS保持一致 52 | Illuminate\Http\Request::enableHttpMethodParameterOverride(); 53 | } 54 | 55 | public function onRequest($request, $response) 56 | { 57 | // 由于laravel_kernel接受illuminate_request并返回illuminate_response,所 58 | // 以该方法针对swoole的请求和响应进行兼容处理 59 | $illuminate_request = $this->dealWithRequest($request); 60 | $illuminate_response = $this->laravel_kernel->handle($illuminate_request); 61 | $this->dealWithResponse($response, $illuminate_response); 62 | 63 | // 结束请求生命周期 64 | $this->laravel_kernel->terminate($illuminate_request, $illuminate_response); 65 | } 66 | 67 | private function dealWithRequest($request) 68 | { 69 | $get = isset($request->get) ? $request->get : array(); 70 | $post = isset($request->post) ? $request->post : array(); 71 | $cookie = isset($request->cookie) ? $request->cookie : array(); 72 | $server = isset($request->server) ? $request->server : array(); 73 | $header = isset($request->header) ? $request->header : array(); 74 | 75 | // #4 swoole 1.7.19 will no longer do urlencode on cookies by denghongcai 76 | if (strnatcasecmp(SWOOLE_VERSION, '1.7.19') < 0) { 77 | // #2 laravel结合swoole每次刷新session都会变的问题 by cong8341 78 | // 注:由于swoole对cookie中的特殊字符(=等)做了urlencode,导致laravel的encrypter 79 | // 在下次请求时接受到的payload与上一个请求响应时发出的不一致,最终导致每次请求 80 | // 都解出不一样的laravel_session 81 | foreach ($cookie as $key => $value) { 82 | $cookie[$key] = urldecode($value); 83 | } 84 | } 85 | 86 | // #5 增强与Laravel的兼容,处理特殊请求体 by denghongcai 87 | // swoole中header并没有包含于$request->server中,需要合并 88 | foreach ($header as $key => $value) { 89 | $header['http_'.$key] = $value; 90 | unset($header[$key]); 91 | } 92 | $server = array_merge($server, $header); 93 | 94 | // 在swoole环境下$_SERVER的所有key都为小写,需要把它们转化为大写 95 | foreach ($server as $key => $value) { 96 | $server[strtoupper($key)] = $value; 97 | unset($server[$key]); 98 | } 99 | 100 | // #5 增强与Laravel的兼容,处理特殊请求体 by denghongcai 101 | // 对于Content-Type为非application/x-www-form-urlencoded的请求体需要给 102 | // SymfonyRequest传入原始的Body 103 | $content = $request->rawContent(); 104 | $content = empty($content) ? null : $content; 105 | 106 | // 创建illuminate_request 107 | $illuminate_request = IlluminateRequest::createFromBase( 108 | new SymfonyRequest($get, $post, array(), $cookie, array()/*$_FILES*/, $server, $content) 109 | ); 110 | 111 | return $illuminate_request; 112 | } 113 | 114 | private function dealWithResponse($response, $illuminate_response) 115 | { 116 | // status 117 | $response->status($illuminate_response->getStatusCode()); 118 | // headers 119 | foreach ($illuminate_response->headers->allPreserveCase() as $name => $values) { 120 | foreach ($values as $value) { 121 | $response->header($name, $value); 122 | } 123 | } 124 | // cookies 125 | foreach ($illuminate_response->headers->getCookies() as $cookie) { 126 | $response->cookie( 127 | $cookie->getName(), 128 | $cookie->getValue(), 129 | $cookie->getExpiresTime(), 130 | $cookie->getPath(), 131 | $cookie->getDomain(), 132 | $cookie->isSecure(), 133 | $cookie->isHttpOnly() 134 | ); 135 | } 136 | // content 137 | $content = $illuminate_response->getContent(); 138 | // send content & close 139 | $response->end($content); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swala/server", 3 | "description": "Swoole & Laravel", 4 | "type": "swala-server", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "ChieveiT", 9 | "email": "javachieveit@163.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.5.9", 14 | "ext-swoole": ">=1.7.7", 15 | "laravel/framework": "5.1.*", 16 | "swala/installer": "~1.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | '127.0.0.1', 5 | 'port' => 9501, 6 | 7 | // http://wiki.swoole.com/wiki/page/274.html 8 | 'max_conn' => 1024, 9 | 'timeout' => 2.5, //select and epoll_wait timeout. 10 | 'poll_thread_num' => 4, //reactor thread num 11 | 'writer_num' => 4, //writer thread num 12 | 'worker_num' => 4, //worker process num 13 | 'max_request' => 2000, 14 | 'dispatch_mode' => 1, 15 | 16 | 'log_file' => __DIR__ . '/log/log', 17 | 'daemonize' => 1 18 | ); 19 | -------------------------------------------------------------------------------- /docs/feature_test.md: -------------------------------------------------------------------------------- 1 | #特性测试 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
测试脚本测试目的
test_resource检测swoole的全局资源常驻性
test_stand_alone检测worker的全局资源独立性
test_grab检测worker的不可抢占性
18 | 19 | ##运行 20 | - 启动 21 | php {$script_name} & 22 | 23 | - 关闭 24 | kill -15 {$pid} 25 | 26 | 27 | > Written with [StackEdit](https://stackedit.io/). 28 | -------------------------------------------------------------------------------- /feature_test/A.php: -------------------------------------------------------------------------------- 1 | 1, //worker process num 5 | 'max_request' => 100, 6 | 'dispatch_mode' => 1, 7 | 8 | 'timeout' => 10, 9 | ); 10 | $serv = new swoole_http_server('127.0.0.1', 9501); 11 | $serv->set($setting); 12 | 13 | //全局计数器 14 | $count = 0; 15 | 16 | //测试worker能否被抢占 17 | $serv->on('request', function($request, $response){ 18 | global $count; 19 | $count++; 20 | 21 | sleep(5); 22 | 23 | $time = date('H:i:s'); 24 | $response->end("request: {$count}
time: {$time}"); 25 | }); 26 | 27 | $serv->start(); 28 | 29 | -------------------------------------------------------------------------------- /feature_test/test_resource.php: -------------------------------------------------------------------------------- 1 | 1, //worker process num 5 | 'max_request' => 3, 6 | 'dispatch_mode' => 1, 7 | ); 8 | $serv = new swoole_http_server('127.0.0.1', 9501); 9 | $serv->set($setting); 10 | 11 | //全局资源 12 | $resource = NULL; 13 | 14 | //测试worker的资源管理特性 15 | $serv->on('request', function($request, $response){ 16 | global $resource; 17 | 18 | if (class_exists('A', false) && $resource && !isset($temp_resource)) { 19 | $response->end('Class A is loaded
Object A still exists
temp_resource is undefined'); 20 | return; 21 | } 22 | 23 | //类定义 24 | require __DIR__ . '/A.php'; 25 | 26 | //暴露在全局作用域的全局符号 27 | $resource = new A(); 28 | 29 | //局限在函数作用域内的局部符号 30 | $temp_resource = 'temp resource'; 31 | 32 | $response->end('request first'); 33 | }); 34 | 35 | $serv->start(); 36 | 37 | -------------------------------------------------------------------------------- /feature_test/test_stand_alone.php: -------------------------------------------------------------------------------- 1 | 3, //worker process num 5 | 'max_request' => 3, 6 | 'dispatch_mode' => 1, 7 | ); 8 | $serv = new swoole_http_server('127.0.0.1', 9501); 9 | $serv->set($setting); 10 | 11 | //全局资源 12 | $resource = 'global'; 13 | 14 | //测试三条worker进程的全局资源是否存在竞争 15 | $serv->on('WorkerStart', function($serv, $worker_id){ 16 | global $resource; 17 | $resource = 'worker ' . $worker_id; 18 | }); 19 | $serv->on('request', function($request, $response){ 20 | global $resource; 21 | $response->end($resource); 22 | }); 23 | 24 | $serv->start(); 25 | 26 | -------------------------------------------------------------------------------- /instance/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /log/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /shutdown.php: -------------------------------------------------------------------------------- 1 | start(); 13 | -------------------------------------------------------------------------------- /task_restart.php: -------------------------------------------------------------------------------- 1 |