├── public ├── .htaccess ├── index.php └── http_server.php ├── application ├── logs │ └── .gitkeep ├── library │ ├── readme.txt │ ├── TaskInterface.php │ ├── Task │ │ └── Email.php │ ├── TaskFactory.php │ ├── Response.php │ └── TokenLibrary.php ├── views │ ├── index │ │ └── index.phtml │ └── error │ │ └── error.phtml ├── models │ ├── Common.php │ ├── Order.php │ └── Sample.php ├── controllers │ ├── Error.php │ └── Index.php ├── plugins │ ├── Sample.php │ ├── Authentication.php │ └── Safe.php ├── helper │ └── functions.php └── Bootstrap.php ├── .gitignore ├── composer.json ├── README.md └── conf └── application.ini /public/.htaccess: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/logs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/* 2 | logs/attack/* -------------------------------------------------------------------------------- /application/library/readme.txt: -------------------------------------------------------------------------------- 1 | 项目库文件放在这里 2 | 默认会自动加载里面的文件 子文件夹的文件不会自动加载 -------------------------------------------------------------------------------- /application/views/index/index.phtml: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /application/views/error/error.phtml: -------------------------------------------------------------------------------- 1 | getMessage(); 3 | ?> 4 | -------------------------------------------------------------------------------- /application/library/TaskInterface.php: -------------------------------------------------------------------------------- 1 | set['database']); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /application/models/Order.php: -------------------------------------------------------------------------------- 1 | get(self::ORDER, 10); //contains an Array 10 users 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /application/models/Sample.php: -------------------------------------------------------------------------------- 1 | bootstrap()->run(); 17 | -------------------------------------------------------------------------------- /application/controllers/Error.php: -------------------------------------------------------------------------------- 1 | getMessage() : '程序出现异常'; 17 | return json_fail(500, $msg); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /application/plugins/Sample.php: -------------------------------------------------------------------------------- 1 | select('admin_user','*'); 17 | //return Response::success('success',$res); 18 | $name = get('name','','trim'); 19 | return Response::success('ok',compact('name')); 20 | 21 | } 22 | 23 | public function taskAction() 24 | { 25 | //异步任务 26 | $taskData = array( 27 | 'event' => 'email', 28 | 'data' => array( 29 | 'id' => 1, 30 | 'to' => 'aaaa' 31 | ) 32 | ); 33 | //1 php-fpm模式下调用异步任务 34 | //TaskFactory::createTask($taskData); 35 | //2 swoole调用异步任务 36 | $server = SWOOLE_SERVER; 37 | $server::$httpServer->task($taskData); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /application/plugins/Authentication.php: -------------------------------------------------------------------------------- 1 | 使用swoole_http_server + yaf 专注于网站api接口,同时能处理异步任务,如异步发送邮件,发送短信验证码等耗时相对长一点的任务 6 | 7 | > 本项目兼容php-fpm模式,不过在异步任务调用处要调整 8 | 9 | 参照 [LinkedDestiny](https://github.com/LinkedDestiny/swoole-yaf) 10 | 参照 [xuebingwang](https://github.com/xuebingwang/xbw-swoole-yaf) 11 | 12 | ##**使用说明** 13 | ``` 14 | //1.安装yaf,swoole扩展 15 | composer install 16 | composer dump-autoload -o 17 | 18 | 19 | //2.配置Nginx反向代理,将所有请求代理到swoole端口上,配置如下 20 | location / { 21 | proxy_set_header Connection "keep-alive"; 22 | proxy_set_header Host $http_host; 23 | proxy_http_version 1.1; 24 | proxy_pass http://127.0.0.1:9501; 25 | } 26 | 27 | //打开终端 28 | cd yafApi/public 29 | php http_server.php 30 | ``` 31 | 32 | ##**注意事项** 33 | 1. 若你的接口需要多版本控制,需要配置modules,或参考[xuebingwang](https://github.com/xuebingwang/xbw-swoole-yaf) 34 | 2. 调用task时候注意事项 35 | ``` 36 | //1.当使用如下代码时候,可以在controller中调用task 37 | $this->application->bootstrap(); 38 | //controller中代码 39 | HttpServer::$http->task(time()); 40 | 41 | 42 | //2.当入口文件使用如下代码时候 43 | $this->application->bootstrap()->run(); 44 | //在controller中调用则会报出如下错误 45 | PHP Fatal error: Call to a member function task() 46 | 47 | ``` 48 | ** 不懂C,现阶段专注于应用层,未涉及底层代码,望请大神指教** 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /application/library/TokenLibrary.php: -------------------------------------------------------------------------------- 1 | getMessage(); 46 | } 47 | } 48 | return 'empty token'; 49 | } 50 | } -------------------------------------------------------------------------------- /conf/application.ini: -------------------------------------------------------------------------------- 1 | [common] 2 | application.directory = APPLICATION_PATH "/application" 3 | ;统一将异常抛到Error控制器中 4 | application.dispatcher.catchException=1 5 | application.dispatcher.throwException=1 6 | 7 | [product : common] 8 | ;生产模式下 swoole配置 9 | swoole.host = 0.0.0.0 10 | swoole.port = 9501 11 | swoole.worker_num = 4 12 | swoole.task_worker_num = 4 13 | swoole.daemonize = false 14 | swoole.dispatch_mode = 3 15 | swoole.heartbeat_check_interval = 60 16 | swoole.heartbeat_idle_time = 600 17 | swoole.open_tcp_nodelay = true 18 | 19 | ;生产模式下 MSYQL配置 20 | mysql.host = 127.0.0.1 21 | mysql.user = root 22 | mysql.password = root 23 | mysql.port = 3306 24 | mysql.db = shop 25 | mysql.prefix = shop_ 26 | 27 | ;生产模式下 REDIS配置 28 | redis.host = 127.0.0.1 29 | redis.port = 6379 30 | redis.auth = 31 | redis.db = 1 32 | 33 | [develop : common] 34 | ;开发模式下 swoole配置 35 | swoole.host = 0.0.0.0 36 | swoole.port = 9501 37 | swoole.worker_num = 4 38 | swoole.task_worker_num = 4 39 | swoole.daemonize = false 40 | swoole.dispatch_mode = 3 41 | swoole.heartbeat_check_interval = 60 42 | swoole.heartbeat_idle_time = 600 43 | swoole.open_tcp_nodelay = true 44 | swoole.log_file = /tmp/swoole_http_server.log 45 | 46 | ;开发模式下 MSYQL配置 47 | mysql.host = 127.0.0.1 48 | mysql.user = root 49 | mysql.password = root 50 | mysql.port = 3306 51 | mysql.db = shop 52 | mysql.prefix = shop_ 53 | 54 | ;开发模式下 REDIS配置 55 | redis.host = 127.0.0.1 56 | redis.port = 6379 57 | redis.auth = 58 | redis.db = 1 59 | -------------------------------------------------------------------------------- /application/helper/functions.php: -------------------------------------------------------------------------------- 1 | ', print_r($arr), ''; 7 | } 8 | } 9 | 10 | 11 | /** 12 | * get获取参数 为了代码能兼容swwole模式和fpm模式 13 | * @param $name string get参数名字 14 | * @param $default 参数若不存在赋予一个默认值 15 | * @param $func 过滤函数 16 | */ 17 | if (!function_exists('get')) { 18 | function get($name = '', $default = '', $func = '') 19 | { 20 | $gets = common_response_values('get', $name, $default, $func); 21 | return $gets; 22 | } 23 | } 24 | 25 | if (!function_exists('post')) { 26 | function post($name = '', $default = '', $func = '') 27 | { 28 | $posts = common_response_values('post', $name, $default, $func); 29 | return $posts; 30 | } 31 | } 32 | 33 | /** 34 | *获取用户输入的公共函数 35 | */ 36 | if (!function_exists('common_response_values')) { 37 | function common_response_values($type = 'get', $name = '', $default = '', $func = '') 38 | { 39 | $requestObj = Yaf_Dispatcher::getInstance()->getRequest(); 40 | $server = defined('SWOOLE_SERVER') ? SWOOLE_SERVER : ''; 41 | switch (strtolower($type)) { 42 | case 'get': 43 | $values = defined('SWOOLE_SERVER') ? $server::$get : $requestObj->getQuery(); 44 | break; 45 | case 'post': 46 | $values = defined('SWOOLE_SERVER') ? $server::$post : $requestObj->getPost(); 47 | break; 48 | default: 49 | return false; 50 | break; 51 | } 52 | 53 | if (!empty($name)) { 54 | $value = array_key_exists($name, $values) ? $values[$name] : $default; 55 | return empty($func) ? $value : call_user_func($func, $value); 56 | } 57 | return $values; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /application/Bootstrap.php: -------------------------------------------------------------------------------- 1 | getConfig(); 20 | Yaf_Registry::set('config', $objConfig); 21 | } 22 | 23 | /** 24 | * 其他 如公共方法 25 | */ 26 | public function _initOthers(Yaf_Dispatcher $dispatcher) 27 | { 28 | 29 | $helper_path = APPLICATION_PATH.'/application/helper/'; 30 | Yaf_Loader::import($helper_path.'functions.php'); 31 | } 32 | 33 | /** 34 | * 加载插件 类似于中间件 35 | */ 36 | public function _initPlugin(Yaf_Dispatcher $dispatcher) 37 | { 38 | // $dispatcher->registerPlugin(new SafePlugin());//注册一个安全过滤插件 39 | //$dispatcher->registerPlugin(new AuthenticationPlugin()); 40 | } 41 | 42 | /** 43 | *初始化服务,如mysql,redis,monolog等 44 | */ 45 | public function _initServices(Yaf_Dispatcher $dispatcher) 46 | { 47 | $config = Yaf_Registry::get('config'); 48 | 49 | //mysql操作类可以使用medoo 50 | $database = new Medoo([ 51 | 'database_type' => 'mysql', 52 | 'database_name' => $config->mysql['db'], 53 | 'server' =>$config->mysql['host'], 54 | 'username' => $config->mysql['user'], 55 | 'password' => $config->mysql['password'], 56 | 'port' => $config->mysql['port'], 57 | 'prefix' => $config->mysql['prefix'], 58 | 'charset' => 'utf8', 59 | ]); 60 | Yaf_Registry::set('db',$database); 61 | //redis可以使用predis 62 | 63 | //记录日志可以使用monolog 64 | } 65 | 66 | 67 | /** 68 | * 路由协议 69 | */ 70 | public function _initRoute(Yaf_Dispatcher $dispatcher) 71 | { 72 | //在这里注册自己的路由协议,默认使用简单路由 73 | /* 74 | $router = Yaf_Dispatcher::getInstance()->getRouter(); 75 | $routeConfig = array( 76 | "item" => array( 77 | "type" => "regex", 78 | "match" => "#^/(software|game)/(.*).html$#", 79 | "route" => array('action' => 'item'), 80 | "map" => array( 1 => 'data_type', 2 => 'docid' ), 81 | ), 82 | //正则匹配 83 | "category" => array( 84 | "type" => "regex", 85 | "match" => "#^/(software|game|video)/(.*)/(list_(.*).html)?$#", 86 | "route" => array('action' => 'list' ), 87 | "map" => array( 1 => 'data_type', 2 => 'cid',4 => 'page_num' ), 88 | ), 89 | //使用动态结果 :a 表示action 90 | "name" => array( 91 | "type" => "rewrite", //Yaf_Route_Rewrite route 92 | "match" => "/user-list/:a/:id", //match only /user-list/开头的 93 | "route" => array( 94 | 'controller' => "user", //route to user controller, 95 | 'action' => ":a", //使用动态的action 96 | ), 97 | ), 98 | ); 99 | $test = new Yaf_Config_Simple($routeConfig); 100 | $router->addConfig(new Yaf_Config_Simple($routeConfig));*/ 101 | } 102 | 103 | public function _initView(Yaf_Dispatcher $dispatcher) 104 | { 105 | //关闭view输出 106 | $dispatcher->disableView(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /application/plugins/Safe.php: -------------------------------------------------------------------------------- 1 | "\\=\\+\\/v(?:8|9|\\+|\\/)|\\%0acontent\\-(?:id|location|type|transfer\\-encoding)", 13 | ); 14 | 15 | $args_arr = array( 16 | 'xss' => "[\\'\\\"\\;\\*\\<\\>].*\\bon[a-zA-Z]{3,15}[\\s\\r\\n\\v\\f]*\\=|\\b(?:expression)\\(|\\ "[^\\{\\s]{1}(\\s|\\b)+(?:select\\b|update\\b|insert(?:(\\/\\*.*?\\*\\/)|(\\s)|(\\+))+into\\b).+?(?:from\\b|set\\b)|[^\\{\\s]{1}(\\s|\\b)+(?:create|delete|drop|truncate|rename|desc)(?:(\\/\\*.*?\\*\\/)|(\\s)|(\\+))+(?:table\\b|from\\b|database\\b)|into(?:(\\/\\*.*?\\*\\/)|\\s|\\+)+(?:dump|out)file\\b|\\bsleep\\([\\s]*[\\d]+[\\s]*\\)|benchmark\\(([^\\,]*)\\,([^\\,]*)\\)|(?:declare|set|select)\\b.*@|union\\b.*(?:select|all)\\b|(?:select|update|insert|create|delete|drop|grant|truncate|rename|exec|desc|from|table|database|set|where)\\b.*(charset|ascii|bin|char|uncompress|concat|concat_ws|conv|export_set|hex|instr|left|load_file|locate|mid|sub|substring|oct|reverse|right|unhex)\\(|(?:master\\.\\.sysdatabases|msysaccessobjects|msysqueries|sysmodules|mysql\\.db|sys\\.database_name|information_schema\\.|sysobjects|sp_makewebtask|xp_cmdshell|sp_oamethod|sp_addextendedproc|sp_oacreate|xp_regread|sys\\.dbms_export_extension)", 18 | 'other' => "\\.\\.[\\\\\\/].*\\%00([^0-9a-fA-F]|$)|%00[\\'\\\"\\.]" 19 | ); 20 | 21 | $referer = empty($_SERVER['HTTP_REFERER']) ? array() : array($_SERVER['HTTP_REFERER']); 22 | $query_string = empty($_SERVER["QUERY_STRING"]) ? array() : array($_SERVER["QUERY_STRING"]); 23 | 24 | self::check_data($query_string, $url_arr); 25 | isset($_SERVER['SHELL']) ? self::swooleMode($referer,$args_arr) : self::normalMode($referer,$args_arr); 26 | } 27 | 28 | /* 29 | *swoole模式下获取请求参数 30 | */ 31 | private function swooleMode($referer,$args_arr) 32 | { 33 | $_GET = HttpServer::$get; 34 | $_POST = HttpServer::$post; 35 | $_COOKIE = HttpServer::$cookies; 36 | self::check_data(array($_GET, $_POST, $_COOKIE, $referer), $args_arr); 37 | } 38 | 39 | /* 40 | *正常模式下获取请求参数 41 | */ 42 | private function normalMode($referer,$args_arr) 43 | { 44 | self::check_data(array($_GET, $_POST, $_COOKIE, $referer), $args_arr); 45 | } 46 | 47 | private function check_data($arr, $v) 48 | { 49 | if (!empty($arr)) { 50 | foreach ($arr as $key => $value) { 51 | is_array($key) ? self::check_data($key, $v) : self::check($key, $v); 52 | is_array($value) ? self::check_data($value, $v) : self::check($value, $v); 53 | } 54 | } 55 | } 56 | 57 | private function check($str, $v) 58 | { 59 | foreach ($v as $key => $value) { 60 | if (preg_match("/" . $value . "/is", $str) == 1 || preg_match("/" . $value . "/is", urlencode($str)) == 1) { 61 | // self::W_log(); 62 | throw new Yaf_Exception('attack-请提交正确参数'); 63 | } 64 | $v[$key] = htmlspecialchars($value); 65 | } 66 | } 67 | 68 | private function W_log() 69 | { 70 | 71 | $path = LogLibrary::logPath('attack'); 72 | $data = [ 73 | 'IP' => $_SERVER['REMOTE_ADDR'], 74 | 'URL' => $_SERVER['REQUEST_URI'] 75 | ]; 76 | LogLibrary::writeLog($path, '受到攻击', $data); 77 | } 78 | } -------------------------------------------------------------------------------- /public/http_server.php: -------------------------------------------------------------------------------- 1 | toArray(); 27 | $config = $configArr[$this->environment]['swoole']; 28 | extract($config); 29 | 30 | self::$httpServer = new swoole_http_server($host, $port); 31 | self::$httpServer->set(array( 32 | 'worker_num' => $worker_num, //worker进程数 33 | 'task_worker_num' => $task_worker_num, //task_worker进程数 34 | 'daemonize' => $daemonize, 35 | 'dispatch_mode' => $dispatch_mode, 36 | 'open_tcp_nodelay' => $open_tcp_nodelay, 37 | 'open_tcp_keepalive' => '', 38 | 'tcp_defer_accept' => '', 39 | //'log_file' => ROOT_PATH . '/logs/swoole_http_server.log', 40 | )); 41 | self::$httpServer->on('WorkerStart', array($this, 'onWorkerStart')); 42 | self::$httpServer->on('request', array($this, 'onRequest')); 43 | self::$httpServer->on('task', array($this, 'onTask')); 44 | self::$httpServer->on('finish', array($this, 'onFinish')); 45 | self::$httpServer->start(); 46 | } 47 | 48 | /** 49 | * woker进程启动 50 | */ 51 | public function onWorkerStart($serv, $worker_id) 52 | { 53 | define('SWOOLE_SERVER', __CLASS__); 54 | require_once dirname(__FILE__) . '/../vendor/autoload.php'; 55 | 56 | define('APPLICATION_PATH', dirname(__FILE__).'/..'); 57 | $environment = $this->environment; 58 | $this->application = new Yaf_Application(APPLICATION_PATH . "/conf/application.ini", $environment); 59 | $this->application->bootstrap(); 60 | 61 | if ($worker_id >= $serv->setting['worker_num']) { 62 | cli_set_process_title("swoolehttp:task_worker"); 63 | } else { 64 | cli_set_process_title("swoolehttp:worker"); 65 | } 66 | } 67 | 68 | /** 69 | * 处理http请求 70 | * @param $request 71 | * @param $response 72 | * @return mixed 73 | */ 74 | public function onRequest($request, $response) 75 | { 76 | //请求过滤,会请求2次 77 | if (in_array('/favicon.ico', [$request->server['path_info'], $request->server['request_uri']])) { 78 | return $response->end(); 79 | } 80 | 81 | self::$header = isset($request->header) ? $request->header : []; 82 | self::$get = isset($request->get) ? $request->get : []; 83 | self::$post = isset($request->post) ? $request->post : []; 84 | self::$cookies = isset($request->cookies) ? $request->cookies : []; 85 | self::$server = isset($request->server) ? $request->server : []; 86 | self::$rawContent = $request->rawContent(); 87 | 88 | ob_start(); 89 | try { 90 | $yafRequest = new Yaf_Request_Http(self::$server['request_uri']); 91 | $this->application->getDispatcher()->dispatch($yafRequest); 92 | } catch (Yaf_Exception $e) { 93 | var_dump($e->getMessage()); 94 | } 95 | $result = ob_get_contents(); 96 | ob_end_clean(); 97 | $response->header('content-type', 'application/json;charset=utf8',true); 98 | $response->end($result); 99 | } 100 | 101 | /** 102 | * 异步任务 103 | */ 104 | public function onTask($serv, $taskId, $fromId, array $taskdata) 105 | { 106 | echo "新的异步任务[来自进程 {$fromId},当前进程 {$taskId}],data:" . json_encode($taskdata) . PHP_EOL; 107 | //在异步任务内需要调用swoole_server的话,需在createTask中将$serv参数传递过去 108 | TaskFactory::createTask($taskdata); 109 | } 110 | 111 | /** 112 | * 异步任务后续处理 113 | */ 114 | public function onFinish($serv, $taskId, $data) 115 | { 116 | } 117 | 118 | public static function getInstance() 119 | { 120 | if (!self::$instance) { 121 | self::$instance = new self(); 122 | } 123 | return self::$instance; 124 | } 125 | } 126 | 127 | HttpServer::getInstance(); --------------------------------------------------------------------------------