├── .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 | --------------------------------------------------------------------------------