├── .gitignore
├── docs
├── feehicms_backend_php-fpm.png
├── feehicms_backend_yii2-swoole.png
├── feehicms_frontend_index_php-fpm.png
└── feehicms_frontend_index_yii2-swoole.png
├── src
├── web
│ ├── Logger.php
│ ├── Dispatcher.php
│ ├── ErrorHandler.php
│ ├── Session.php
│ ├── Response.php
│ └── Request.php
├── debug
│ ├── panels
│ │ ├── TimelinePanel.php
│ │ └── ProfilingPanel.php
│ └── Module.php
├── swoole
│ ├── Util.php
│ └── SwooleServer.php
└── console
│ └── SwooleController.php
├── feehi.service
├── feehi-backend.service
├── composer.json
├── backend.php
├── frontend.php
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/docs/feehicms_backend_php-fpm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liufee/yii2-swoole/HEAD/docs/feehicms_backend_php-fpm.png
--------------------------------------------------------------------------------
/docs/feehicms_backend_yii2-swoole.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liufee/yii2-swoole/HEAD/docs/feehicms_backend_yii2-swoole.png
--------------------------------------------------------------------------------
/docs/feehicms_frontend_index_php-fpm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liufee/yii2-swoole/HEAD/docs/feehicms_frontend_index_php-fpm.png
--------------------------------------------------------------------------------
/docs/feehicms_frontend_index_yii2-swoole.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/liufee/yii2-swoole/HEAD/docs/feehicms_frontend_index_yii2-swoole.png
--------------------------------------------------------------------------------
/src/web/Logger.php:
--------------------------------------------------------------------------------
1 | yiiBeginAt;
18 | }
19 | }
--------------------------------------------------------------------------------
/feehi.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Feehi Server
3 | After=network.target
4 | After=syslog.target
5 |
6 | [Service]
7 | Type=forking
8 | PIDFile=/path/to/yii2app/frontend/runtime/server.pid
9 | ExecStart=/path/to/php /path/to/yii2app/yii swoole/start
10 | ExecStop=/bin/kill $MAINPID
11 | ExecReload=/bin/kill -USR1 $MAINPID
12 | Restart=always
13 |
14 | [Install]
15 | WantedBy=multi-user.target graphical.target
16 |
--------------------------------------------------------------------------------
/feehi-backend.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Feehi Backend Server
3 | After=network.target
4 | After=syslog.target
5 |
6 | [Service]
7 | Type=forking
8 | PIDFile=/path/to/yii2app/backend/runtime/server.pid
9 | ExecStart=/path/to/php /path/to/yii2app/yii swoole-backend/start
10 | ExecStop=/bin/kill $MAINPID
11 | ExecReload=/bin/kill -USR1 $MAINPID
12 | Restart=always
13 |
14 | [Install]
15 | WantedBy=multi-user.target graphical.target
16 |
--------------------------------------------------------------------------------
/src/debug/panels/TimelinePanel.php:
--------------------------------------------------------------------------------
1 | Yii::$app->log->yiiBeginAt,
20 | 'end' => microtime(true),
21 | 'memory' => memory_get_peak_usage(),
22 | ];
23 | }
24 | }
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "feehi/yii2-swoole",
3 | "type": "library",
4 | "description": "yii2 swoole,让yii2跑在swoole上",
5 | "keywords": ["yii2","swoole","yii2-swoole", "yii2 swoole"],
6 | "homepage": "http://blog.feehi.com",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "liufee",
11 | "email": "job@feehi.com",
12 | "homepage": "http://blog.feehi.com",
13 | "role": "Developer"
14 | }
15 | ],
16 | "require:": {
17 | "php": ">=5.4"
18 | },
19 | "suggest": {
20 | "feehi/yii2-swoole": "Need extension swoole version >= 1.9;Recommend with php >= 7"
21 | },
22 | "autoload": {
23 | "psr-4": {
24 | "feehi\\": "src/"
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/debug/panels/ProfilingPanel.php:
--------------------------------------------------------------------------------
1 | module->logTarget;
18 | $messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE);
19 | return [
20 | 'memory' => memory_get_peak_usage(),
21 | 'time' => microtime(true) - Yii::$app->log->yiiBeginAt,
22 | 'messages' => $messages,
23 | ];
24 | }
25 | }
--------------------------------------------------------------------------------
/src/swoole/Util.php:
--------------------------------------------------------------------------------
1 | getResponse()->swooleResponse) ){
22 | echo "dump function must called in request period" . PHP_EOL;
23 | }
24 | Yii::$app->getResponse()->swooleResponse->header("Content-Type", "text/html;charset=utf-8");
25 | Yii::$app->getResponse()->swooleResponse->end($body);
26 | }
27 | }
--------------------------------------------------------------------------------
/src/debug/Module.php:
--------------------------------------------------------------------------------
1 | setViewPath('@vendor/yiisoft/yii2-debug/src/views');
21 | }
22 |
23 | public function setDebugHeaders($event)
24 | {
25 | if (!$this->checkAccess()) {
26 | return;
27 | }
28 | $url = Url::toRoute(['/' . $this->id . '/default/view',
29 | 'tag' => $this->logTarget->tag,
30 | ]);
31 | $event->sender->getHeaders()
32 | ->set('X-Debug-Tag', $this->logTarget->tag)
33 | ->set('X-Debug-Duration', number_format((microtime(true) - Yii::$app->log->yiiBeginAt) * 1000 + 1))
34 | ->set('X-Debug-Link', $url);
35 | }
36 | }
--------------------------------------------------------------------------------
/src/web/ErrorHandler.php:
--------------------------------------------------------------------------------
1 | exception = $exception;
24 |
25 | // disable error capturing to avoid recursive errors while handling exceptions
26 | $this->unregister();
27 |
28 | // set preventive HTTP status code to 500 in case error handling somehow fails and headers are sent
29 | // HTTP exceptions will override this value in renderException()
30 | if (PHP_SAPI !== 'cli') {
31 | http_response_code(500);
32 | }
33 |
34 | try {
35 | $this->logException($exception);
36 | if ($this->discardExistingOutput) {
37 | $this->clearOutput();
38 | }
39 | $this->renderException($exception);
40 | if (!YII_ENV_TEST) {
41 | \Yii::getLogger()->flush(true);
42 | if (defined('HHVM_VERSION')) {
43 | flush();
44 | }
45 | }
46 | } catch (\Exception $e) {
47 | // an other exception could be thrown while displaying the exception
48 | $this->handleFallbackExceptionMessage($e, $exception);
49 | } catch (\Throwable $e) {
50 | // additional check for \Throwable introduced in PHP 7
51 | $this->handleFallbackExceptionMessage($e, $exception);
52 | }
53 |
54 | $this->exception = null;
55 | }
56 |
57 | protected function handleFallbackExceptionMessage($exception, $previousException)
58 | {
59 | $msg = "An Error occurred while handling another error:\n";
60 | $msg .= (string) $exception;
61 | $msg .= "\nPrevious exception:\n";
62 | $msg .= (string) $previousException;
63 | if (YII_DEBUG) {
64 | if (PHP_SAPI === 'cli') {
65 | echo $msg . "\n";
66 | } else {
67 | echo '
' . htmlspecialchars($msg, ENT_QUOTES, Yii::$app->charset) . '
';
68 | }
69 | } else {
70 | echo 'An internal server error occurred.';
71 | }
72 | $msg .= "\n\$_SERVER = " . VarDumper::export($_SERVER);
73 | error_log($msg);
74 | if (defined('HHVM_VERSION')) {
75 | flush();
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/backend.php:
--------------------------------------------------------------------------------
1 | ";
29 | foreach ($var as $k => $v){
30 | $temp .= $k . '=>' . $v . "
";
31 | }
32 | $temp .= "}
";
33 | $var = $temp;
34 | }
35 | Yii::$app->get('response')->swooleResponse->end($var);
36 | }
37 |
38 | $web = $rootDir . "/backend/web/";
39 |
40 | $server = new \swoole_http_server("0.0.0.0", 9998);
41 |
42 | $server->set([
43 | 'document_root' => $web,
44 | 'enable_static_handler' => true,
45 | ]);
46 |
47 | $server->on('request', function ($request, $response)use ($config, $web){
48 | if( isset($request->files) ) {
49 | $files = $request->files;
50 | foreach ($files as $k => $v) {
51 | if( isset($v['name']) ){
52 | $_FILES = $files;
53 | break;
54 | }
55 | foreach ($v as $key => $val) {
56 | $_FILES[$k]['name'][$key] = $val['name'];
57 | $_FILES[$k]['type'][$key] = $val['type'];
58 | $_FILES[$k]['tmp_name'][$key] = $val['tmp_name'];
59 | $_FILES[$k]['size'][$key] = $val['size'];
60 | if(isset($val['error'])) $_FILES[$k]['error'][$key] = $val['error'];
61 | }
62 | }
63 | }
64 |
65 | $aliases = [
66 | '@web' => $web,
67 | '@webroot' => $web,
68 | ];
69 | $config['aliases'] = isset($config['aliases']) ? array_merge($aliases, $config['aliases']) : $aliases;
70 | $config['components']['request'] = [
71 | 'class' => feehi\web\Request::class,
72 | 'swooleRequest' => $request,
73 | 'cookieValidationKey' => 'KaNMPF6oZegCr0bhED4JHYnhOse7UhrS',
74 | 'enableCsrfValidation' => true,
75 | ];
76 | $config['components']['response'] = [
77 | 'class' => feehi\web\Response::class,
78 | 'swooleResponse' => $response,
79 | ];
80 | $config['components']['assetManager'] = [
81 | 'class' => yii\web\AssetManager::class,
82 | 'baseUrl' => '/assets'
83 | ];
84 | $application = new yii\web\Application($config);
85 | Yii::$app->setAliases($aliases);
86 | $application->run();
87 | });
88 |
89 | $server->start();
--------------------------------------------------------------------------------
/frontend.php:
--------------------------------------------------------------------------------
1 | ";
29 | foreach ($var as $k => $v){
30 | $temp .= $k . '=>' . $v . "
";
31 | }
32 | $temp .= "}
";
33 | $var = $temp;
34 | }
35 | yii::$app->get('response')->swooleResponse->end($var);
36 | }
37 |
38 | $web = $rootDir . "/frontend/web/";
39 |
40 | $server = new \swoole_http_server("0.0.0.0", 9999);
41 |
42 | $server->set([
43 | 'document_root' => $web,
44 | 'enable_static_handler' => true,
45 | ]);
46 |
47 | $server->on('request', function ($request, $response)use ($config, $web){
48 | if( isset($request->files) ) {
49 | $files = $request->files;
50 | foreach ($files as $k => $v) {
51 | if( isset($v['name']) ){
52 | $_FILES = $files;
53 | break;
54 | }
55 | foreach ($v as $key => $val) {
56 | $_FILES[$k]['name'][$key] = $val['name'];
57 | $_FILES[$k]['type'][$key] = $val['type'];
58 | $_FILES[$k]['tmp_name'][$key] = $val['tmp_name'];
59 | $_FILES[$k]['size'][$key] = $val['size'];
60 | if(isset($val['error'])) $_FILES[$k]['error'][$key] = $val['error'];
61 | }
62 | }
63 | }
64 |
65 | $aliases = [
66 | '@web' => $web,
67 | '@webroot' => $web,
68 | ];
69 | $config['aliases'] = isset($config['aliases']) ? array_merge($aliases, $config['aliases']) : $aliases;
70 | $config['components']['request'] = [
71 | 'class' => feehi\web\Request::class,
72 | 'swooleRequest' => $request,
73 | 'cookieValidationKey' => 'KaNMPF6oZegCr0bhED4JHYnhOse7UhrS',
74 | 'enableCsrfValidation' => true,
75 | ];
76 | $config['components']['response'] = [
77 | 'class' => feehi\web\Response::class,
78 | 'swooleResponse' => $response,
79 | ];
80 | $config['components']['assetManager'] = [
81 | 'class' => yii\web\AssetManager::class,
82 | 'baseUrl' => '/assets'
83 | ];
84 | $application = new yii\web\Application($config);
85 | Yii::$app->setAliases($aliases);
86 | $application->run();
87 | });
88 |
89 | $server->start();
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | yii2 swoole
2 | ===============================
3 |
4 | 让yii2运行在swoole上,不用修改一句代码。
5 |
6 |
7 | 演示站点
8 | ----------------
9 | 可以参考cms系统[FeehiCMS](http://www.github.com/liufee/cms)
10 |
11 | 前置说明
12 | ---------------
13 | 1. 有yii2-advanced-app的使用经验
14 | 2. 了解并使用过swoole, 如果没有请先阅读swoole的doc
15 | 3. 强烈建议先按照这篇文章阅读并实践理解yii2和swoole结合的两种方式
16 | https://www.jianshu.com/p/9c2788ccf3c0
17 | 4. 当你做完123 并且使用过yii2-swoole之后
18 | 可以去了解一下swoft 对比一下为协程而设计的框架和yii2这种的区别
19 |
20 | 安装
21 | ---------------
22 | 1. 使用composer
23 | composer的安装以及国内镜像设置请点击[此处](https://developer.aliyun.com/composer)
24 |
25 | ```bash
26 | $ cd /path/to/yii2-app
27 | $ composer require "feehi/yii2-swoole"
28 | $ composer install -vvv
29 | ```
30 |
31 |
32 | 配置yii2
33 | -------------
34 | 打开console/config/main.php,在顶层配置中加入如下配置。(注意:并不是配置在components里面,而应该在最外层,即与components同级)。[完整示例](https://github.com/liufee/cms/blob/master/console/config/main.php)
35 |
36 | ```bash
37 | 'id' => 'app-console',
38 | ...//其他配置
39 | 'controllerMap'=>[
40 | ...//其他配置项
41 | 'swoole-backend' => [
42 | 'class' => feehi\console\SwooleController::class,
43 | 'rootDir' => str_replace('console/config', '', __DIR__ ),//yii2项目根路径
44 | 'app' => 'backend',
45 | 'host' => '127.0.0.1',
46 | 'port' => 9998,
47 | 'web' => 'web',//默认为web。rootDir app web目的是拼接yii2的根目录,如果你的应用为basic,那么app为空即可。
48 | 'debug' => true,//默认开启debug,上线应置为false
49 | 'env' => 'dev',//默认为dev,上线应置为prod
50 | 'swooleConfig' => [
51 | 'reactor_num' => 2,
52 | 'worker_num' => 4,
53 | 'daemonize' => false,
54 | 'log_file' => __DIR__ . '/../../backend/runtime/logs/swoole.log',
55 | 'log_level' => 0,
56 | 'pid_file' => __DIR__ . '/../../backend/runtime/server.pid',
57 | ],
58 | ]
59 | ...//其他配置
60 | ]
61 | ...//其他配置
62 | ```
63 |
64 |
65 | 启动命令
66 | -------------
67 | * 启动 /path/to/php /path/to/yii swoole-backend/start
68 | * 关闭 /path/to/php /path/to/yii swoole-backend/stop
69 | * 重启 /path/to/php /path/to/yii swoole-backend/restart
70 |
71 |
72 | 使用systemd管理yii2-swoole的启动关闭
73 | ---------------------------
74 | 像管理apache一样使用service httpd start和service httpd stop以及service httpd restart来启动、关闭、重启yii2 swoole服务了。
75 |
76 | 1. 复制feehi.service和feehi-backend.service到/etc/systemd/system目录
77 | 2. 分别修改feehi.service和feehi-backend.service中[Service]部分的 /path/to/yii2app为你的目录,/path/to/php为你的php命令绝对路径
78 | 3. 运行systemctl daemon-reload
79 |
80 |
81 | 加入开机自动启动
82 | ---------------------------
83 |
84 | 方法一
85 |
86 | 1. 使用systemd管理服务
87 | 2. 运行systemctl enable feehi以及systemctl enable feehi-backend设置开机自动启动
88 |
89 | 方法二
90 |
91 | 在/etc/rc.local中加入
92 | /path/to/php /path/to/yii2app/yii swoole-backend/start
93 |
94 |
95 | Nginx配置
96 | -------------
97 | 虽然swoole从1.9.17版本以后底层支持作为静态资源web服务器,但毕竟没有完全实现http协议,强烈推荐配合nginx使用,把swoole仅作为应用服务器。
98 |
99 | ```bash
100 | *
101 | * 后台
102 | *
103 | server {
104 | set $web /www/cms-swoole/backend/web;
105 | root $web;
106 | server_name swoole-admin.cms.test.docker;
107 |
108 | location ~* .(ico|gif|bmp|jpg|jpeg|png|swf|js|css|mp3)$ {
109 | root $web;
110 | }
111 |
112 | location / {
113 | proxy_http_version 1.1;
114 | proxy_set_header Connection "keep-alive";
115 | proxy_set_header X-Real-IP $remote_addr;
116 | proxy_set_header Host http://swoole-admin.cms.test.docker;
117 | proxy_pass http://127.0.0.1:9998;
118 | }
119 | }
120 | ```
121 |
122 |
123 | 调试
124 | -------------
125 |
126 | debug
127 |
128 | var_dump、echo都是输出到控制台,不方便调试。可以使用\feehi\swoole\Util::dump(),输出数组、对象、字符串、布尔值到浏览器
129 |
130 | log
131 |
132 | 已经修复
133 | 关于logger为何要替换的原因参见这篇文章详解: https://zguoqiang.com/2018/12/17/swoole%E5%9F%BA%E7%A1%80-%E4%B8%8E%E4%BC%A0%E7%BB%9FMVC%E6%A1%86%E6%9E%B6%E7%9A%84%E6%95%B4%E5%90%88/
134 |
--------------------------------------------------------------------------------
/src/swoole/SwooleServer.php:
--------------------------------------------------------------------------------
1 | 60000];
21 |
22 | public $runApp;
23 |
24 | public function __construct($host, $port, $mode, $socketType, $swooleConfig=[], $config=[])
25 | {
26 | $this->swoole = new swoole_http_server($host, $port, $mode, $socketType);
27 | $this->webRoot = $swooleConfig['document_root'];
28 | if (!empty($this->config)) $this->config = array_merge($this->config, $config);
29 | $this->swoole->set($swooleConfig);
30 | $this->swoole->on('request', [$this, 'onRequest']);
31 | $this->swoole->on('WorkerStart', [$this, 'onWorkerStart']);
32 | }
33 |
34 | public function run()
35 | {
36 | $this->swoole->start();
37 | }
38 |
39 | /**
40 | * @param \swoole_http_request $request
41 | * @param \swoole_http_response $response
42 | */
43 | public function onRequest($request, $response)
44 | {
45 | //拦截无效请求
46 | //$this->rejectUnusedRequest($request, $response);
47 |
48 | //静态资源服务器
49 | //$this->staticRequest($request, $response);
50 |
51 | //转换$_FILE超全局变量
52 | $this->mountGlobalFilesVar($request);
53 |
54 | call_user_func_array($this->runApp, [$request, $response]);
55 | }
56 |
57 | public function onWorkerStart( $serv , $worker_id) {
58 | if( $worker_id == 0 ) {
59 | swoole_timer_tick($this->config['gcSessionInterval'], function(){//一分钟清理一次session
60 | (new Session())->gcSession();
61 | });
62 | }
63 | }
64 |
65 | /**
66 | * @param \swoole_http_request $request
67 | * @param \swoole_http_response $response
68 | */
69 | private function rejectUnusedRequest($request, $response)
70 | {
71 | $uri = $request->server['request_uri'];
72 | $iru = strrev($uri);
73 |
74 | if( strpos('pam.', $iru) === 0 ){//.map后缀
75 | $response->status(200);
76 | $response->end('');
77 | }
78 | }
79 |
80 | /**
81 | * @param \swoole_http_request $request
82 | * @param \swoole_http_response $response
83 | */
84 | private function staticRequest($request, $response)
85 | {
86 | $uri = $request->server['request_uri'];
87 | $extension = pathinfo($uri, PATHINFO_EXTENSION);
88 | if (!empty($extension) && in_array($extension, ['js', 'css', 'jpg', 'jpeg', 'png', 'gif', 'webp'])) {
89 | $web = $this->webRoot;
90 | rtrim($web, '/');
91 | $file = $web . '/' . $uri;
92 | if (is_file($file)) {
93 | $temp = strrev($file);
94 | if (strpos($uri, 'sj.') === 0) {
95 | $response->header('Content-Type', 'application/x-javascript', false);
96 | } else if(strpos($temp, 'ssc.') === 0) {
97 | $response->header('Content-Type', 'text/css', false);
98 | } else {
99 | $response->header('Content-Type', 'application/octet-stream', false);
100 | }
101 | $response->sendfile($file, 0);
102 | } else {
103 | $response->status(404);
104 | $response->end('');
105 | }
106 | }
107 | }
108 |
109 | /**
110 | * @param \swoole_http_request $request
111 | */
112 | private function mountGlobalFilesVar($request)
113 | {
114 | //清空已上传文件,防止跨请求污染
115 | $_FILES = [];
116 | if (isset($request->files)) {
117 | $files = $request->files;
118 | foreach ($files as $k => $v) {
119 | if( isset($v['name']) ){
120 | $_FILES = $files;
121 | break;
122 | }
123 | foreach ($v as $key => $val) {
124 | $_FILES[$k]['name'][$key] = $val['name'];
125 | $_FILES[$k]['type'][$key] = $val['type'];
126 | $_FILES[$k]['tmp_name'][$key] = $val['tmp_name'];
127 | $_FILES[$k]['size'][$key] = $val['size'];
128 | if(isset($val['error'])) $_FILES[$k]['error'][$key] = $val['error'];
129 | }
130 | }
131 | }
132 | $_GET = isset($request->get) ? $request->get : [];
133 | $_POST = isset($request->post) ? $request->post : [];
134 | $_COOKIE = isset($request->cookie) ? $request->cookie : [];
135 |
136 | $server = isset($request->server) ? $request->server : [];
137 | $header = isset($request->header) ? $request->header : [];
138 | foreach ($server as $key => $value) {
139 | $_SERVER[strtoupper($key)] = $value;
140 | unset($server[$key]);
141 | }
142 | foreach ($header as $key => $value) {
143 | $_SERVER['HTTP_'.strtoupper($key)] = $value;
144 | }
145 | $_SERVER['SERVER_SOFTWARE'] = 'swoole/' . SWOOLE_VERSION;
146 | }
147 |
148 | }
--------------------------------------------------------------------------------
/src/console/SwooleController.php:
--------------------------------------------------------------------------------
1 | getPid() !== false) {
58 | $this->stderr('server already started');
59 | exit(1);
60 | }
61 |
62 | $pidDir = dirname($this->swooleConfig['pid_file']);
63 | if (!file_exists($pidDir)) {
64 | FileHelper::createDirectory($pidDir);
65 | }
66 |
67 | $logDir = dirname($this->swooleConfig['log_file']);
68 | if (!file_exists($logDir)) {
69 | FileHelper::createDirectory($logDir);
70 | }
71 |
72 | $rootDir = $this->rootDir;
73 | $web = $rootDir . $this->app . DIRECTORY_SEPARATOR . $this->web;
74 |
75 | defined('YII_DEBUG') or define('YII_DEBUG', $this->debug);
76 | defined('YII_ENV') or define('YII_ENV', $this->env);
77 |
78 | require $rootDir . '/vendor/autoload.php';
79 | if ($this->type == 'basic') {
80 | $config = require $rootDir . '/config/web.php';
81 | } else {
82 | require $rootDir . '/common/config/bootstrap.php';
83 | require $rootDir . $this->app . '/config/bootstrap.php';
84 |
85 | $config = ArrayHelper::merge(
86 | require($rootDir . '/common/config/main.php'),
87 | require($rootDir . '/common/config/main-local.php'),
88 | require($rootDir . $this->app . '/config/main.php'),
89 | require($rootDir . $this->app . '/config/main-local.php')
90 | );
91 | }
92 |
93 | $this->swooleConfig = array_merge([
94 | 'document_root' => $web,
95 | 'enable_static_handler' => true,
96 | ], $this->swooleConfig);
97 |
98 | $server = new SwooleServer($this->host, $this->port, $this->mode, $this->socketType, $this->swooleConfig, ['gcSessionInterval' => $this->gcSessionInterval]);
99 |
100 | /*
101 | * @param \swoole_http_request $request
102 | * @param \swoole_http_response $response
103 | */
104 | $server->runApp = function ($request, $response) use ($config, $web) {
105 | $yiiBeginAt = microtime(true);
106 | $aliases = [
107 | '@web' => '',
108 | '@webroot' => $web,
109 | '@webroot/assets' => $web.'/assets',
110 | '@web/assets' => $web.'/assets'
111 | ];
112 | $config['aliases'] = isset($config['aliases']) ? array_merge($aliases, $config['aliases']) : $aliases;
113 |
114 | $requestComponent = [
115 | 'class' => Request::class,
116 | 'swooleRequest' => $request,
117 | ];
118 | $config['components']['request'] = isset($config['components']['request']) ? array_merge($config['components']['request'], $requestComponent) : $requestComponent;
119 |
120 | $responseComponent = [
121 | 'class' => Response::class,
122 | 'swooleResponse' => $response,
123 | ];
124 | $config['components']['response'] = isset($config['components']['response']) ? array_merge($config['components']['response'], $responseComponent) : $responseComponent;
125 |
126 | $config['components']['session'] = isset($config['components']['session']) ? array_merge(['savePath' => $web . '/../runtime/session'], $config['components']['session'], ['class' => Session::class]) : ['class' => Session::class, 'savePath' => $web . '/../session'];
127 |
128 | $config['components']['errorHandler'] = isset($config['components']['errorHandler']) ? array_merge($config['components']['errorHandler'], ['class' => ErrorHandler::class]) : ['class' => ErrorHandler::class];
129 |
130 | if (isset($config['components']['log'])) {
131 | $config['components']['log'] = array_merge($config['components']['log'], ['class' => Dispatcher::class, 'logger' => Logger::class]);
132 | }
133 |
134 | if (isset($config['modules']['debug'])) {
135 | $config['modules']['debug'] = array_merge($config['modules']['debug'], [
136 | 'class' => Module::class,
137 | 'panels' => [
138 | 'profiling' => ['class' => ProfilingPanel::class],
139 | 'timeline' => ['class' => TimelinePanel::class],
140 | ],
141 | ]);
142 | }
143 |
144 | try {
145 | $application = new Application($config);
146 | // 这里将全局的logger替换成单个子app的logger 理论上其他的组件也需要做类似处理
147 | Yii::setLogger(Yii::$app->log->logger);
148 | Yii::$app->log->yiiBeginAt = $yiiBeginAt;
149 | Yii::$app->setAliases($aliases);
150 | try {
151 | $application->state = Application::STATE_BEFORE_REQUEST;
152 | $application->trigger(Application::EVENT_BEFORE_REQUEST);
153 |
154 | $application->state = Application::STATE_HANDLING_REQUEST;
155 | $tempResponse = $application->handleRequest($application->getRequest());
156 |
157 | $application->state = Application::STATE_AFTER_REQUEST;
158 | $application->trigger(Application::EVENT_AFTER_REQUEST);
159 |
160 | $application->state = Application::STATE_SENDING_RESPONSE;
161 |
162 | $tempResponse->send();
163 |
164 | $application->state = Application::STATE_END;
165 | } catch (ExitException $e) {
166 | $application->end($e->statusCode, isset($tempResponse) ? $tempResponse : null);
167 | }
168 | Yii::$app->getDb()->close();
169 | UploadedFile::reset();
170 | // 这里刷新当前work app的log
171 | /*
172 | Yii::$app->getLog()->getLogger()->flush();
173 | Yii::$app->getLog()->getLogger()->flush(true);
174 | */
175 |
176 | // 这里刷新master app的log 也就是console里的log 避免出现console常驻而看不到log的情况
177 | Yii::getLogger()->flush();
178 | Yii::getLogger()->flush(true);
179 | } catch (\Exception $e) {
180 | Yii::$app->getErrorHandler()->handleException($e);
181 | }
182 | };
183 |
184 | $this->stdout("server is running, listening {$this->host}:{$this->port}" . PHP_EOL);
185 | $server->run();
186 | }
187 |
188 | public function actionStop()
189 | {
190 | $this->sendSignal(SIGTERM);
191 | $this->stdout("server is stopped, stop listening {$this->host}:{$this->port}" . PHP_EOL);
192 | }
193 |
194 | public function actioReloadTask()
195 | {
196 | $this->sendSignal(SIGUSR2);
197 | }
198 |
199 | public function actionRestart()
200 | {
201 | $this->sendSignal(SIGTERM);
202 | $time = 0;
203 | while (posix_getpgid($this->getPid()) && $time <= 10) {
204 | usleep(100000);
205 | $time++;
206 | }
207 | if ($time > 100) {
208 | $this->stderr('Server stopped timeout' . PHP_EOL);
209 | exit(1);
210 | }
211 | if ($this->getPid() === false) {
212 | $this->stdout('Server is stopped success' . PHP_EOL);
213 | } else {
214 | $this->stderr('Server stopped error, please handle kill process' . PHP_EOL);
215 | }
216 | $this->actionStart();
217 | }
218 |
219 | public function actionReload()
220 | {
221 | $this->actionRestart();
222 | }
223 |
224 | private function sendSignal($sig)
225 | {
226 | if ($pid = $this->getPid()) {
227 | posix_kill($pid, $sig);
228 | } else {
229 | $this->stdout('server is not running!' . PHP_EOL);
230 | exit(1);
231 | }
232 | }
233 |
234 | private function getPid()
235 | {
236 | $pid_file = $this->swooleConfig['pid_file'];
237 | if (file_exists($pid_file)) {
238 | $pid = file_get_contents($pid_file);
239 | if (posix_getpgid($pid)) {
240 | return $pid;
241 | } else {
242 | unlink($pid_file);
243 | }
244 | }
245 | return false;
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/src/web/Session.php:
--------------------------------------------------------------------------------
1 | 1400,
29 | 'path' => '/',
30 | 'domain' => '',
31 | 'secure' => false,
32 | 'httponly' => true,
33 | ];
34 |
35 | private $_prefix = "feehi_";
36 |
37 | public function init()
38 | {
39 | parent::init();
40 | if ($this->getIsActive()) {
41 | Yii::warning('Session is already started', __METHOD__);
42 | $this->updateFlashCounters();
43 | }
44 | if($this->timeout !== null) $this->_cookieParams['lifetime'] = $this->timeout;
45 | }
46 |
47 | public function getSessionFullName()
48 | {
49 | return $this->getSavePath() . $this->_prefix . $this->getId();
50 | }
51 |
52 | public function persist()
53 | {
54 | $this->open();
55 | file_put_contents($this->getSessionFullName(), json_encode($_SESSION));
56 | }
57 |
58 | /**
59 | * swoole每隔设置的毫秒数执行此方法回收session
60 | */
61 | public function gcSession()
62 | {
63 | $handle = opendir( $this->getSavePath() );
64 | while (false !== ($file = readdir($handle)))
65 | {
66 | if ($file != "." && $file != ".." && (strpos($file, $this->_prefix) === 0) && is_file($this->getSavePath() . $file)) {
67 | if( strpos($file, $this->_prefix) !== 0 ) continue;
68 | $lastUpdatedAt = filemtime($this->getSavePath() . $file);
69 | if( time() - $lastUpdatedAt > $this->getCookieParams()['lifetime'] ){
70 | unlink($this->getSavePath() . $file);
71 | }
72 | }
73 | }
74 | }
75 |
76 | public function open()
77 | {
78 | if ($this->getIsActive()) {
79 | return;
80 | }
81 | if( !is_dir($this->getSavePath()) ) FileHelper::createDirectory($this->getSavePath());
82 | if( !is_readable($this->getSavePath()) ){
83 | throw new InvalidConfigException("SESSION saved path {$this->savePath} is not readable");
84 | }
85 | if( !is_writable($this->getSavePath()) ){
86 | throw new InvalidConfigException("SESSION saved path {$this->savePath} is not writable");
87 | }
88 | $file = $this->getSessionFullName();
89 | if( file_exists($file) && is_file($file) ) {
90 | $data = file_get_contents($file);
91 | $_SESSION = json_decode($data, true);
92 | }else{
93 | $_SESSION = [];
94 | }
95 | $this->_started = true;
96 | }
97 |
98 | public function getCookieParams()
99 | {
100 | return $this->_cookieParams;
101 | }
102 |
103 | public function setCookieParams(array $config){
104 | $this->_cookieParams = $config;
105 | }
106 |
107 | public function destroy()
108 | {
109 | $this->open();
110 | if ($this->getIsActive()) {
111 | $_SESSION = [];
112 | }
113 | }
114 |
115 | public function getIsActive()
116 | {
117 | return $this->_started;
118 | }
119 |
120 | private $_hasSessionId;
121 |
122 | public function getHasSessionId()
123 | {
124 | if ($this->_hasSessionId === null) {
125 | $name = $this->getName();
126 | $request = Yii::$app->getRequest();
127 | if (!empty($_COOKIE[$name]) && ini_get('session.use_cookies')) {
128 | $this->_hasSessionId = true;
129 | } elseif (!ini_get('session.use_only_cookies') && ini_get('session.use_trans_sid')) {
130 | $this->_hasSessionId = $request->get($name) != '';
131 | } else {
132 | $this->_hasSessionId = false;
133 | }
134 | }
135 | return $this->_hasSessionId;
136 | }
137 |
138 | public function setHasSessionId($value)
139 | {
140 | $this->_hasSessionId = $value;
141 | }
142 |
143 | public function getId()
144 | {
145 | if( isset($_COOKIE[$this->getName()]) ){
146 | $id = $_COOKIE[$this->getName()];
147 | }else{
148 | $id = uniqid();
149 | }
150 | return $id;
151 | }
152 |
153 | public function regenerateID($deleteOldSession = false)
154 | {
155 | }
156 |
157 | public function setName($name)
158 | {
159 | $this->name = $name;
160 | }
161 |
162 | public function getName()
163 | {
164 | return $this->name;
165 | }
166 |
167 | public function getSavePath()
168 | {
169 | if( strrpos( $this->savePath, '/') !==0 ){
170 | $this->savePath .= '/';
171 | }
172 | return $this->savePath;
173 | }
174 |
175 | public function setSavePath($value)
176 | {
177 | $this->savePath = $value;
178 | }
179 |
180 | public function getIterator()
181 | {
182 | $this->open();
183 | return new SessionIterator();
184 | }
185 |
186 | public function getCount()
187 | {
188 | $this->open();
189 | return count($_SESSION);
190 | }
191 |
192 | public function count()
193 | {
194 | $this->open();
195 | return $this->getCount();
196 | }
197 |
198 | public function get($key, $defaultValue = null)
199 | {
200 | $this->open();
201 | return isset($_SESSION[$key]) ? $_SESSION[$key] : $defaultValue;
202 | }
203 |
204 | public function set($key, $value)
205 | {
206 | $this->open();
207 | $_SESSION[$key] = $value;
208 | }
209 |
210 | public function remove($key)
211 | {
212 | $this->open();
213 | if (isset($_SESSION[$key])) {
214 | $value = $_SESSION[$key];
215 | unset($_SESSION[$key]);
216 | return $value;
217 | }
218 | return null;
219 | }
220 |
221 | public function removeAll()
222 | {
223 | $this->open();
224 | foreach (array_keys($_SESSION) as $key) {
225 | unset($_SESSION[$key]);
226 | }
227 | }
228 |
229 | public function has($key)
230 | {
231 | $this->open();
232 | return isset($_SESSION[$key]);
233 | }
234 |
235 | protected function updateFlashCounters()
236 | {
237 | $this->open();
238 | $counters = $this->get($this->flashParam, []);
239 | if (is_array($counters)) {
240 | foreach ($counters as $key => $count) {
241 | if ($count > 0) {
242 | unset($counters[$key], $_SESSION[$key]);
243 | } elseif ($count == 0) {
244 | $counters[$key]++;
245 | }
246 | }
247 | $_SESSION[$this->flashParam] = $counters;
248 | } else {
249 | // fix the unexpected problem that flashParam doesn't return an array
250 | unset($_SESSION[$this->flashParam]);
251 | }
252 | }
253 |
254 | public function getFlash($key, $defaultValue = null, $delete = true)
255 | {
256 | $this->open();
257 | $counters = $this->get($this->flashParam, []);
258 | if (isset($counters[$key])) {
259 | $value = $this->get($key, $defaultValue);
260 | if ($delete) {
261 | $this->removeFlash($key);
262 | } elseif ($counters[$key] < 0) {
263 | // mark for deletion in the next request
264 | $counters[$key] = 1;
265 | $_SESSION[$this->flashParam] = $counters;
266 | }
267 | return $value;
268 | }
269 | return $defaultValue;
270 | }
271 |
272 | public function getAllFlashes($delete = false)
273 | {
274 | $this->open();
275 | $counters = $this->get($this->flashParam, []);
276 | $flashes = [];
277 | foreach (array_keys($counters) as $key) {
278 | if (array_key_exists($key, $_SESSION)) {
279 | $flashes[$key] = $_SESSION[$key];
280 | if ($delete) {
281 | unset($counters[$key], $_SESSION[$key]);
282 | } elseif ($counters[$key] < 0) {
283 | // mark for deletion in the next request
284 | $counters[$key] = 1;
285 | }
286 | } else {
287 | unset($counters[$key]);
288 | }
289 | }
290 | $_SESSION[$this->flashParam] = $counters;
291 | return $flashes;
292 | }
293 |
294 | public function setFlash($key, $value = true, $removeAfterAccess = true)
295 | {
296 | $this->open();
297 | $counters = $this->get($this->flashParam, []);
298 | $counters[$key] = $removeAfterAccess ? -1 : 0;
299 | $_SESSION[$key] = $value;
300 | $_SESSION[$this->flashParam] = $counters;
301 | }
302 |
303 | public function addFlash($key, $value = true, $removeAfterAccess = true)
304 | {
305 | $this->open();
306 | $counters = $this->get($this->flashParam, []);
307 | $counters[$key] = $removeAfterAccess ? -1 : 0;
308 | $_SESSION[$this->flashParam] = $counters;
309 | if (empty($_SESSION[$key])) {
310 | $_SESSION[$key] = [$value];
311 | } else {
312 | if (is_array($_SESSION[$key])) {
313 | $_SESSION[$key][] = $value;
314 | } else {
315 | $_SESSION[$key] = [$_SESSION[$key], $value];
316 | }
317 | }
318 | }
319 |
320 | public function removeFlash($key)
321 | {
322 | $this->open();
323 | $counters = $this->get($this->flashParam, []);
324 | $value = isset($_SESSION[$key], $counters[$key]) ? $_SESSION[$key] : null;
325 | unset($counters[$key], $_SESSION[$key]);
326 | $_SESSION[$this->flashParam] = $counters;
327 | return $value;
328 | }
329 |
330 | public function removeAllFlashes()
331 | {
332 | $this->open();
333 | $counters = $this->get($this->flashParam, []);
334 | foreach (array_keys($counters) as $key) {
335 | unset($_SESSION[$key]);
336 | }
337 | unset($_SESSION[$this->flashParam]);
338 | }
339 |
340 | public function hasFlash($key)
341 | {
342 | $this->open();
343 | return $this->getFlash($key) !== null;
344 | }
345 |
346 | public function offsetExists($offset)
347 | {
348 | $this->open();
349 | return isset($_SESSION[$offset]);
350 | }
351 |
352 | public function offsetGet($offset)
353 | {
354 | $this->open();
355 | return isset($_SESSION[$offset]) ? $_SESSION[$offset] : null;
356 | }
357 |
358 | public function offsetSet($offset, $item)
359 | {
360 | $this->open();
361 | $_SESSION[$offset] = $item;
362 | }
363 |
364 | public function offsetUnset($offset)
365 | {
366 | $this->open();
367 | unset($_SESSION[$offset]);
368 | }
369 | }
370 |
--------------------------------------------------------------------------------
/src/web/Response.php:
--------------------------------------------------------------------------------
1 | 'Continue',
66 | 101 => 'Switching Protocols',
67 | 102 => 'Processing',
68 | 118 => 'Connection timed out',
69 | 200 => 'OK',
70 | 201 => 'Created',
71 | 202 => 'Accepted',
72 | 203 => 'Non-Authoritative',
73 | 204 => 'No Content',
74 | 205 => 'Reset Content',
75 | 206 => 'Partial Content',
76 | 207 => 'Multi-Status',
77 | 208 => 'Already Reported',
78 | 210 => 'Content Different',
79 | 226 => 'IM Used',
80 | 300 => 'Multiple Choices',
81 | 301 => 'Moved Permanently',
82 | 302 => 'Found',
83 | 303 => 'See Other',
84 | 304 => 'Not Modified',
85 | 305 => 'Use Proxy',
86 | 306 => 'Reserved',
87 | 307 => 'Temporary Redirect',
88 | 308 => 'Permanent Redirect',
89 | 310 => 'Too many Redirect',
90 | 400 => 'Bad Request',
91 | 401 => 'Unauthorized',
92 | 402 => 'Payment Required',
93 | 403 => 'Forbidden',
94 | 404 => 'Not Found',
95 | 405 => 'Method Not Allowed',
96 | 406 => 'Not Acceptable',
97 | 407 => 'Proxy Authentication Required',
98 | 408 => 'Request Time-out',
99 | 409 => 'Conflict',
100 | 410 => 'Gone',
101 | 411 => 'Length Required',
102 | 412 => 'Precondition Failed',
103 | 413 => 'Request Entity Too Large',
104 | 414 => 'Request-URI Too Long',
105 | 415 => 'Unsupported Media Type',
106 | 416 => 'Requested range unsatisfiable',
107 | 417 => 'Expectation failed',
108 | 418 => 'I\'m a teapot',
109 | 421 => 'Misdirected Request',
110 | 422 => 'Unprocessable entity',
111 | 423 => 'Locked',
112 | 424 => 'Method failure',
113 | 425 => 'Unordered Collection',
114 | 426 => 'Upgrade Required',
115 | 428 => 'Precondition Required',
116 | 429 => 'Too Many Requests',
117 | 431 => 'Request Header Fields Too Large',
118 | 449 => 'Retry With',
119 | 450 => 'Blocked by Windows Parental Controls',
120 | 451 => 'Unavailable For Legal Reasons',
121 | 500 => 'Internal Server Error',
122 | 501 => 'Not Implemented',
123 | 502 => 'Bad Gateway or Proxy Error',
124 | 503 => 'Service Unavailable',
125 | 504 => 'Gateway Time-out',
126 | 505 => 'HTTP Version not supported',
127 | 507 => 'Insufficient storage',
128 | 508 => 'Loop Detected',
129 | 509 => 'Bandwidth Limit Exceeded',
130 | 510 => 'Not Extended',
131 | 511 => 'Network Authentication Required',
132 | ];
133 |
134 | private $_statusCode = 200;
135 |
136 | private $_headers;
137 |
138 |
139 |
140 | public function init()
141 | {
142 | if ($this->version === null) {
143 | $swooleRequest = Yii::$app->getRequest()->swooleRequest;
144 | if (isset($swooleRequest->server['server_protocol']) && $swooleRequest->server['server_protocol'] === 'HTTP/1.0') {
145 | $this->version = '1.0';
146 | } else {
147 | $this->version = '1.1';
148 | }
149 | }
150 | if ($this->charset === null) {
151 | $this->charset = Yii::$app->charset;
152 | }
153 | $this->formatters = array_merge($this->defaultFormatters(), $this->formatters);
154 | }
155 |
156 | public function getStatusCode()
157 | {
158 | return $this->_statusCode;
159 | }
160 |
161 | public function setStatusCode($value, $text = null)
162 | {
163 | if ($value === null) {
164 | $value = 200;
165 | }
166 | $this->_statusCode = (int) $value;
167 | if ($this->getIsInvalid()) {
168 | throw new InvalidArgumentException("The HTTP status code is invalid: $value");
169 | }
170 | if ($text === null) {
171 | $this->statusText = isset(static::$httpStatuses[$this->_statusCode]) ? static::$httpStatuses[$this->_statusCode] : '';
172 | } else {
173 | $this->statusText = $text;
174 | }
175 | return $this;
176 | }
177 |
178 | public function setStatusCodeByException($e)
179 | {
180 | if ($e instanceof HttpException) {
181 | $this->setStatusCode($e->statusCode);
182 | } else {
183 | $this->setStatusCode(500);
184 | }
185 | return $this;
186 | }
187 |
188 | public function getHeaders()
189 | {
190 | if ($this->_headers === null) {
191 | $this->_headers = new HeaderCollection;
192 | }
193 | return $this->_headers;
194 | }
195 |
196 | public function send()
197 | {
198 | if ($this->isSent) {
199 | return;
200 | }
201 | $this->trigger(self::EVENT_BEFORE_SEND);
202 | $this->prepare();
203 | $this->trigger(self::EVENT_AFTER_PREPARE);
204 | $this->sendHeaders();
205 | $this->sendContent();
206 | $this->trigger(self::EVENT_AFTER_SEND);
207 | $this->isSent = true;
208 | }
209 |
210 | public function clear()
211 | {
212 | $this->_headers = null;
213 | $this->_cookies = null;
214 | $this->_statusCode = 200;
215 | $this->statusText = 'OK';
216 | $this->data = null;
217 | $this->stream = null;
218 | $this->content = null;
219 | $this->isSent = false;
220 | }
221 |
222 | protected function sendHeaders()
223 | {
224 | /*if (headers_sent()) {
225 | return;
226 | }*/
227 | $statusCode = $this->getStatusCode();
228 | $this->swooleResponse->status($statusCode);
229 | if ($this->_headers) {
230 | $headers = $this->getHeaders();
231 | foreach ($headers as $name => $values) {
232 | $name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name)));
233 | // set replace for first occurrence of header but false afterwards to allow multiple
234 | $this->swooleResponse->header($name, end( $values ) );
235 | }
236 | }
237 | $this->sendCookies();
238 | }
239 |
240 | protected function sendCookies()
241 | {
242 | $session = Yii::$app->getSession();
243 | $data = $session->getCookieParams();
244 | $this->swooleResponse->cookie($session->getName(), $session->getId(), time()+$data['lifetime'], $data['path'], $data['domain'], $data['secure'], $data['httponly']);
245 | if ($this->_cookies === null) {
246 | return;
247 | }
248 | $request = Yii::$app->getRequest();
249 | if ($request->enableCookieValidation) {
250 | if ($request->cookieValidationKey == '') {
251 | throw new InvalidConfigException(get_class($request) . '::cookieValidationKey must be configured with a secret key.');
252 | }
253 | $validationKey = $request->cookieValidationKey;
254 | }
255 | foreach ($this->getCookies() as $cookie) {
256 | $value = $cookie->value;
257 | if ($cookie->expire != 1 && isset($validationKey)) {
258 | $value = Yii::$app->getSecurity()->hashData(serialize([$cookie->name, $value]), $validationKey);
259 | }
260 | $this->swooleResponse->cookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly);
261 | }
262 | }
263 |
264 | protected function sendContent()
265 | {
266 | if ($this->stream === null) {
267 | $this->swooleResponse->end( $this->content );
268 |
269 | $session = Yii::$app->getSession();
270 | $session->persist();
271 | return;
272 | }
273 |
274 | set_time_limit(0); // Reset time limit for big files
275 | $chunkSize = 8 * 1024 * 1024; // 8MB per chunk
276 |
277 | if (is_array($this->stream)) {
278 | list ($handle, $begin, $end) = $this->stream;
279 | fseek($handle, $begin);
280 | while (!feof($handle) && ($pos = ftell($handle)) <= $end) {
281 | if ($pos + $chunkSize > $end) {
282 | $chunkSize = $end - $pos + 1;
283 | }
284 | $this->swooleResponse->write( fread($handle, $chunkSize) );
285 | flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
286 | }
287 | fclose($handle);
288 | $this->swooleResponse->end(null);
289 | } else {
290 | while (!feof($this->stream)) {
291 | $this->swooleResponse->write( fread($this->stream, $chunkSize) );
292 | flush();
293 | }
294 | fclose($this->stream);
295 | $this->swooleResponse->end(null);
296 | }
297 | $session = Yii::$app->getSession();
298 | $session->persist();
299 | }
300 |
301 | public function sendFile($filePath, $attachmentName = null, $options = [])
302 | {
303 | if (!isset($options['mimeType'])) {
304 | $options['mimeType'] = FileHelper::getMimeTypeByExtension($filePath);
305 | }
306 | if ($attachmentName === null) {
307 | $attachmentName = basename($filePath);
308 | }
309 | $handle = fopen($filePath, 'rb');
310 | $this->sendStreamAsFile($handle, $attachmentName, $options);
311 |
312 | return $this;
313 | }
314 |
315 | public function sendContentAsFile($content, $attachmentName, $options = [])
316 | {
317 | $headers = $this->getHeaders();
318 |
319 | $contentLength = StringHelper::byteLength($content);
320 | $range = $this->getHttpRange($contentLength);
321 |
322 | if ($range === false) {
323 | $headers->set('Content-Range', "bytes */$contentLength");
324 | throw new RangeNotSatisfiableHttpException();
325 | }
326 |
327 | list($begin, $end) = $range;
328 | if ($begin != 0 || $end != $contentLength - 1) {
329 | $this->setStatusCode(206);
330 | $headers->set('Content-Range', "bytes $begin-$end/$contentLength");
331 | $this->content = StringHelper::byteSubstr($content, $begin, $end - $begin + 1);
332 | } else {
333 | $this->setStatusCode(200);
334 | $this->content = $content;
335 | }
336 |
337 | $mimeType = isset($options['mimeType']) ? $options['mimeType'] : 'application/octet-stream';
338 | $this->setDownloadHeaders($attachmentName, $mimeType, !empty($options['inline']), $end - $begin + 1);
339 |
340 | $this->format = self::FORMAT_RAW;
341 |
342 | return $this;
343 | }
344 |
345 | public function sendStreamAsFile($handle, $attachmentName, $options = [])
346 | {
347 | $headers = $this->getHeaders();
348 | if (isset($options['fileSize'])) {
349 | $fileSize = $options['fileSize'];
350 | } else {
351 | fseek($handle, 0, SEEK_END);
352 | $fileSize = ftell($handle);
353 | }
354 |
355 | $range = $this->getHttpRange($fileSize);
356 | if ($range === false) {
357 | $headers->set('Content-Range', "bytes */$fileSize");
358 | throw new RangeNotSatisfiableHttpException();
359 | }
360 |
361 | list($begin, $end) = $range;
362 | if ($begin != 0 || $end != $fileSize - 1) {
363 | $this->setStatusCode(206);
364 | $headers->set('Content-Range', "bytes $begin-$end/$fileSize");
365 | } else {
366 | $this->setStatusCode(200);
367 | }
368 |
369 | $mimeType = isset($options['mimeType']) ? $options['mimeType'] : 'application/octet-stream';
370 | $this->setDownloadHeaders($attachmentName, $mimeType, !empty($options['inline']), $end - $begin + 1);
371 |
372 | $this->format = self::FORMAT_RAW;
373 | $this->stream = [$handle, $begin, $end];
374 |
375 | return $this;
376 | }
377 |
378 | public function setDownloadHeaders($attachmentName, $mimeType = null, $inline = false, $contentLength = null)
379 | {
380 | $headers = $this->getHeaders();
381 |
382 | $disposition = $inline ? 'inline' : 'attachment';
383 | $headers->setDefault('Pragma', 'public')
384 | ->setDefault('Accept-Ranges', 'bytes')
385 | ->setDefault('Expires', '0')
386 | ->setDefault('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
387 | ->setDefault('Content-Disposition', $this->getDispositionHeaderValue($disposition, $attachmentName));
388 |
389 | if ($mimeType !== null) {
390 | $headers->setDefault('Content-Type', $mimeType);
391 | }
392 |
393 | if ($contentLength !== null) {
394 | $headers->setDefault('Content-Length', $contentLength);
395 | }
396 |
397 | return $this;
398 | }
399 |
400 | protected function getHttpRange($fileSize)
401 | {
402 | if (!isset($_SERVER['HTTP_RANGE']) || $_SERVER['HTTP_RANGE'] === '-') {
403 | return [0, $fileSize - 1];
404 | }
405 | if (!preg_match('/^bytes=(\d*)-(\d*)$/', $_SERVER['HTTP_RANGE'], $matches)) {
406 | return false;
407 | }
408 | if ($matches[1] === '') {
409 | $start = $fileSize - $matches[2];
410 | $end = $fileSize - 1;
411 | } elseif ($matches[2] !== '') {
412 | $start = $matches[1];
413 | $end = $matches[2];
414 | if ($end >= $fileSize) {
415 | $end = $fileSize - 1;
416 | }
417 | } else {
418 | $start = $matches[1];
419 | $end = $fileSize - 1;
420 | }
421 | if ($start < 0 || $start > $end) {
422 | return false;
423 | } else {
424 | return [$start, $end];
425 | }
426 | }
427 |
428 | public function xSendFile($filePath, $attachmentName = null, $options = [])
429 | {
430 | if ($attachmentName === null) {
431 | $attachmentName = basename($filePath);
432 | }
433 | if (isset($options['mimeType'])) {
434 | $mimeType = $options['mimeType'];
435 | } elseif (($mimeType = FileHelper::getMimeTypeByExtension($filePath)) === null) {
436 | $mimeType = 'application/octet-stream';
437 | }
438 | if (isset($options['xHeader'])) {
439 | $xHeader = $options['xHeader'];
440 | } else {
441 | $xHeader = 'X-Sendfile';
442 | }
443 |
444 | $disposition = empty($options['inline']) ? 'attachment' : 'inline';
445 | $this->getHeaders()
446 | ->setDefault($xHeader, $filePath)
447 | ->setDefault('Content-Type', $mimeType)
448 | ->setDefault('Content-Disposition', $this->getDispositionHeaderValue($disposition, $attachmentName));
449 |
450 | $this->format = self::FORMAT_RAW;
451 |
452 | return $this;
453 | }
454 |
455 | protected function getDispositionHeaderValue($disposition, $attachmentName)
456 | {
457 | $fallbackName = str_replace('"', '\\"', str_replace(['%', '/', '\\'], '_', Inflector::transliterate($attachmentName, Inflector::TRANSLITERATE_LOOSE)));
458 | $utfName = rawurlencode(str_replace(['%', '/', '\\'], '', $attachmentName));
459 |
460 | $dispositionHeader = "{$disposition}; filename=\"{$fallbackName}\"";
461 | if ($utfName !== $fallbackName) {
462 | $dispositionHeader .= "; filename*=utf-8''{$utfName}";
463 | }
464 | return $dispositionHeader;
465 | }
466 |
467 | public function redirect($url, $statusCode = 302, $checkAjax = true)
468 | {
469 | if (is_array($url) && isset($url[0])) {
470 | // ensure the route is absolute
471 | $url[0] = '/' . ltrim($url[0], '/');
472 | }
473 | $url = Url::to($url);
474 | if (strpos($url, '/') === 0 && strpos($url, '//') !== 0) {
475 | $hostInfo = Yii::$app->getRequest()->getHostInfo();
476 | if( strpos($hostInfo, 'http://') === 0 || strpos($hostInfo, 'https://') === 0 ){
477 | $url = $hostInfo . $url;
478 | }else{
479 | $url = (Yii::$app->getRequest()->getIsSecureConnection() ? "https://" : "http://" ) . $hostInfo . $url;
480 | }
481 | }
482 |
483 | if ($checkAjax) {
484 | if (Yii::$app->getRequest()->getIsAjax()) {
485 | if (Yii::$app->getRequest()->getHeaders()->get('X-Ie-Redirect-Compatibility') !== null && $statusCode === 302) {
486 | // Ajax 302 redirect in IE does not work. Change status code to 200. See https://github.com/yiisoft/yii2/issues/9670
487 | $statusCode = 200;
488 | }
489 | if (Yii::$app->getRequest()->getIsPjax()) {
490 | $this->swooleResponse->header('X-Pjax-Url', $url);
491 | } else {
492 | $this->swooleResponse->header('X-Redirect', $url);
493 | }
494 | } else {
495 | $this->swooleResponse->header('Location', $url);
496 | }
497 | } else {
498 | $this->swooleResponse->header('Location', $url);
499 | }
500 |
501 | $this->setStatusCode($statusCode);
502 |
503 | return $this;
504 | }
505 |
506 | public function refresh($anchor = '')
507 | {
508 | return $this->redirect(Yii::$app->getRequest()->getUrl() . $anchor);
509 | }
510 |
511 | private $_cookies;
512 |
513 | public function getCookies()
514 | {
515 | if ($this->_cookies === null) {
516 | $this->_cookies = new CookieCollection;
517 | }
518 | return $this->_cookies;
519 | }
520 |
521 | public function getIsInvalid()
522 | {
523 | return $this->getStatusCode() < 100 || $this->getStatusCode() >= 600;
524 | }
525 |
526 | public function getIsInformational()
527 | {
528 | return $this->getStatusCode() >= 100 && $this->getStatusCode() < 200;
529 | }
530 |
531 | public function getIsSuccessful()
532 | {
533 | return $this->getStatusCode() >= 200 && $this->getStatusCode() < 300;
534 | }
535 |
536 | public function getIsRedirection()
537 | {
538 | return $this->getStatusCode() >= 300 && $this->getStatusCode() < 400;
539 | }
540 |
541 | public function getIsClientError()
542 | {
543 | return $this->getStatusCode() >= 400 && $this->getStatusCode() < 500;
544 | }
545 |
546 | public function getIsServerError()
547 | {
548 | return $this->getStatusCode() >= 500 && $this->getStatusCode() < 600;
549 | }
550 |
551 | public function getIsOk()
552 | {
553 | return $this->getStatusCode() == 200;
554 | }
555 |
556 | public function getIsForbidden()
557 | {
558 | return $this->getStatusCode() == 403;
559 | }
560 |
561 | public function getIsNotFound()
562 | {
563 | return $this->getStatusCode() == 404;
564 | }
565 |
566 | public function getIsEmpty()
567 | {
568 | return in_array($this->getStatusCode(), [201, 204, 304]);
569 | }
570 |
571 | protected function defaultFormatters()
572 | {
573 | return [
574 | self::FORMAT_HTML => 'yii\web\HtmlResponseFormatter',
575 | self::FORMAT_XML => 'yii\web\XmlResponseFormatter',
576 | self::FORMAT_JSON => 'yii\web\JsonResponseFormatter',
577 | self::FORMAT_JSONP => [
578 | 'class' => 'yii\web\JsonResponseFormatter',
579 | 'useJsonp' => true,
580 | ],
581 | ];
582 | }
583 |
584 | protected function prepare()
585 | {
586 | if ($this->stream !== null) {
587 | return;
588 | }
589 |
590 | if (isset($this->formatters[$this->format])) {
591 | $formatter = $this->formatters[$this->format];
592 | if (!is_object($formatter)) {
593 | $this->formatters[$this->format] = $formatter = Yii::createObject($formatter);
594 | }
595 | if ($formatter instanceof ResponseFormatterInterface) {
596 | $formatter->format($this);
597 | } else {
598 | throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatterInterface.");
599 | }
600 | } elseif ($this->format === self::FORMAT_RAW) {
601 | if ($this->data !== null) {
602 | $this->content = $this->data;
603 | }
604 | } else {
605 | throw new InvalidConfigException("Unsupported response format: {$this->format}");
606 | }
607 |
608 | if (is_array($this->content)) {
609 | throw new InvalidArgumentException('Response content must not be an array.');
610 | } elseif (is_object($this->content)) {
611 | if( in_array($this->getStatusCode(), [301, 302]) ){
612 | $this->content = null;
613 | }else {
614 | if (method_exists($this->content, '__toString')) {
615 | $this->content = $this->content->__toString();
616 | } else {
617 | throw new InvalidArgumentException('Response content must be a string or an object implementing __toString().');
618 | }
619 | }
620 | }
621 | }
622 | }
623 |
--------------------------------------------------------------------------------
/src/web/Request.php:
--------------------------------------------------------------------------------
1 | true];
34 |
35 | public $enableCsrfCookie = true;
36 |
37 | public $enableCookieValidation = true;
38 |
39 | public $cookieValidationKey;
40 |
41 | public $methodParam = '_method';
42 |
43 | public $parsers = [];
44 |
45 |
46 | private $_cookies;
47 |
48 | private $_headers;
49 |
50 |
51 | public function resolve()
52 | {
53 | $result = Yii::$app->getUrlManager()->parseRequest($this);
54 | if ($result !== false) {
55 | list ($route, $params) = $result;
56 | if ($this->_queryParams === null) {
57 | $this->setQueryParams( array_merge($params, $this->getQueryParams()) ); // preserve numeric keys
58 | } else {
59 | $this->_queryParams = $params + $this->_queryParams;
60 | }
61 | return [$route, $this->getQueryParams()];
62 | }
63 |
64 | throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
65 | }
66 |
67 | public function getHeaders()
68 | {
69 | if ($this->_headers === null) {
70 | $this->_headers = new HeaderCollection;
71 | $headers = $this->swooleRequest->header;
72 | foreach ($headers as $name => $value) {
73 | $this->_headers->add($name, $value);
74 | }
75 | }
76 | return $this->_headers;
77 | }
78 |
79 | public function getMethod()
80 | {
81 | return $this->swooleRequest->server["request_method"];
82 | }
83 |
84 | public function getIsGet()
85 | {
86 | return $this->getMethod() === 'GET';
87 | }
88 |
89 | public function getIsOptions()
90 | {
91 | return $this->getMethod() === 'OPTIONS';
92 | }
93 |
94 | public function getIsHead()
95 | {
96 | return $this->getMethod() === 'HEAD';
97 | }
98 |
99 | public function getIsPost()
100 | {
101 | return $this->getMethod() === 'POST';
102 | }
103 |
104 | public function getIsDelete()
105 | {
106 | return $this->getMethod() === 'DELETE';
107 | }
108 |
109 | public function getIsPut()
110 | {
111 | return $this->getMethod() === 'PUT';
112 | }
113 |
114 | public function getIsPatch()
115 | {
116 | return $this->getMethod() === 'PATCH';
117 | }
118 |
119 | public function getIsAjax()
120 | {
121 | return isset($this->swooleRequest->header["x-requested-with"]) && $this->swooleRequest->header["x-requested-with"] === 'XMLHttpRequest';
122 | }
123 |
124 | public function getIsPjax()
125 | {
126 | return $this->getIsAjax() && !empty($this->swooleRequest->header["x-pjax"]);
127 | }
128 |
129 | public function getIsFlash()
130 | {
131 | return isset($this->swooleRequest->header["user-agent"]) &&
132 | (stripos($this->swooleRequest->header["user-agent"], 'Shockwave') !== false || stripos($this->swooleRequest->header["user-agent"], 'Flash') !== false);
133 | }
134 |
135 | private $_rawBody;
136 |
137 | public function getRawBody()
138 | {
139 | if ($this->_rawBody === null) {
140 | $this->_rawBody = $this->swooleRequest->rawContent();
141 | return $this->_rawBody;
142 | }
143 |
144 | return $this->_rawBody;
145 | }
146 |
147 | public function setRawBody($rawBody)
148 | {
149 | $this->_rawBody = $rawBody;
150 | }
151 |
152 | private $_bodyParams;
153 |
154 | public function getBodyParams()
155 | {
156 | if ($this->_bodyParams === null) {
157 | if (isset($this->swooleRequest->post[$this->methodParam])) {
158 | $this->_bodyParams = $this->swooleRequest->post;
159 | unset($this->_bodyParams[$this->methodParam]);
160 | return $this->_bodyParams;
161 | }
162 |
163 | $rawContentType = $this->getContentType();
164 | if (($pos = strpos($rawContentType, ';')) !== false) {
165 | // e.g. application/json; charset=UTF-8
166 | $contentType = substr($rawContentType, 0, $pos);
167 | } else {
168 | $contentType = $rawContentType;
169 | }
170 |
171 | if (isset($this->parsers[$contentType])) {
172 | $parser = Yii::createObject($this->parsers[$contentType]);
173 | if (!($parser instanceof RequestParserInterface)) {
174 | throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
175 | }
176 | $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
177 | } elseif (isset($this->parsers['*'])) {
178 | $parser = Yii::createObject($this->parsers['*']);
179 | if (!($parser instanceof RequestParserInterface)) {
180 | throw new InvalidConfigException("The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
181 | }
182 | $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
183 | } elseif ($this->getMethod() === 'POST') {
184 | // PHP has already parsed the body so we have all params in $_POST
185 | $this->_bodyParams = $this->swooleRequest->post;
186 | } else {
187 | $this->_bodyParams = [];
188 | mb_parse_str($this->getRawBody(), $this->_bodyParams);
189 | }
190 | }
191 |
192 | return $this->_bodyParams;
193 | }
194 |
195 | public function setBodyParams($values)
196 | {
197 | $this->_bodyParams = $values;
198 | }
199 |
200 | public function getBodyParam($name, $defaultValue = null)
201 | {
202 | $params = $this->getBodyParams();
203 |
204 | return isset($params[$name]) ? $params[$name] : $defaultValue;
205 | }
206 |
207 | public function post($name = null, $defaultValue = null)
208 | {
209 | if ($name === null) {
210 | return $this->getBodyParams();
211 | }
212 |
213 | return $this->getBodyParam($name, $defaultValue);
214 | }
215 |
216 | private $_queryParams;
217 |
218 | public function getQueryParams()
219 | {
220 | if ($this->_queryParams === null) {
221 | $get = [];
222 | if( isset( $this->swooleRequest->server['query_string'] )){
223 | $temp = explode('&', urldecode( $this->swooleRequest->server['query_string'] ) );
224 | foreach ($temp as $v){
225 | $arr = explode('=', $v);
226 | if( count($arr) < 2 ) continue;
227 | if( preg_match('/\[(.*)\]/i', $arr[0], $matches) ){
228 | $get[str_replace($matches[0], '', $arr[0])][$matches[1]] = $arr[1];
229 | }else {
230 | $get[$arr[0]] = $arr[1];
231 | }
232 | }
233 | };
234 | return $get;
235 | }
236 |
237 | return $this->_queryParams;
238 | }
239 |
240 | public function setQueryParams($values)
241 | {
242 | $this->_queryParams = $values;
243 | }
244 |
245 | public function get($name = null, $defaultValue = null)
246 | {
247 | if ($name === null) {
248 | return $this->getQueryParams();
249 | }
250 |
251 | return $this->getQueryParam($name, $defaultValue);
252 | }
253 |
254 | public function getQueryParam($name, $defaultValue = null)
255 | {
256 | $params = $this->getQueryParams();
257 |
258 | return isset($params[$name]) ? $params[$name] : $defaultValue;
259 | }
260 |
261 | private $_hostInfo;
262 | private $_hostName;
263 |
264 | public function getHostInfo()
265 | {
266 | if ($this->_hostInfo === null) {
267 | $this->_hostInfo = $this->swooleRequest->header['host'];
268 | }
269 | return $this->_hostInfo;
270 | }
271 |
272 | public function setHostInfo($value)
273 | {
274 | $this->_hostName = null;
275 | $this->_hostInfo = $value === null ? null : rtrim($value, '/');
276 | }
277 |
278 |
279 | public function getHostName()
280 | {
281 | if ($this->_hostName === null) {
282 | $this->_hostName = parse_url($this->getHostInfo(), PHP_URL_HOST);
283 | }
284 |
285 | return $this->_hostName;
286 | }
287 |
288 | private $_baseUrl;
289 |
290 | public function getBaseUrl()
291 | {
292 | if ($this->_baseUrl === null) {
293 | $this->_baseUrl = rtrim(dirname($this->getScriptUrl()), '\\/');
294 | }
295 |
296 | return $this->_baseUrl;
297 | }
298 |
299 | public function setBaseUrl($value)
300 | {
301 | $this->_baseUrl = $value;
302 | }
303 |
304 | private $_scriptUrl;
305 |
306 | public function getScriptUrl()
307 | {
308 | if ($this->_scriptUrl === null) {
309 | $this->_scriptUrl = '/';
310 | }
311 |
312 | return $this->_scriptUrl;
313 | }
314 |
315 | public function setScriptUrl($value)
316 | {
317 | $this->_scriptUrl = $value === null ? null : '/' . trim($value, '/');
318 | }
319 |
320 | private $_scriptFile;
321 |
322 |
323 | public function getScriptFile()
324 | {
325 | if (isset($this->_scriptFile)) {
326 | return $this->_scriptFile;
327 | }
328 |
329 | return Yii::getAlias("@web");
330 | }
331 |
332 | public function setScriptFile($value)
333 | {
334 | $this->_scriptFile = $value;
335 | }
336 |
337 | private $_pathInfo;
338 |
339 | public function getPathInfo()
340 | {
341 | if ($this->_pathInfo === null) {
342 | $this->_pathInfo = $this->resolvePathInfo();
343 | }
344 |
345 | return $this->_pathInfo;
346 | }
347 |
348 | public function setPathInfo($value)
349 | {
350 | $this->_pathInfo = $value === null ? null : ltrim($value, '/');
351 | }
352 |
353 | protected function resolvePathInfo()
354 | {
355 | $pathInfo = $this->getUrl();
356 |
357 | if (($pos = strpos($pathInfo, '?')) !== false) {
358 | $pathInfo = substr($pathInfo, 0, $pos);
359 | }
360 |
361 | $pathInfo = urldecode($pathInfo);
362 |
363 | // try to encode in UTF8 if not so
364 | // http://w3.org/International/questions/qa-forms-utf-8.html
365 | if (!preg_match('%^(?:
366 | [\x09\x0A\x0D\x20-\x7E] # ASCII
367 | | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
368 | | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
369 | | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
370 | | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
371 | | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
372 | | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
373 | | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
374 | )*$%xs', $pathInfo)
375 | ) {
376 | $pathInfo = utf8_encode($pathInfo);
377 | }
378 |
379 | $pathInfo = substr($pathInfo, 1);
380 |
381 | if (substr($pathInfo, 0, 1) === '/') {
382 | $pathInfo = substr($pathInfo, 1);
383 | }
384 |
385 | return (string) $pathInfo;
386 | }
387 |
388 | public function getAbsoluteUrl()
389 | {
390 | return $this->getHostInfo() . $this->getUrl();
391 | }
392 |
393 | private $_url;
394 |
395 | public function getUrl()
396 | {
397 | if ($this->_url === null) {
398 | $this->_url = $this->resolveRequestUri();
399 | }
400 |
401 | return $this->_url;
402 | }
403 |
404 | public function setUrl($value)
405 | {
406 | $this->_url = $value;
407 | }
408 |
409 | protected function resolveRequestUri()
410 | {
411 | $requestUri = $this->swooleRequest->server['request_uri'];
412 | if( isset($this->swooleRequest->server['query_string']) ) $requestUri .= '?' . $this->swooleRequest->server['query_string'];
413 | return $requestUri;
414 | }
415 |
416 | public function getQueryString()
417 | {
418 | return isset($this->swooleRequest->server['query_string']) ? $this->swooleRequest->server['query_string'] : '';
419 | }
420 |
421 | public function getIsSecureConnection()
422 | {
423 | return false;
424 | }
425 |
426 | public function getServerName()
427 | {
428 | return $_SERVER['HOSTNAME'];
429 | }
430 |
431 | public function getServerPort()
432 | {
433 | return $this->swooleRequest->server['server_port'];
434 | }
435 |
436 | public function getReferrer()
437 | {
438 | return isset( $this->swooleRequest->header["referer"] ) ? $this->swooleRequest->header["referer"] : null ;
439 | }
440 |
441 | public function getUserAgent()
442 | {
443 | return isset( $this->swooleRequest->header['user-agent'] ) ? $this->swooleRequest->header['user-agent'] : null;
444 | }
445 |
446 | public function getUserIP()
447 | {
448 | return isset( $this->swooleRequest->server['remote_addr'] ) ? $this->swooleRequest->server['remote_addr'] : null;
449 | }
450 |
451 | public function getUserHost()
452 | {
453 | return isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : null;
454 | }
455 |
456 | public function getAuthUser()
457 | {
458 | return isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
459 | }
460 |
461 | public function getAuthPassword()
462 | {
463 | return isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
464 | }
465 |
466 | private $_port;
467 |
468 | public function getPort()
469 | {
470 | return $this->swooleRequest->server['server_port'];
471 | }
472 |
473 | public function setPort($value)
474 | {
475 | if ($value != $this->_port) {
476 | $this->_port = (int) $value;
477 | $this->_hostInfo = null;
478 | }
479 | }
480 |
481 | private $_securePort;
482 |
483 | public function getSecurePort()
484 | {
485 | if ($this->_securePort === null) {
486 | $this->_securePort = $this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : 443;
487 | }
488 |
489 | return $this->_securePort;
490 | }
491 |
492 | public function setSecurePort($value)
493 | {
494 | if ($value != $this->_securePort) {
495 | $this->_securePort = (int) $value;
496 | $this->_hostInfo = null;
497 | }
498 | }
499 |
500 | private $_contentTypes;
501 |
502 | public function getAcceptableContentTypes()
503 | {
504 | if ($this->_contentTypes === null) {
505 | if (isset($this->swooleRequest->header["accept"])) {
506 | $this->_contentTypes = $this->parseAcceptHeader($this->swooleRequest->header["accept"]);
507 | } else {
508 | $this->_contentTypes = [];
509 | }
510 | }
511 |
512 | return $this->_contentTypes;
513 | }
514 |
515 | public function setAcceptableContentTypes($value)
516 | {
517 | $this->_contentTypes = $value;
518 | }
519 |
520 | public function getContentType()
521 | {
522 | if (isset($this->swooleRequest->header['content-type'])) {
523 | return $this->swooleRequest->header['content-type'];
524 | }
525 |
526 | return null;
527 | }
528 |
529 | private $_languages;
530 |
531 | public function getAcceptableLanguages()
532 | {
533 | if ($this->_languages === null) {
534 | if (isset($this->swooleRequest->header['accept-language'])) {
535 | $this->_languages = array_keys($this->parseAcceptHeader($this->swooleRequest->header['accept-language']));
536 | } else {
537 | $this->_languages = [];
538 | }
539 | }
540 |
541 | return $this->_languages;
542 | }
543 |
544 | public function setAcceptableLanguages($value)
545 | {
546 | $this->_languages = $value;
547 | }
548 |
549 | public function parseAcceptHeader($header)
550 | {
551 | $accepts = [];
552 | foreach (explode(',', $header) as $i => $part) {
553 | $params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY);
554 | if (empty($params)) {
555 | continue;
556 | }
557 | $values = [
558 | 'q' => [$i, array_shift($params), 1],
559 | ];
560 | foreach ($params as $param) {
561 | if (strpos($param, '=') !== false) {
562 | list ($key, $value) = explode('=', $param, 2);
563 | if ($key === 'q') {
564 | $values['q'][2] = (double) $value;
565 | } else {
566 | $values[$key] = $value;
567 | }
568 | } else {
569 | $values[] = $param;
570 | }
571 | }
572 | $accepts[] = $values;
573 | }
574 |
575 | usort($accepts, function ($a, $b) {
576 | $a = $a['q']; // index, name, q
577 | $b = $b['q'];
578 | if ($a[2] > $b[2]) {
579 | return -1;
580 | }
581 |
582 | if ($a[2] < $b[2]) {
583 | return 1;
584 | }
585 |
586 | if ($a[1] === $b[1]) {
587 | return $a[0] > $b[0] ? 1 : -1;
588 | }
589 |
590 | if ($a[1] === '*/*') {
591 | return 1;
592 | }
593 |
594 | if ($b[1] === '*/*') {
595 | return -1;
596 | }
597 |
598 | $wa = $a[1][strlen($a[1]) - 1] === '*';
599 | $wb = $b[1][strlen($b[1]) - 1] === '*';
600 | if ($wa xor $wb) {
601 | return $wa ? 1 : -1;
602 | }
603 |
604 | return $a[0] > $b[0] ? 1 : -1;
605 | });
606 |
607 | $result = [];
608 | foreach ($accepts as $accept) {
609 | $name = $accept['q'][1];
610 | $accept['q'] = $accept['q'][2];
611 | $result[$name] = $accept;
612 | }
613 |
614 | return $result;
615 | }
616 |
617 | public function getPreferredLanguage(array $languages = [])
618 | {
619 | if (empty($languages)) {
620 | return Yii::$app->language;
621 | }
622 | foreach ($this->getAcceptableLanguages() as $acceptableLanguage) {
623 | $acceptableLanguage = str_replace('_', '-', strtolower($acceptableLanguage));
624 | foreach ($languages as $language) {
625 | $normalizedLanguage = str_replace('_', '-', strtolower($language));
626 |
627 | if ($normalizedLanguage === $acceptableLanguage || // en-us==en-us
628 | strpos($acceptableLanguage, $normalizedLanguage . '-') === 0 || // en==en-us
629 | strpos($normalizedLanguage, $acceptableLanguage . '-') === 0) { // en-us==en
630 |
631 | return $language;
632 | }
633 | }
634 | }
635 |
636 | return reset($languages);
637 | }
638 |
639 | //待修改
640 | public function getETags()
641 | {
642 | if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
643 | return preg_split('/[\s,]+/', str_replace('-gzip', '', $_SERVER['HTTP_IF_NONE_MATCH']), -1, PREG_SPLIT_NO_EMPTY);
644 | }
645 |
646 | return [];
647 | }
648 |
649 | public function getCookies()
650 | {
651 | if ($this->_cookies === null) {
652 | $this->_cookies = new CookieCollection($this->loadCookies(), [
653 | 'readOnly' => true,
654 | ]);
655 | }
656 |
657 | return $this->_cookies;
658 | }
659 |
660 | protected function loadCookies()
661 | {
662 | $cookies = [];
663 | if ($this->enableCookieValidation) {
664 | if ($this->cookieValidationKey == '') {
665 | throw new InvalidConfigException(get_class($this) . '::cookieValidationKey must be configured with a secret key.');
666 | }
667 | if( !isset($this->swooleRequest->cookie) ) return [];
668 | foreach ($this->swooleRequest->cookie as $name => $value) {
669 | if (!is_string($value)) {
670 | continue;
671 | }
672 | $data = Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey);
673 | if ($data === false) {
674 | continue;
675 | }
676 | $data = @unserialize($data);
677 | if (is_array($data) && isset($data[0], $data[1]) && $data[0] === $name) {
678 | $cookies[$name] = new Cookie([
679 | 'name' => $name,
680 | 'value' => $data[1],
681 | 'expire' => null,
682 | ]);
683 | }
684 | }
685 | } else {
686 | foreach ($this->swooleRequest->cookie as $name => $value) {
687 | $cookies[$name] = new Cookie([
688 | 'name' => $name,
689 | 'value' => $value,
690 | 'expire' => null,
691 | ]);
692 | }
693 | }
694 |
695 | return $cookies;
696 | }
697 |
698 | private $_csrfToken;
699 |
700 | public function getCsrfToken($regenerate = false)
701 | {
702 | if ($this->_csrfToken === null || $regenerate) {
703 | if ($regenerate || ($token = $this->loadCsrfToken()) === null) {
704 | $token = $this->generateCsrfToken();
705 | }
706 | $this->_csrfToken = Yii::$app->security->maskToken($token);
707 | }
708 |
709 | return $this->_csrfToken;
710 | }
711 |
712 | protected function loadCsrfToken()
713 | {
714 | if ($this->enableCsrfCookie) {
715 | return $this->getCookies()->getValue($this->csrfParam);
716 | }
717 | return Yii::$app->getSession()->get($this->csrfParam);
718 | }
719 |
720 | protected function generateCsrfToken()
721 | {
722 | $token = Yii::$app->getSecurity()->generateRandomKey();
723 | if ($this->enableCsrfCookie) {
724 | $cookie = $this->createCsrfCookie($token);
725 | Yii::$app->getResponse()->getCookies()->add($cookie);
726 | } else {
727 | Yii::$app->getSession()->set($this->csrfParam, $token);
728 | }
729 | return $token;
730 | }
731 |
732 | public function getCsrfTokenFromHeader()
733 | {
734 | return $this->headers->get(static::CSRF_HEADER);
735 | }
736 |
737 | protected function createCsrfCookie($token)
738 | {
739 | $options = $this->csrfCookie;
740 | $options['name'] = $this->csrfParam;
741 | $options['value'] = $token;
742 | return new Cookie($options);
743 | }
744 |
745 | public function validateCsrfToken($clientSuppliedToken = null)
746 | {
747 | $method = $this->getMethod();
748 | // only validate CSRF token on non-"safe" methods http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
749 | if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
750 | return true;
751 | }
752 |
753 | $trueToken = $this->getCsrfToken();
754 |
755 | if ($clientSuppliedToken !== null) {
756 | return $this->validateCsrfTokenInternal($clientSuppliedToken, $trueToken);
757 | }
758 |
759 | return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
760 | || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
761 | }
762 |
763 | private function validateCsrfTokenInternal($clientSuppliedToken, $trueToken)
764 | {
765 | if (!is_string($clientSuppliedToken)) {
766 | return false;
767 | }
768 |
769 | $security = Yii::$app->security;
770 |
771 | return $security->unmaskToken($clientSuppliedToken) === $security->unmaskToken($trueToken);
772 | }
773 | }
774 |
--------------------------------------------------------------------------------