├── .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 | php | >=5.5.9 |
8 |
9 |
10 | ext-swoole | >=1.7.7 |
11 |
12 |
13 | laravel/framework | 5.1.* |
14 |
15 |
16 | swala/installer | ~1.0 |
17 |
18 |
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 | test_resource | 检测swoole的全局资源常驻性 |
10 |
11 |
12 | test_stand_alone | 检测worker的全局资源独立性 |
13 |
14 |
15 | test_grab | 检测worker的不可抢占性 |
16 |
17 |
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 |