本篇文章将介绍怎么使用swoole运行yii2 advanced框架。在swoole中正常使用web服务器所支持的一般功能,同时运行了http服务和socket服务。在实践此文章前请先在自己的机器上安装上下面介绍的项目环境。该项目和一般的yii2 advanced一样,只是有3个地方不同。这里只介绍3个重要地方:swoole脚本文件、swoole服务配置文件、yii2 advanced中源码request.php修改
centos7+php7+swoole2.2+yii2 advanced
这是一个运行swoole服务的php脚本(YiiWebsocket.php),您可以放在任意位置,使用php运行他
php YiiWebsockrt.php
源码如下
<?php
/**
* websocket服务器swoole入口脚本(兼容websocket和http响应)
* @author 老虎还是猫
* @property THIS_APP 运行模块名称
* @property ROOT_DIR 根据实际情况,定义站点根目录。用于清晰脚本存放位置(比如本项目的站点根目录为/root/advanced/frontend/web)
* @property YII_ROOT 定义yii2 advanced的根目录,便于引用
* @date 2018/06/21
*/
define('THIS_APP','frontend');
//根据实际情况,定义站点根目录。
define('ROOT_DIR', '/root/advanced/'.THIS_APP.'/web');
//定义yii2 advanced的根目录,便于引用
define('YII_ROOT','/root/advanced');
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
require YII_ROOT . '/vendor/autoload.php';
require YII_ROOT . '/vendor/yiisoft/yii2/Yii.php';
require YII_ROOT . '/common/config/bootstrap.php';
require YII_ROOT . '/'.THIS_APP.'/config/bootstrap.php';
class YiiWebSocket{
//声明配置信息
private static $config = null;
//yii2配置信息
private static $yiiconfig = null;
//swoole的web soccket server对象
private static $web_socket = null;
//入口方法(外部唯一调用方法)
public static function run(){
//获取配置信息。
if(empty(self::$config)){
self::$config = include(YII_ROOT . '/'.THIS_APP.'/config/ServerConfig.php');
}
//获取yii2配置信息。
if(empty(self::$yiiconfig)){
self::$yiiconfig = yii\helpers\ArrayHelper::merge(
require YII_ROOT . '/common/config/main.php',
require YII_ROOT . '/common/config/main-local.php',
require YII_ROOT . '/'.THIS_APP.'/config/main.php',
require YII_ROOT . '/'.THIS_APP.'/config/main-local.php'
);
}
if(empty(self::$web_socket)){
self::$web_socket = new swoole_websocket_server(self::$config['Server']['swoole_server_ip'], self::$config['Server']['swoole_http_server_port']);
}
self::$web_socket->set(self::$config['SwooleConfig']);
self::$web_socket->listen(self::$config['Server']['swoole_server_ip'], self::$config['Server']['swoole_socket_server_port'], SWOOLE_SOCK_TCP);
//websocket通信建立时触发
self::$web_socket->on('open', function (swoole_websocket_server $server, $request) {
echo "server: handshake success with fd{$request->fd}\n";
});
//websocket client发送请求时触发
self::$web_socket->on('message', function (swoole_websocket_server $server, $frame) {
self::onMessage($server, $frame);
});
//websocket通信关闭时触发
self::$web_socket->on('close', function ($ser, $fd) {
//echo "client {$fd} closed\n";
});
//websocket继承httpserver,注册onRequest方法后可以响应http请求。否则报404
self::$web_socket->on('request', function($request, $response){
$return = self::onRequest($request, $response);
});
self::$web_socket->start();
}
//request方法,响应http请求
private static function onRequest($request, $response){
//阻止favicon.ico 进入
if ($request->server['request_uri']=='/favicon.ico'){
$response->header("Content-Type", "text/html; charset=utf-8");
$response->status(404);
$response->end();
return ;
}
//动态设置$response的header
$path_arr = pathinfo($request->server['request_uri']);
if(!empty($path_arr['extension'])){
//判断是否为脚本
if(array_key_exists($path_arr['extension'],self::$config['ContentType'])){
try{
$response->header("Content-Type", self::$config['ContentType'][$path_arr['extension']]);
$response->status(200);
$response->end(file_get_contents(ROOT_DIR . $request->server['request_uri']));
}catch(\Exception $e){
$response->header("Content-Type", "text/html; charset=utf-8");
$response->status(404);
$response->end();
throw $e;
}
return;
}
//判断是否为下载文件
if(array_key_exists($path_arr['extension'],self::$config['DownloadType'])){
try{
$response->header("Content-Type", self::$config['DownloadType'][$path_arr['extension']]);
$response->sendfile(urldecode(ROOT_DIR . $request->server['request_uri']));
}catch(\Exception $e){
$response->header("Content-Type", "text/html; charset=utf-8");
$response->status(404);
$response->end();
throw $e;
}
return;
}
}
//设置跨域请求
if(!empty($request->header['origin']) and in_array($request->header['origin'],self::$config['AccessArr'])){
header('Access-Control-Allow-Origin:'.$request->server['remote_addr']);
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Headers: TOKEN,Content-Type');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
}
//即时更新$_SERVER
foreach ($request->server as $k => $v) {
$_SERVER[strtoupper($k)] = $v;
}
foreach ($request->header as $k => $v) {
$_SERVER[strtoupper($k)] = $v;
}
$_SERVER['SCRIPT_FILENAME'] = ROOT_DIR;
//获取$_GET
$_GET = [];
if (isset($request->get)) {
foreach ($request->get as $k => $v) {
$_GET[$k] = $v;
}
}
//获取$_POST
$_POST = [];
if (isset($request->post)) {
foreach ($request->post as $k => $v) {
$_POST[$k] = $v;
}
}
//捕获异常,防止服务器持续等待
try{
$res = self::YiiLogick();
$response->header("Content-Type", "text/html; charset=utf-8");
$response->end($res);
}catch(Exception $e){
throw $e;
$response->header("Content-Type", "text/html; charset=utf-8");
$response->status(500);
$response->end();
}
return;
}
//websocket请求返回,使用第三方类库处理(\extend\)。上传信息使用json格式,其中route为路由
private static function onMessage(swoole_websocket_server $server, $frame){
try{
//解析数据
$received_data = json_decode($frame->data,1);
//获取路由信息
$route = $received_data['route'];
if(empty($received_data)){
$server->push($frame->fd, "Routing information must be required !!");
return;
}
$routeArr = explode('/',$received_data['route']);
$functionid = sizeof($routeArr)-1;
$functionname = $routeArr[$functionid];
unset($routeArr[$functionid]);
$classname = implode('\\',$routeArr);
$class = new $classname;
$class->init($frame->fd,$received_data);
$res = $class->$functionname();
$server->push($frame->fd, $res);
}catch(Exception $e){
echo $e->getTraceAsString();
$server->push($frame->fd, 'Websocket server error !!');
}
return;
}
//使用yii,运行系统业务逻辑
private static function YiiLogick(){
// 执行应用
ob_start();
try {
(new yii\web\Application(self::$yiiconfig))->run();
} catch (\Exception $exception) {
throw $exception;
return;
}
$res = ob_get_contents();
ob_end_clean();
return $res;
}
}
YiiWebSocket::run();
下面讲解下重要代码,其余代码都有详细注释
define('THIS_APP','frontend');
//根据实际情况,定义站点根目录。
define('ROOT_DIR', '/root/advanced/'.THIS_APP.'/web');
//定义yii2 advanced的根目录,便于引用
define('YII_ROOT','/root/advanced');
为了便于使用,我定义了3个常量
THIS_APP :默认的yii框架为我们提供了2个站点(frontend和backend),我们也能自己扩展新的站点。所以在这里进行定义我们 需要使用的站点。
ROOT_DIR :和一般的web服务器不同,swoole没有站点根目录这样的定义。他不会通过一个url访问到服务器上的资源,必须自己对url进行解析返回对应的服务器资源。为了便于使用资源,清晰资源的存放位置,所以在这里定义了一个站点根目录。所有的服务器的资源都应当放到该目录或者其子目录下。
YII_ROOT :因为要使用yii的框架逻辑所以我们需要引用其资源。所以应当在这里清晰的定义yii框架的根目录。
//获取配置信息。
if(empty(self::$config)){
self::$config = include(YII_ROOT . '/'.THIS_APP.'/config/ServerConfig.php');
}
为了编译管理swoole服务,我将所有需要用到的配置信息放到了一个文件中。在这里对其进行引用,若果您需要修改该文件的存放位置或者名称,同时需要修改这个地方。
$_SERVER['SCRIPT_FILENAME'] = ROOT_DIR;
yii对资源的引用是同过$_SERVER的部分参数来实现的,所以我们需要重写这部分。
这是一个存放所有swoole用到的配置参数的php文件(ServerConfig.php),我把他放到了/frontend/config/目录下。一个站点一个配置,我觉得这样比较合理,所以把他放到了fronted的配置文件夹里。当然您可以把他放到任何地方,只要保证能在swoole服务脚本文件中对其正常引用。下面是源码:
<?php
return [
//swoole服务配置信息
'Server' => [
// swoole服务ip
'swoole_server_ip' => '192.168.1.113',
// swoole服务http监听端口
'swoole_http_server_port' => '9501',
// swoole服务socket监听端口
'swoole_socket_server_port' => '9502',
//redis服务使用ip
'redis_ip' => '127.0.0.1',
//redis服务监听端口
'redis_port' => '6379',
],
//swoole配置参数
'SwooleConfig' => [
'log_file' => './log/swoole.log', //指定swoole错误日志文件
//'daemonize' => 1 //设置进程为守护进程,当使用systemd管理的时候,一定不要设置此参数,或者设置为0
],
//定义文件类型和Content-Type对应关系
'ContentType' => [
'xml' => 'application/xml,text/xml,application/x-xml',
'json' => 'application/json,text/x-json,application/jsonrequest,text/json',
'js' => 'text/javascript,application/javascript,application/x-javascript',
'css' => 'text/css',
'rss' => 'application/rss+xml',
'yaml' => 'application/x-yaml,text/yaml',
'atom' => 'application/atom+xml',
'pdf' => 'application/pdf',
'text' => 'text/plain',
//'image' => 'image/png,image/jpg,image/jpeg,image/pjpeg,image/gif,image/webp,image/*',
'png' => 'image/png',
'jpg' => 'image/jpg',
'jpeg' => 'image/jpeg',
'pjpeg' => 'image/pjpeg',
'gif' => 'image/gif',
'webp' => 'image/webp',
'csv' => 'text/csv',
'html' => 'text/html,application/xhtml+xml,*/*',
],
//定义可下载文件
'DownloadType' => [
'xls' => 'application/x-xls,application/vnd.ms-excel',
'tgz' => '',
'zip' => '',
],
//定义允许跨域请求的域名
'AccessArr' => [
'http://localhost:8080',
]
];
源码里面有详细的注释,我就不再解释了。
在\vendor\yiisoft\yii2\web目录下面找到Request.php,找到其中的getScriptUrl方法,修改如下
public function getScriptUrl()
{
if ($this->_scriptUrl === null) {
$this->_scriptUrl = '/';
/*
$scriptFile = $this->getScriptFile();
$scriptName = basename($scriptFile);file_put_contents(
if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $scriptName) {
$this->_scriptUrl = $_SERVER['SCRIPT_NAME'];
} elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $scriptName) {
$this->_scriptUrl = $_SERVER['PHP_SELF'];
} elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $scriptName) {
$this->_scriptUrl = $_SERVER['ORIG_SCRIPT_NAME'];
} elseif (isset($_SERVER['PHP_SELF']) && ($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) {
$this->_scriptUrl = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName;
} elseif (!empty($_SERVER['DOCUMENT_ROOT']) && strpos($scriptFile, $_SERVER['DOCUMENT_ROOT']) === 0) {
$this->_scriptUrl = str_replace([$_SERVER['DOCUMENT_ROOT'], '\\'], ['', '/'], $scriptFile);
} else {
throw new InvalidConfigException('Unable to determine the entry script URL.');
}*/
}
return $this->_scriptUrl;
}
这样才能保证对资源的正常引用
我把我测试使用的项目上传到了github Yii2-swoole,供大家参考