├── apps ├── test_http │ ├── config │ │ ├── server_conf.php │ │ └── worker_conf.php │ ├── controller │ │ ├── base_controller.php │ │ └── index_controller.php │ └── index.php └── test_tcp │ ├── config │ ├── server_conf.php │ └── worker_conf.php │ ├── controller │ ├── base_controller.php │ └── index_controller.php │ ├── index.php │ └── protocol │ └── voip_protocol.php ├── bin ├── asf.ini └── asf.php ├── lib ├── autoload.php ├── config.php ├── controller.php ├── fast-route │ ├── composer.json │ ├── composer.lock │ └── vendor │ │ ├── autoload.php │ │ ├── composer │ │ ├── ClassLoader.php │ │ ├── LICENSE │ │ ├── autoload_classmap.php │ │ ├── autoload_files.php │ │ ├── autoload_namespaces.php │ │ ├── autoload_psr4.php │ │ ├── autoload_real.php │ │ └── installed.json │ │ └── nikic │ │ └── fast-route │ │ ├── LICENSE │ │ ├── README.md │ │ ├── composer.json │ │ ├── phpunit.xml │ │ ├── src │ │ ├── BadRouteException.php │ │ ├── DataGenerator.php │ │ ├── DataGenerator │ │ │ ├── CharCountBased.php │ │ │ ├── GroupCountBased.php │ │ │ ├── GroupPosBased.php │ │ │ ├── MarkBased.php │ │ │ └── RegexBasedAbstract.php │ │ ├── Dispatcher.php │ │ ├── Dispatcher │ │ │ ├── CharCountBased.php │ │ │ ├── GroupCountBased.php │ │ │ ├── GroupPosBased.php │ │ │ ├── MarkBased.php │ │ │ └── RegexBasedAbstract.php │ │ ├── Route.php │ │ ├── RouteCollector.php │ │ ├── RouteParser.php │ │ ├── RouteParser │ │ │ └── Std.php │ │ ├── bootstrap.php │ │ └── functions.php │ │ └── test │ │ ├── Dispatcher │ │ ├── CharCountBasedTest.php │ │ ├── DispatcherTest.php │ │ ├── GroupCountBasedTest.php │ │ ├── GroupPosBasedTest.php │ │ └── MarkBasedTest.php │ │ ├── RouteParser │ │ └── StdTest.php │ │ └── bootstrap.php ├── log.php ├── mysql.php ├── protocol.php ├── route.php └── swoole.php └── readme.md /apps/test_http/config/server_conf.php: -------------------------------------------------------------------------------- 1 | 'test_http', //server名称 6 | 'protocol' => 'http', //没有该选项则默认为http server 7 | 'log_level' => NOTICE, //跟踪级别TRACE,DEBUG,INFO,NOTICE,WARNING,ERROR 8 | 'is_sington' => true, //是否单实例 9 | 'listen' => ['0.0.0.0:9501', '172.16.18.116:9502'], //listen监听端口 10 | 'worker_num' => 1, //工作进程数 11 | 'daemonize' => false, //是否以守护进程方式运行 12 | 'log_file' => '/home/jfy/testprog/asf/apps/test_http/index.log', //log文件 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /apps/test_http/config/worker_conf.php: -------------------------------------------------------------------------------- 1 | DEBUG, 6 | 'mysql' => array( 7 | 'socket' => '/tmp/mysql.sock', 8 | 'host' => 'localhost', 9 | 'port' => 3306, 10 | 'user' => 'root', 11 | 'password' => 'cpyf', 12 | 'database' => 'test', 13 | 'charset' => 'utf8', 14 | ), 15 | 'route' => [ 16 | //_handler.* 控制器为底层通用控制器,会根据具体的controller/action进行再次分发 17 | 18 | // ['PUT', '/user/{number}/{id:\d+}', 'index.index'], 19 | // ['GET', '/{controller}/{number}/{id:\d+}', '_handler.controller_param'], 20 | // ['GET', '/{controller}/{number}', '_handler.controller_param'], 21 | // ['POST', '/{controller}/{number}/{id:\d+}', '_handler.controller_param'], 22 | // ['DELETE', '/{controller}/{number}/{id:\d+}', '_handler.controller_param'], 23 | 24 | /*下面这三条规则,由底层填加到算定义规则的后面 25 | ['POST', '/{controller}/{action}[/]', '_handler.controller_action'], 26 | ['POST', '/{controller}[/]', '_handler.controller'], 27 | [['GET','POST'], '/{controller}/{param:.+}', '_handler.controller_param'], 28 | */ 29 | ], 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /apps/test_http/controller/base_controller.php: -------------------------------------------------------------------------------- 1 | content = $this->request->rawContent(); 6 | } 7 | 8 | public function _deinit(){ 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/test_http/controller/index_controller.php: -------------------------------------------------------------------------------- 1 | content)); 6 | log::prn_log(DEBUG, json_encode($this->param)); 7 | 8 | $db = $this->mysql; 9 | 10 | $result = $db->gearman_queue->insert([ 11 | 'unique_key' => 'd847233c-1ef2-11e5-9130-2c44fd7aee72', 12 | 'function_name' => 'test', 13 | 'priority' => 1, 14 | 'data' => 'test', 15 | 'when_to_run' => 0, 16 | ]); 17 | if ( $result === false ) return 'error'; 18 | 19 | $result = $db->select_one("select * from gearman_queue where unique_key='d847233c-1ef2-11e5-9130-2c44fd7aee72'"); 20 | if ( $result === false ) return 'error'; 21 | var_dump($result); 22 | 23 | $result = $db->gearman_queue->update([ 24 | 'function_name' => 'testtest', 25 | 'priority' => 100, 26 | 'data' => 'testtesttesttest', 27 | 'when_to_run' => 100, 28 | ],[ 29 | 'unique_key' => 'd847233c-1ef2-11e5-9130-2c44fd7aee72', 30 | ]); 31 | if ( $result === false ) return 'error'; 32 | var_dump($db->select_one("select * from gearman_queue where unique_key='d847233c-1ef2-11e5-9130-2c44fd7aee72'")); 33 | 34 | $result = $db->gearman_queue->delete([ 35 | 'unique_key' => 'd847233c-1ef2-11e5-9130-2c44fd7aee72', 36 | ]); 37 | if ( $result === false ) return 'error'; 38 | 39 | $result = $db->select_more("select * from gearman_queue limit 3"); 40 | if ( $result === false ) return 'error'; 41 | var_dump($result); 42 | 43 | return 'ok'; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /apps/test_http/index.php: -------------------------------------------------------------------------------- 1 | start(); 14 | -------------------------------------------------------------------------------- /apps/test_tcp/config/server_conf.php: -------------------------------------------------------------------------------- 1 | 'test_tcp', 6 | 'protocol' => 'voip', 7 | 'log_level' => DEBUG, 8 | 'is_sington' => true, 9 | 'listen' => ['0.0.0.0:9511', '172.16.18.116:9512'], 10 | 'worker_num' => 1, 11 | 'daemonize' => 0, 12 | 'log_file' => '/home/jfy/testprog/asf/apps/test_tcp/index.log', 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /apps/test_tcp/config/worker_conf.php: -------------------------------------------------------------------------------- 1 | DEBUG, 6 | 'mysql' => array( 7 | 'socket' => '/tmp/mysql.sock', 8 | 'host' => 'localhost', 9 | 'port' => 3306, 10 | 'user' => 'root', 11 | 'password' => 'cpyf', 12 | 'database' => 'test', 13 | 'charset' => 'utf8', 14 | ) 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /apps/test_tcp/controller/base_controller.php: -------------------------------------------------------------------------------- 1 | request; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/test_tcp/index.php: -------------------------------------------------------------------------------- 1 | start(); 14 | -------------------------------------------------------------------------------- /apps/test_tcp/protocol/voip_protocol.php: -------------------------------------------------------------------------------- 1 | mysql; 14 | $obj = new index_controller($serv, $data); 15 | return $obj->index(); 16 | } 17 | 18 | public static function encode($serv, $fd, $data){ 19 | return $data; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /bin/asf.ini: -------------------------------------------------------------------------------- 1 | [servers] 2 | test_http = /home/jfy/testprog/asf/apps/test_http/index.php 3 | test_tcp = /home/jfy/testprog/asf/apps/test_tcp/index.php 4 | -------------------------------------------------------------------------------- /bin/asf.php: -------------------------------------------------------------------------------- 1 | 0 && posix_kill($pid, 0)){ 62 | exit("the server is already started!\n"); 63 | } 64 | } 65 | start_and_wait($param, 15); 66 | exit; 67 | break; 68 | case 'stop': 69 | stop_and_wait($param, 5); 70 | exit; 71 | break; 72 | case 'restart': 73 | stop_and_wait($param, 5); 74 | start_and_wait($param,15); 75 | exit; 76 | break; 77 | case 'reload': 78 | $pid = @file_get_contents($pid_file); 79 | if(empty($pid)) 80 | { 81 | exit("Server is not running!\n"); 82 | } 83 | if (!posix_kill($pid, 0)){ 84 | exit("Server is not running!\n"); 85 | } 86 | posix_kill($pid, SIGUSR1); 87 | echo "Server reload ok!\n"; 88 | break; 89 | case 'status': 90 | $pid = @file_get_contents($pid_file); 91 | if(empty($pid)) 92 | { 93 | exit("Server is not running!\n"); 94 | } 95 | if (!posix_kill($pid, 0)){ 96 | exit("Server is not running!\n"); 97 | } 98 | exec("ps -ef | grep {$param['server_name']} | grep -v grep | grep -v asf", $ret); 99 | foreach($ret as $line) echo $line."\n"; 100 | break; 101 | default: 102 | echo "Usage: asf ".PHP_EOL; 103 | exit; 104 | 105 | } 106 | 107 | function list_server($param){ 108 | foreach($param['servers'] as $server_name => $server_file) { 109 | $pid_file = swoole::$info_dir . "swoole_{$server_name}.pid"; 110 | $pid = @file_get_contents($pid_file); 111 | if(empty($pid)) 112 | { 113 | echo sprintf('%-16s ', "[$server_name]")."Server is not running!\n"; 114 | continue; 115 | } 116 | if (!posix_kill($pid, 0)){ 117 | echo sprintf('%-16s ', "[$server_name]")."Server is not running!\n"; 118 | continue; 119 | } 120 | echo sprintf('%-16s ', "[$server_name]")."Server is running!\n"; 121 | } 122 | } 123 | 124 | function app_init($param) 125 | { 126 | if ( file_exists($param['server_path']) ) { 127 | echo "new app [{$param['server_path']}] is exists\n"; 128 | exit; 129 | } 130 | 131 | echo exec('/bin/cp -r ' . $param['server_examples_test_path'] .' '. $param['server_path']); 132 | 133 | $config_file = "{$param['server_path']}//config//server_conf.php"; 134 | $content = file_get_contents($config_file); 135 | $content = str_replace("'server_name' => 'test'","'server_name' => '{$param['server_name']}'",$content); 136 | file_put_contents($config_file,$content); 137 | 138 | 139 | echo "app [{$param['server_name']}] init ok\n"; 140 | } 141 | 142 | function start_and_wait($param, $wait_time = 5) 143 | { 144 | global $param; 145 | $pid_file = $param['pid_file']; 146 | $server_file = $param['server_file']; 147 | $server_name = $param['server_name']; 148 | 149 | echo exec("/usr/bin/php $server_file"); 150 | 151 | $start_time = time(); 152 | $succ=false; 153 | while(true) 154 | { 155 | if (file_exists($pid_file)){ 156 | $pid = file_get_contents($pid_file); 157 | $pid = intval($pid); 158 | if ($pid > 0 && posix_kill($pid, 0)){ 159 | exec("ps -ef | grep $server_name | grep -v grep", $ret); 160 | if ( count($ret) > 2 ) { 161 | $succ=true; 162 | break; 163 | } 164 | } 165 | } 166 | clearstatcache(); 167 | usleep(100); 168 | if(time()-$start_time >= $wait_time) 169 | { 170 | usleep(500000); 171 | break; 172 | } 173 | } 174 | if ( $succ ) 175 | echo "Server start ok!\n"; 176 | else 177 | echo "Server start error, please view logfile!\n"; 178 | 179 | return; 180 | } 181 | 182 | function stop_and_wait($param, $wait_time = 5) 183 | { 184 | global $param; 185 | $pid_file = $param['pid_file']; 186 | $server_file = $param['server_file']; 187 | $server_name = $param['server_name']; 188 | 189 | $pid = @file_get_contents($pid_file); 190 | if(empty($pid)) 191 | { 192 | exit("Server is not running!\n"); 193 | } 194 | if (!posix_kill($pid, 0)){ 195 | exit("Server is not running!\n"); 196 | } 197 | posix_kill($pid, SIGTERM); 198 | 199 | $start_time = time(); 200 | while(is_file($pid_file)) 201 | { 202 | clearstatcache(); 203 | usleep(1000); 204 | if(time()-$start_time >= $wait_time) 205 | { 206 | posix_kill($pid, SIGTERM); 207 | posix_kill($pid, SIGTERM); 208 | unlink($pid_file); 209 | usleep(500000); 210 | echo aaa; 211 | break; 212 | } 213 | } 214 | 215 | echo "Server stop ok!\n"; 216 | 217 | return; 218 | } 219 | 220 | function print_info(){ 221 | echo PHP_EOL; 222 | echo "welcome to use App-Server-Framework:".PHP_EOL.PHP_EOL; 223 | echo " php asf.php servername start|stop|reload|restart|status|init".PHP_EOL; 224 | echo " php asf.php list".PHP_EOL.PHP_EOL; 225 | exit; 226 | } 227 | -------------------------------------------------------------------------------- /lib/autoload.php: -------------------------------------------------------------------------------- 1 | server = $server; 22 | $this->mysql = $server->mysql; 23 | $this->request = $request; 24 | $this->param = $param; 25 | 26 | if (method_exists($this, '_init')) $this->_init(); 27 | } 28 | 29 | public function __destruct() { 30 | if (method_exists($this, '_deinit')) $this->_deinit (); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/fast-route/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "nikic/fast-route": "^0.6.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /lib/fast-route/composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "1a7e203f91cff86c3ca2ceb33689b3fa", 8 | "content-hash": "80ae0823c4b602cdf73c8d0491912464", 9 | "packages": [ 10 | { 11 | "name": "nikic/fast-route", 12 | "version": "v0.6.0", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/nikic/FastRoute.git", 16 | "reference": "31fa86924556b80735f98b294a7ffdfb26789f22" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/nikic/FastRoute/zipball/31fa86924556b80735f98b294a7ffdfb26789f22", 21 | "reference": "31fa86924556b80735f98b294a7ffdfb26789f22", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": ">=5.4.0" 26 | }, 27 | "type": "library", 28 | "autoload": { 29 | "psr-4": { 30 | "FastRoute\\": "src/" 31 | }, 32 | "files": [ 33 | "src/functions.php" 34 | ] 35 | }, 36 | "notification-url": "https://packagist.org/downloads/", 37 | "license": [ 38 | "BSD-3-Clause" 39 | ], 40 | "authors": [ 41 | { 42 | "name": "Nikita Popov", 43 | "email": "nikic@php.net" 44 | } 45 | ], 46 | "description": "Fast request router for PHP", 47 | "keywords": [ 48 | "router", 49 | "routing" 50 | ], 51 | "time": "2015-06-18 19:15:47" 52 | } 53 | ], 54 | "packages-dev": [], 55 | "aliases": [], 56 | "minimum-stability": "stable", 57 | "stability-flags": [], 58 | "prefer-stable": false, 59 | "prefer-lowest": false, 60 | "platform": [], 61 | "platform-dev": [] 62 | } 63 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/autoload.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0 class loader 17 | * 18 | * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md 19 | * 20 | * $loader = new \Composer\Autoload\ClassLoader(); 21 | * 22 | * // register classes with namespaces 23 | * $loader->add('Symfony\Component', __DIR__.'/component'); 24 | * $loader->add('Symfony', __DIR__.'/framework'); 25 | * 26 | * // activate the autoloader 27 | * $loader->register(); 28 | * 29 | * // to enable searching the include path (eg. for PEAR packages) 30 | * $loader->setUseIncludePath(true); 31 | * 32 | * In this example, if you try to use a class in the Symfony\Component 33 | * namespace or one of its children (Symfony\Component\Console for instance), 34 | * the autoloader will first look for the class under the component/ 35 | * directory, and it will then fallback to the framework/ directory if not 36 | * found before giving up. 37 | * 38 | * This class is loosely based on the Symfony UniversalClassLoader. 39 | * 40 | * @author Fabien Potencier 41 | * @author Jordi Boggiano 42 | */ 43 | class ClassLoader 44 | { 45 | // PSR-4 46 | private $prefixLengthsPsr4 = array(); 47 | private $prefixDirsPsr4 = array(); 48 | private $fallbackDirsPsr4 = array(); 49 | 50 | // PSR-0 51 | private $prefixesPsr0 = array(); 52 | private $fallbackDirsPsr0 = array(); 53 | 54 | private $useIncludePath = false; 55 | private $classMap = array(); 56 | 57 | private $classMapAuthoritative = false; 58 | 59 | public function getPrefixes() 60 | { 61 | if (!empty($this->prefixesPsr0)) { 62 | return call_user_func_array('array_merge', $this->prefixesPsr0); 63 | } 64 | 65 | return array(); 66 | } 67 | 68 | public function getPrefixesPsr4() 69 | { 70 | return $this->prefixDirsPsr4; 71 | } 72 | 73 | public function getFallbackDirs() 74 | { 75 | return $this->fallbackDirsPsr0; 76 | } 77 | 78 | public function getFallbackDirsPsr4() 79 | { 80 | return $this->fallbackDirsPsr4; 81 | } 82 | 83 | public function getClassMap() 84 | { 85 | return $this->classMap; 86 | } 87 | 88 | /** 89 | * @param array $classMap Class to filename map 90 | */ 91 | public function addClassMap(array $classMap) 92 | { 93 | if ($this->classMap) { 94 | $this->classMap = array_merge($this->classMap, $classMap); 95 | } else { 96 | $this->classMap = $classMap; 97 | } 98 | } 99 | 100 | /** 101 | * Registers a set of PSR-0 directories for a given prefix, either 102 | * appending or prepending to the ones previously set for this prefix. 103 | * 104 | * @param string $prefix The prefix 105 | * @param array|string $paths The PSR-0 root directories 106 | * @param bool $prepend Whether to prepend the directories 107 | */ 108 | public function add($prefix, $paths, $prepend = false) 109 | { 110 | if (!$prefix) { 111 | if ($prepend) { 112 | $this->fallbackDirsPsr0 = array_merge( 113 | (array) $paths, 114 | $this->fallbackDirsPsr0 115 | ); 116 | } else { 117 | $this->fallbackDirsPsr0 = array_merge( 118 | $this->fallbackDirsPsr0, 119 | (array) $paths 120 | ); 121 | } 122 | 123 | return; 124 | } 125 | 126 | $first = $prefix[0]; 127 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 128 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 129 | 130 | return; 131 | } 132 | if ($prepend) { 133 | $this->prefixesPsr0[$first][$prefix] = array_merge( 134 | (array) $paths, 135 | $this->prefixesPsr0[$first][$prefix] 136 | ); 137 | } else { 138 | $this->prefixesPsr0[$first][$prefix] = array_merge( 139 | $this->prefixesPsr0[$first][$prefix], 140 | (array) $paths 141 | ); 142 | } 143 | } 144 | 145 | /** 146 | * Registers a set of PSR-4 directories for a given namespace, either 147 | * appending or prepending to the ones previously set for this namespace. 148 | * 149 | * @param string $prefix The prefix/namespace, with trailing '\\' 150 | * @param array|string $paths The PSR-0 base directories 151 | * @param bool $prepend Whether to prepend the directories 152 | * 153 | * @throws \InvalidArgumentException 154 | */ 155 | public function addPsr4($prefix, $paths, $prepend = false) 156 | { 157 | if (!$prefix) { 158 | // Register directories for the root namespace. 159 | if ($prepend) { 160 | $this->fallbackDirsPsr4 = array_merge( 161 | (array) $paths, 162 | $this->fallbackDirsPsr4 163 | ); 164 | } else { 165 | $this->fallbackDirsPsr4 = array_merge( 166 | $this->fallbackDirsPsr4, 167 | (array) $paths 168 | ); 169 | } 170 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 171 | // Register directories for a new namespace. 172 | $length = strlen($prefix); 173 | if ('\\' !== $prefix[$length - 1]) { 174 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 175 | } 176 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 177 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 178 | } elseif ($prepend) { 179 | // Prepend directories for an already registered namespace. 180 | $this->prefixDirsPsr4[$prefix] = array_merge( 181 | (array) $paths, 182 | $this->prefixDirsPsr4[$prefix] 183 | ); 184 | } else { 185 | // Append directories for an already registered namespace. 186 | $this->prefixDirsPsr4[$prefix] = array_merge( 187 | $this->prefixDirsPsr4[$prefix], 188 | (array) $paths 189 | ); 190 | } 191 | } 192 | 193 | /** 194 | * Registers a set of PSR-0 directories for a given prefix, 195 | * replacing any others previously set for this prefix. 196 | * 197 | * @param string $prefix The prefix 198 | * @param array|string $paths The PSR-0 base directories 199 | */ 200 | public function set($prefix, $paths) 201 | { 202 | if (!$prefix) { 203 | $this->fallbackDirsPsr0 = (array) $paths; 204 | } else { 205 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 206 | } 207 | } 208 | 209 | /** 210 | * Registers a set of PSR-4 directories for a given namespace, 211 | * replacing any others previously set for this namespace. 212 | * 213 | * @param string $prefix The prefix/namespace, with trailing '\\' 214 | * @param array|string $paths The PSR-4 base directories 215 | * 216 | * @throws \InvalidArgumentException 217 | */ 218 | public function setPsr4($prefix, $paths) 219 | { 220 | if (!$prefix) { 221 | $this->fallbackDirsPsr4 = (array) $paths; 222 | } else { 223 | $length = strlen($prefix); 224 | if ('\\' !== $prefix[$length - 1]) { 225 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 226 | } 227 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 228 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 229 | } 230 | } 231 | 232 | /** 233 | * Turns on searching the include path for class files. 234 | * 235 | * @param bool $useIncludePath 236 | */ 237 | public function setUseIncludePath($useIncludePath) 238 | { 239 | $this->useIncludePath = $useIncludePath; 240 | } 241 | 242 | /** 243 | * Can be used to check if the autoloader uses the include path to check 244 | * for classes. 245 | * 246 | * @return bool 247 | */ 248 | public function getUseIncludePath() 249 | { 250 | return $this->useIncludePath; 251 | } 252 | 253 | /** 254 | * Turns off searching the prefix and fallback directories for classes 255 | * that have not been registered with the class map. 256 | * 257 | * @param bool $classMapAuthoritative 258 | */ 259 | public function setClassMapAuthoritative($classMapAuthoritative) 260 | { 261 | $this->classMapAuthoritative = $classMapAuthoritative; 262 | } 263 | 264 | /** 265 | * Should class lookup fail if not found in the current class map? 266 | * 267 | * @return bool 268 | */ 269 | public function isClassMapAuthoritative() 270 | { 271 | return $this->classMapAuthoritative; 272 | } 273 | 274 | /** 275 | * Registers this instance as an autoloader. 276 | * 277 | * @param bool $prepend Whether to prepend the autoloader or not 278 | */ 279 | public function register($prepend = false) 280 | { 281 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 282 | } 283 | 284 | /** 285 | * Unregisters this instance as an autoloader. 286 | */ 287 | public function unregister() 288 | { 289 | spl_autoload_unregister(array($this, 'loadClass')); 290 | } 291 | 292 | /** 293 | * Loads the given class or interface. 294 | * 295 | * @param string $class The name of the class 296 | * @return bool|null True if loaded, null otherwise 297 | */ 298 | public function loadClass($class) 299 | { 300 | if ($file = $this->findFile($class)) { 301 | includeFile($file); 302 | 303 | return true; 304 | } 305 | } 306 | 307 | /** 308 | * Finds the path to the file where the class is defined. 309 | * 310 | * @param string $class The name of the class 311 | * 312 | * @return string|false The path if found, false otherwise 313 | */ 314 | public function findFile($class) 315 | { 316 | // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 317 | if ('\\' == $class[0]) { 318 | $class = substr($class, 1); 319 | } 320 | 321 | // class map lookup 322 | if (isset($this->classMap[$class])) { 323 | return $this->classMap[$class]; 324 | } 325 | if ($this->classMapAuthoritative) { 326 | return false; 327 | } 328 | 329 | $file = $this->findFileWithExtension($class, '.php'); 330 | 331 | // Search for Hack files if we are running on HHVM 332 | if ($file === null && defined('HHVM_VERSION')) { 333 | $file = $this->findFileWithExtension($class, '.hh'); 334 | } 335 | 336 | if ($file === null) { 337 | // Remember that this class does not exist. 338 | return $this->classMap[$class] = false; 339 | } 340 | 341 | return $file; 342 | } 343 | 344 | private function findFileWithExtension($class, $ext) 345 | { 346 | // PSR-4 lookup 347 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 348 | 349 | $first = $class[0]; 350 | if (isset($this->prefixLengthsPsr4[$first])) { 351 | foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { 352 | if (0 === strpos($class, $prefix)) { 353 | foreach ($this->prefixDirsPsr4[$prefix] as $dir) { 354 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { 355 | return $file; 356 | } 357 | } 358 | } 359 | } 360 | } 361 | 362 | // PSR-4 fallback dirs 363 | foreach ($this->fallbackDirsPsr4 as $dir) { 364 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 365 | return $file; 366 | } 367 | } 368 | 369 | // PSR-0 lookup 370 | if (false !== $pos = strrpos($class, '\\')) { 371 | // namespaced class name 372 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 373 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 374 | } else { 375 | // PEAR-like class name 376 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 377 | } 378 | 379 | if (isset($this->prefixesPsr0[$first])) { 380 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 381 | if (0 === strpos($class, $prefix)) { 382 | foreach ($dirs as $dir) { 383 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 384 | return $file; 385 | } 386 | } 387 | } 388 | } 389 | } 390 | 391 | // PSR-0 fallback dirs 392 | foreach ($this->fallbackDirsPsr0 as $dir) { 393 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 394 | return $file; 395 | } 396 | } 397 | 398 | // PSR-0 include paths. 399 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 400 | return $file; 401 | } 402 | } 403 | } 404 | 405 | /** 406 | * Scope isolated include. 407 | * 408 | * Prevents access to $this/self from included files. 409 | */ 410 | function includeFile($file) 411 | { 412 | include $file; 413 | } 414 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/composer/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2015 Nils Adermann, Jordi Boggiano 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/composer/autoload_classmap.php: -------------------------------------------------------------------------------- 1 | array($vendorDir . '/nikic/fast-route/src'), 10 | ); 11 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | $path) { 28 | $loader->set($namespace, $path); 29 | } 30 | 31 | $map = require __DIR__ . '/autoload_psr4.php'; 32 | foreach ($map as $namespace => $path) { 33 | $loader->setPsr4($namespace, $path); 34 | } 35 | 36 | $classMap = require __DIR__ . '/autoload_classmap.php'; 37 | if ($classMap) { 38 | $loader->addClassMap($classMap); 39 | } 40 | 41 | $loader->register(true); 42 | 43 | $includeFiles = require __DIR__ . '/autoload_files.php'; 44 | foreach ($includeFiles as $file) { 45 | composerRequire2c5420a4d6479a4d6d823613cf5b33fe($file); 46 | } 47 | 48 | return $loader; 49 | } 50 | } 51 | 52 | function composerRequire2c5420a4d6479a4d6d823613cf5b33fe($file) 53 | { 54 | require $file; 55 | } 56 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "nikic/fast-route", 4 | "version": "v0.6.0", 5 | "version_normalized": "0.6.0.0", 6 | "source": { 7 | "type": "git", 8 | "url": "https://github.com/nikic/FastRoute.git", 9 | "reference": "31fa86924556b80735f98b294a7ffdfb26789f22" 10 | }, 11 | "dist": { 12 | "type": "zip", 13 | "url": "https://api.github.com/repos/nikic/FastRoute/zipball/31fa86924556b80735f98b294a7ffdfb26789f22", 14 | "reference": "31fa86924556b80735f98b294a7ffdfb26789f22", 15 | "shasum": "" 16 | }, 17 | "require": { 18 | "php": ">=5.4.0" 19 | }, 20 | "time": "2015-06-18 19:15:47", 21 | "type": "library", 22 | "installation-source": "dist", 23 | "autoload": { 24 | "psr-4": { 25 | "FastRoute\\": "src/" 26 | }, 27 | "files": [ 28 | "src/functions.php" 29 | ] 30 | }, 31 | "notification-url": "https://packagist.org/downloads/", 32 | "license": [ 33 | "BSD-3-Clause" 34 | ], 35 | "authors": [ 36 | { 37 | "name": "Nikita Popov", 38 | "email": "nikic@php.net" 39 | } 40 | ], 41 | "description": "Fast request router for PHP", 42 | "keywords": [ 43 | "router", 44 | "routing" 45 | ] 46 | } 47 | ] 48 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 by Nikita Popov. 2 | 3 | Some rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/README.md: -------------------------------------------------------------------------------- 1 | FastRoute - Fast request router for PHP 2 | ======================================= 3 | 4 | This library provides a fast implementation of a regular expression based router. [Blog post explaining how the 5 | implementation works and why it is fast.][blog_post] 6 | 7 | Usage 8 | ----- 9 | 10 | Here's a basic usage example: 11 | 12 | ```php 13 | addRoute('GET', '/user/{id:\d+}', 'handler1'); 19 | $r->addRoute('GET', '/user/{id:\d+}/{name}', 'handler2'); 20 | // Or alternatively 21 | $r->addRoute('GET', '/user/{id:\d+}[/{name}]', 'common_handler'); 22 | }); 23 | 24 | $routeInfo = $dispatcher->dispatch($httpMethod, $uri); 25 | switch ($routeInfo[0]) { 26 | case FastRoute\Dispatcher::NOT_FOUND: 27 | // ... 404 Not Found 28 | break; 29 | case FastRoute\Dispatcher::METHOD_NOT_ALLOWED: 30 | $allowedMethods = $routeInfo[1]; 31 | // ... 405 Method Not Allowed 32 | break; 33 | case FastRoute\Dispatcher::FOUND: 34 | $handler = $routeInfo[1]; 35 | $vars = $routeInfo[2]; 36 | // ... call $handler with $vars 37 | break; 38 | } 39 | ``` 40 | 41 | ### Defining routes 42 | 43 | The routes are defined by calling the `FastRoute\simpleDispatcher` function, which accepts 44 | a callable taking a `FastRoute\RouteCollector` instance. The routes are added by calling 45 | `addRoute()` on the collector instance. 46 | 47 | This method accepts the HTTP method the route must match, the route pattern and an associated 48 | handler. The handler does not necessarily have to be a callback (it could also be a controller 49 | class name or any other kind of data you wish to associate with the route). 50 | 51 | By default a route pattern syntax is used where `{foo}` specified a placeholder with name `foo` 52 | and matching the string `[^/]+`. To adjust the pattern the placeholder matches, you can specify 53 | a custom pattern by writing `{bar:[0-9]+}`. 54 | 55 | Furthermore parts of the route enclosed in `[...]` are considered optional, so that `/foo[bar]` 56 | will match both `/foo` and `/foobar`. Optional parts are only supported in a trailing position, 57 | not in the middle of a route. 58 | 59 | A custom pattern for a route placeholder must not use capturing groups. For example `{lang:(en|de)}` 60 | is not a valid placeholder, because `()` is a capturing group. Instead you can use either 61 | `{lang:en|de}` or `{lang:(?:en|de)}`. 62 | 63 | The reason `simpleDispatcher` accepts a callback for defining the routes is to allow seamless 64 | caching. By using `cachedDispatcher` instead of `simpleDispatcher` you can cache the generated 65 | routing data and construct the dispatcher from the cached information: 66 | 67 | ```php 68 | addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); 72 | $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1'); 73 | $r->addRoute('GET', '/user/{name}', 'handler2'); 74 | }, [ 75 | 'cacheFile' => __DIR__ . '/route.cache', /* required */ 76 | 'cacheDisabled' => IS_DEBUG_ENABLED, /* optional, enabled by default */ 77 | ]); 78 | ``` 79 | 80 | The second parameter to the function is an options array, which can be used to specify the cache 81 | file location, among other things. 82 | 83 | ### Dispatching a URI 84 | 85 | A URI is dispatched by calling the `dispatch()` method of the created dispatcher. This method 86 | accepts the HTTP method and a URI. Getting those two bits of information (and normalizing them 87 | appropriately) is your job - this library is not bound to the PHP web SAPIs. 88 | 89 | The `dispatch()` method returns an array whose first element contains a status code. It is one 90 | of `Dispatcher::NOT_FOUND`, `Dispatcher::METHOD_NOT_ALLOWED` and `Dispatcher::FOUND`. For the 91 | method not allowed status the second array element contains a list of HTTP methods allowed for 92 | the supplied URI. For example: 93 | 94 | [FastRoute\Dispatcher::METHOD_NOT_ALLOWED, ['GET', 'POST']] 95 | 96 | > **NOTE:** The HTTP specification requires that a `405 Method Not Allowed` response include the 97 | `Allow:` header to detail available methods for the requested resource. Applications using FastRoute 98 | should use the second array element to add this header when relaying a 405 response. 99 | 100 | For the found status the second array element is the handler that was associated with the route 101 | and the third array element is a dictionary of placeholder names to their values. For example: 102 | 103 | /* Routing against GET /user/nikic/42 */ 104 | 105 | [FastRoute\Dispatcher::FOUND, 'handler0', ['name' => 'nikic', 'id' => '42']] 106 | 107 | ### Overriding the route parser and dispatcher 108 | 109 | The routing process makes use of three components: A route parser, a data generator and a 110 | dispatcher. The three components adhere to the following interfaces: 111 | 112 | ```php 113 | 'FastRoute\\RouteParser\\Std', 174 | 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', 175 | 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', 176 | ]); 177 | ``` 178 | 179 | The above options array corresponds to the defaults. By replacing `GroupCountBased` by 180 | `GroupPosBased` you could switch to a different dispatching strategy. 181 | 182 | ### A Note on HEAD Requests 183 | 184 | The HTTP spec requires servers to [support both GET and HEAD methods][2616-511]: 185 | 186 | > The methods GET and HEAD MUST be supported by all general-purpose servers 187 | 188 | To avoid forcing users to manually register HEAD routes for each resource we fallback to matching an 189 | available GET route for a given resource. The PHP web SAPI transparently removes the entity body 190 | from HEAD responses so this behavior has no effect on the vast majority of users. 191 | 192 | However, implementors using FastRoute outside the web SAPI environment (e.g. a custom server) MUST 193 | NOT send entity bodies generated in response to HEAD requests. If you are a non-SAPI user this is 194 | *your responsibility*; FastRoute has no purview to prevent you from breaking HTTP in such cases. 195 | 196 | Finally, note that applications MAY always specify their own HEAD method route for a given 197 | resource to bypass this behavior entirely. 198 | 199 | ### Credits 200 | 201 | This library is based on a router that [Levi Morrison][levi] implemented for the Aerys server. 202 | 203 | A large number of tests, as well as HTTP compliance considerations, were provided by [Daniel Lowrey][rdlowrey]. 204 | 205 | 206 | [2616-511]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.1 "RFC 2616 Section 5.1.1" 207 | [blog_post]: http://nikic.github.io/2014/02/18/Fast-request-routing-using-regular-expressions.html 208 | [levi]: https://github.com/morrisonlevi 209 | [rdlowrey]: https://github.com/rdlowrey 210 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nikic/fast-route", 3 | "description": "Fast request router for PHP", 4 | "keywords": ["routing", "router"], 5 | "license": "BSD-3-Clause", 6 | "authors": [ 7 | { 8 | "name": "Nikita Popov", 9 | "email": "nikic@php.net" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.4.0" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "FastRoute\\": "src/" 18 | }, 19 | "files": ["src/functions.php"] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | ./test/ 16 | 17 | 18 | 19 | 20 | 21 | ./src/ 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/src/BadRouteException.php: -------------------------------------------------------------------------------- 1 | $route) { 18 | $suffixLen++; 19 | $suffix .= "\t"; 20 | 21 | $regexes[] = '(?:' . $regex . '/(\t{' . $suffixLen . '})\t{' . ($count - $suffixLen) . '})'; 22 | $routeMap[$suffix] = [$route->handler, $route->variables]; 23 | } 24 | 25 | $regex = '~^(?|' . implode('|', $regexes) . ')$~'; 26 | return ['regex' => $regex, 'suffix' => '/' . $suffix, 'routeMap' => $routeMap]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/src/DataGenerator/GroupCountBased.php: -------------------------------------------------------------------------------- 1 | $route) { 15 | $numVariables = count($route->variables); 16 | $numGroups = max($numGroups, $numVariables); 17 | 18 | $regexes[] = $regex . str_repeat('()', $numGroups - $numVariables); 19 | $routeMap[$numGroups + 1] = [$route->handler, $route->variables]; 20 | 21 | ++$numGroups; 22 | } 23 | 24 | $regex = '~^(?|' . implode('|', $regexes) . ')$~'; 25 | return ['regex' => $regex, 'routeMap' => $routeMap]; 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/src/DataGenerator/GroupPosBased.php: -------------------------------------------------------------------------------- 1 | $route) { 15 | $regexes[] = $regex; 16 | $routeMap[$offset] = [$route->handler, $route->variables]; 17 | 18 | $offset += count($route->variables); 19 | } 20 | 21 | $regex = '~^(?:' . implode('|', $regexes) . ')$~'; 22 | return ['regex' => $regex, 'routeMap' => $routeMap]; 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/src/DataGenerator/MarkBased.php: -------------------------------------------------------------------------------- 1 | $route) { 15 | $regexes[] = $regex . '(*MARK:' . $markName . ')'; 16 | $routeMap[$markName] = [$route->handler, $route->variables]; 17 | 18 | ++$markName; 19 | } 20 | 21 | $regex = '~^(?|' . implode('|', $regexes) . ')$~'; 22 | return ['regex' => $regex, 'routeMap' => $routeMap]; 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/src/DataGenerator/RegexBasedAbstract.php: -------------------------------------------------------------------------------- 1 | isStaticRoute($routeData)) { 18 | $this->addStaticRoute($httpMethod, $routeData, $handler); 19 | } else { 20 | $this->addVariableRoute($httpMethod, $routeData, $handler); 21 | } 22 | } 23 | 24 | public function getData() { 25 | if (empty($this->methodToRegexToRoutesMap)) { 26 | return [$this->staticRoutes, []]; 27 | } 28 | 29 | return [$this->staticRoutes, $this->generateVariableRouteData()]; 30 | } 31 | 32 | private function generateVariableRouteData() { 33 | $data = []; 34 | foreach ($this->methodToRegexToRoutesMap as $method => $regexToRoutesMap) { 35 | $chunkSize = $this->computeChunkSize(count($regexToRoutesMap)); 36 | $chunks = array_chunk($regexToRoutesMap, $chunkSize, true); 37 | $data[$method] = array_map([$this, 'processChunk'], $chunks); 38 | } 39 | return $data; 40 | } 41 | 42 | private function computeChunkSize($count) { 43 | $numParts = max(1, round($count / $this->getApproxChunkSize())); 44 | return ceil($count / $numParts); 45 | } 46 | 47 | private function isStaticRoute($routeData) { 48 | return count($routeData) == 1 && is_string($routeData[0]); 49 | } 50 | 51 | private function addStaticRoute($httpMethod, $routeData, $handler) { 52 | $routeStr = $routeData[0]; 53 | 54 | if (isset($this->staticRoutes[$httpMethod][$routeStr])) { 55 | throw new BadRouteException(sprintf( 56 | 'Cannot register two routes matching "%s" for method "%s"', 57 | $routeStr, $httpMethod 58 | )); 59 | } 60 | 61 | if (isset($this->methodToRegexToRoutesMap[$httpMethod])) { 62 | foreach ($this->methodToRegexToRoutesMap[$httpMethod] as $route) { 63 | if ($route->matches($routeStr)) { 64 | throw new BadRouteException(sprintf( 65 | 'Static route "%s" is shadowed by previously defined variable route "%s" for method "%s"', 66 | $routeStr, $route->regex, $httpMethod 67 | )); 68 | } 69 | } 70 | } 71 | 72 | $this->staticRoutes[$httpMethod][$routeStr] = $handler; 73 | } 74 | 75 | private function addVariableRoute($httpMethod, $routeData, $handler) { 76 | list($regex, $variables) = $this->buildRegexForRoute($routeData); 77 | 78 | if (isset($this->methodToRegexToRoutesMap[$httpMethod][$regex])) { 79 | throw new BadRouteException(sprintf( 80 | 'Cannot register two routes matching "%s" for method "%s"', 81 | $regex, $httpMethod 82 | )); 83 | } 84 | 85 | $this->methodToRegexToRoutesMap[$httpMethod][$regex] = new Route( 86 | $httpMethod, $handler, $regex, $variables 87 | ); 88 | } 89 | 90 | private function buildRegexForRoute($routeData) { 91 | $regex = ''; 92 | $variables = []; 93 | foreach ($routeData as $part) { 94 | if (is_string($part)) { 95 | $regex .= preg_quote($part, '~'); 96 | continue; 97 | } 98 | 99 | list($varName, $regexPart) = $part; 100 | 101 | if (isset($variables[$varName])) { 102 | throw new BadRouteException(sprintf( 103 | 'Cannot use the same placeholder "%s" twice', $varName 104 | )); 105 | } 106 | 107 | if ($this->regexHasCapturingGroups($regexPart)) { 108 | throw new BadRouteException(sprintf( 109 | 'Regex "%s" for parameter "%s" contains a capturing group', 110 | $regexPart, $varName 111 | )); 112 | } 113 | 114 | $variables[$varName] = $varName; 115 | $regex .= '(' . $regexPart . ')'; 116 | } 117 | 118 | return [$regex, $variables]; 119 | } 120 | 121 | private function regexHasCapturingGroups($regex) { 122 | if (false === strpos($regex, '(')) { 123 | // Needs to have at least a ( to contain a capturing group 124 | return false; 125 | } 126 | 127 | // Semi-accurate detection for capturing groups 128 | return preg_match( 129 | '~ 130 | (?: 131 | \(\?\( 132 | | \[ [^\]\\\\]* (?: \\\\ . [^\]\\\\]* )* \] 133 | | \\\\ . 134 | ) (*SKIP)(*FAIL) | 135 | \( 136 | (?! 137 | \? (?! <(?![!=]) | P< | \' ) 138 | | \* 139 | ) 140 | ~x', 141 | $regex 142 | ); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/src/Dispatcher.php: -------------------------------------------------------------------------------- 1 | 'value', ...]] 18 | * 19 | * @param string $httpMethod 20 | * @param string $uri 21 | * 22 | * @return array 23 | */ 24 | public function dispatch($httpMethod, $uri); 25 | } 26 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/src/Dispatcher/CharCountBased.php: -------------------------------------------------------------------------------- 1 | staticRouteMap, $this->variableRouteData) = $data; 8 | } 9 | 10 | protected function dispatchVariableRoute($routeData, $uri) { 11 | foreach ($routeData as $data) { 12 | if (!preg_match($data['regex'], $uri . $data['suffix'], $matches)) { 13 | continue; 14 | } 15 | 16 | list($handler, $varNames) = $data['routeMap'][end($matches)]; 17 | 18 | $vars = []; 19 | $i = 0; 20 | foreach ($varNames as $varName) { 21 | $vars[$varName] = $matches[++$i]; 22 | } 23 | return [self::FOUND, $handler, $vars]; 24 | } 25 | 26 | return [self::NOT_FOUND]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/src/Dispatcher/GroupCountBased.php: -------------------------------------------------------------------------------- 1 | staticRouteMap, $this->variableRouteData) = $data; 8 | } 9 | 10 | protected function dispatchVariableRoute($routeData, $uri) { 11 | foreach ($routeData as $data) { 12 | if (!preg_match($data['regex'], $uri, $matches)) { 13 | continue; 14 | } 15 | 16 | list($handler, $varNames) = $data['routeMap'][count($matches)]; 17 | 18 | $vars = []; 19 | $i = 0; 20 | foreach ($varNames as $varName) { 21 | $vars[$varName] = $matches[++$i]; 22 | } 23 | return [self::FOUND, $handler, $vars]; 24 | } 25 | 26 | return [self::NOT_FOUND]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/src/Dispatcher/GroupPosBased.php: -------------------------------------------------------------------------------- 1 | staticRouteMap, $this->variableRouteData) = $data; 8 | } 9 | 10 | protected function dispatchVariableRoute($routeData, $uri) { 11 | foreach ($routeData as $data) { 12 | if (!preg_match($data['regex'], $uri, $matches)) { 13 | continue; 14 | } 15 | 16 | // find first non-empty match 17 | for ($i = 1; '' === $matches[$i]; ++$i); 18 | 19 | list($handler, $varNames) = $data['routeMap'][$i]; 20 | 21 | $vars = []; 22 | foreach ($varNames as $varName) { 23 | $vars[$varName] = $matches[$i++]; 24 | } 25 | return [self::FOUND, $handler, $vars]; 26 | } 27 | 28 | return [self::NOT_FOUND]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/src/Dispatcher/MarkBased.php: -------------------------------------------------------------------------------- 1 | staticRouteMap, $this->variableRouteData) = $data; 8 | } 9 | 10 | protected function dispatchVariableRoute($routeData, $uri) { 11 | foreach ($routeData as $data) { 12 | if (!preg_match($data['regex'], $uri, $matches)) { 13 | continue; 14 | } 15 | 16 | list($handler, $varNames) = $data['routeMap'][$matches['MARK']]; 17 | 18 | $vars = []; 19 | $i = 0; 20 | foreach ($varNames as $varName) { 21 | $vars[$varName] = $matches[++$i]; 22 | } 23 | return [self::FOUND, $handler, $vars]; 24 | } 25 | 26 | return [self::NOT_FOUND]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/src/Dispatcher/RegexBasedAbstract.php: -------------------------------------------------------------------------------- 1 | staticRouteMap[$httpMethod][$uri])) { 15 | $handler = $this->staticRouteMap[$httpMethod][$uri]; 16 | return [self::FOUND, $handler, []]; 17 | } else if ($httpMethod === 'HEAD' && isset($this->staticRouteMap['GET'][$uri])) { 18 | $handler = $this->staticRouteMap['GET'][$uri]; 19 | return [self::FOUND, $handler, []]; 20 | } 21 | 22 | $varRouteData = $this->variableRouteData; 23 | if (isset($varRouteData[$httpMethod])) { 24 | $result = $this->dispatchVariableRoute($varRouteData[$httpMethod], $uri); 25 | if ($result[0] === self::FOUND) { 26 | return $result; 27 | } 28 | } else if ($httpMethod === 'HEAD' && isset($varRouteData['GET'])) { 29 | $result = $this->dispatchVariableRoute($varRouteData['GET'], $uri); 30 | if ($result[0] === self::FOUND) { 31 | return $result; 32 | } 33 | } 34 | 35 | // Find allowed methods for this URI by matching against all other HTTP methods as well 36 | $allowedMethods = []; 37 | 38 | foreach ($this->staticRouteMap as $method => $uriMap) { 39 | if ($method !== $httpMethod && isset($uriMap[$uri])) { 40 | $allowedMethods[] = $method; 41 | } 42 | } 43 | 44 | foreach ($varRouteData as $method => $routeData) { 45 | if ($method === $httpMethod) { 46 | continue; 47 | } 48 | 49 | $result = $this->dispatchVariableRoute($routeData, $uri); 50 | if ($result[0] === self::FOUND) { 51 | $allowedMethods[] = $method; 52 | } 53 | } 54 | 55 | // If there are no allowed methods the route simply does not exist 56 | if ($allowedMethods) { 57 | return [self::METHOD_NOT_ALLOWED, $allowedMethods]; 58 | } else { 59 | return [self::NOT_FOUND]; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/src/Route.php: -------------------------------------------------------------------------------- 1 | httpMethod = $httpMethod; 21 | $this->handler = $handler; 22 | $this->regex = $regex; 23 | $this->variables = $variables; 24 | } 25 | 26 | /** 27 | * Tests whether this route matches the given string. 28 | * 29 | * @param string $str 30 | * 31 | * @return bool 32 | */ 33 | public function matches($str) { 34 | $regex = '~^' . $this->regex . '$~'; 35 | return (bool) preg_match($regex, $str); 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/src/RouteCollector.php: -------------------------------------------------------------------------------- 1 | routeParser = $routeParser; 17 | $this->dataGenerator = $dataGenerator; 18 | } 19 | 20 | /** 21 | * Adds a route to the collection. 22 | * 23 | * The syntax used in the $route string depends on the used route parser. 24 | * 25 | * @param string|string[] $httpMethod 26 | * @param string $route 27 | * @param mixed $handler 28 | */ 29 | public function addRoute($httpMethod, $route, $handler) { 30 | $routeDatas = $this->routeParser->parse($route); 31 | foreach ((array) $httpMethod as $method) { 32 | foreach ($routeDatas as $routeData) { 33 | $this->dataGenerator->addRoute($method, $routeData, $handler); 34 | } 35 | } 36 | } 37 | 38 | /** 39 | * Returns the collected route data, as provided by the data generator. 40 | * 41 | * @return array 42 | */ 43 | public function getData() { 44 | return $this->dataGenerator->getData(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/src/RouteParser.php: -------------------------------------------------------------------------------- 1 | parsePlaceholders($currentRoute); 43 | } 44 | return $routeDatas; 45 | } 46 | 47 | /** 48 | * Parses a route string that does not contain optional segments. 49 | */ 50 | private function parsePlaceholders($route) { 51 | if (!preg_match_all( 52 | '~' . self::VARIABLE_REGEX . '~x', $route, $matches, 53 | PREG_OFFSET_CAPTURE | PREG_SET_ORDER 54 | )) { 55 | return [$route]; 56 | } 57 | 58 | $offset = 0; 59 | $routeData = []; 60 | foreach ($matches as $set) { 61 | if ($set[0][1] > $offset) { 62 | $routeData[] = substr($route, $offset, $set[0][1] - $offset); 63 | } 64 | $routeData[] = [ 65 | $set[1][0], 66 | isset($set[2]) ? trim($set[2][0]) : self::DEFAULT_DISPATCH_REGEX 67 | ]; 68 | $offset = $set[0][1] + strlen($set[0][0]); 69 | } 70 | 71 | if ($offset != strlen($route)) { 72 | $routeData[] = substr($route, $offset); 73 | } 74 | 75 | return $routeData; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/src/bootstrap.php: -------------------------------------------------------------------------------- 1 | 'FastRoute\\RouteParser\\Std', 15 | 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', 16 | 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', 17 | 'routeCollector' => 'FastRoute\\RouteCollector', 18 | ]; 19 | 20 | /** @var RouteCollector $routeCollector */ 21 | $routeCollector = new $options['routeCollector']( 22 | new $options['routeParser'], new $options['dataGenerator'] 23 | ); 24 | $routeDefinitionCallback($routeCollector); 25 | 26 | return new $options['dispatcher']($routeCollector->getData()); 27 | } 28 | 29 | /** 30 | * @param callable $routeDefinitionCallback 31 | * @param array $options 32 | * 33 | * @return Dispatcher 34 | */ 35 | function cachedDispatcher(callable $routeDefinitionCallback, array $options = []) { 36 | $options += [ 37 | 'routeParser' => 'FastRoute\\RouteParser\\Std', 38 | 'dataGenerator' => 'FastRoute\\DataGenerator\\GroupCountBased', 39 | 'dispatcher' => 'FastRoute\\Dispatcher\\GroupCountBased', 40 | 'routeCollector' => 'FastRoute\\RouteCollector', 41 | 'cacheDisabled' => false, 42 | ]; 43 | 44 | if (!isset($options['cacheFile'])) { 45 | throw new \LogicException('Must specify "cacheFile" option'); 46 | } 47 | 48 | if (!$options['cacheDisabled'] && file_exists($options['cacheFile'])) { 49 | $dispatchData = require $options['cacheFile']; 50 | if (!is_array($dispatchData)) { 51 | throw new \RuntimeException('Invalid cache file "' . $options['cacheFile'] . '"'); 52 | } 53 | return new $options['dispatcher']($dispatchData); 54 | } 55 | 56 | $routeCollector = new $options['routeCollector']( 57 | new $options['routeParser'], new $options['dataGenerator'] 58 | ); 59 | $routeDefinitionCallback($routeCollector); 60 | 61 | /** @var RouteCollector $routeCollector */ 62 | $dispatchData = $routeCollector->getData(); 63 | file_put_contents( 64 | $options['cacheFile'], 65 | ' $this->getDataGeneratorClass(), 25 | 'dispatcher' => $this->getDispatcherClass() 26 | ]; 27 | } 28 | 29 | /** 30 | * @dataProvider provideFoundDispatchCases 31 | */ 32 | public function testFoundDispatches($method, $uri, $callback, $handler, $argDict) { 33 | $dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions()); 34 | $info = $dispatcher->dispatch($method, $uri); 35 | $this->assertSame($dispatcher::FOUND, $info[0]); 36 | $this->assertSame($handler, $info[1]); 37 | $this->assertSame($argDict, $info[2]); 38 | } 39 | 40 | /** 41 | * @dataProvider provideNotFoundDispatchCases 42 | */ 43 | public function testNotFoundDispatches($method, $uri, $callback) { 44 | $dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions()); 45 | $routeInfo = $dispatcher->dispatch($method, $uri); 46 | $this->assertFalse(isset($routeInfo[1]), 47 | "NOT_FOUND result must only contain a single element in the returned info array" 48 | ); 49 | $this->assertSame($dispatcher::NOT_FOUND, $routeInfo[0]); 50 | } 51 | 52 | /** 53 | * @dataProvider provideMethodNotAllowedDispatchCases 54 | */ 55 | public function testMethodNotAllowedDispatches($method, $uri, $callback, $availableMethods) { 56 | $dispatcher = \FastRoute\simpleDispatcher($callback, $this->generateDispatcherOptions()); 57 | $routeInfo = $dispatcher->dispatch($method, $uri); 58 | $this->assertTrue(isset($routeInfo[1]), 59 | "METHOD_NOT_ALLOWED result must return an array of allowed methods at index 1" 60 | ); 61 | 62 | list($routedStatus, $methodArray) = $dispatcher->dispatch($method, $uri); 63 | $this->assertSame($dispatcher::METHOD_NOT_ALLOWED, $routedStatus); 64 | $this->assertSame($availableMethods, $methodArray); 65 | } 66 | 67 | /** 68 | * @expectedException \FastRoute\BadRouteException 69 | * @expectedExceptionMessage Cannot use the same placeholder "test" twice 70 | */ 71 | public function testDuplicateVariableNameError() { 72 | \FastRoute\simpleDispatcher(function(RouteCollector $r) { 73 | $r->addRoute('GET', '/foo/{test}/{test:\d+}', 'handler0'); 74 | }, $this->generateDispatcherOptions()); 75 | } 76 | 77 | /** 78 | * @expectedException \FastRoute\BadRouteException 79 | * @expectedExceptionMessage Cannot register two routes matching "/user/([^/]+)" for method "GET" 80 | */ 81 | public function testDuplicateVariableRoute() { 82 | \FastRoute\simpleDispatcher(function(RouteCollector $r) { 83 | $r->addRoute('GET', '/user/{id}', 'handler0'); // oops, forgot \d+ restriction ;) 84 | $r->addRoute('GET', '/user/{name}', 'handler1'); 85 | }, $this->generateDispatcherOptions()); 86 | } 87 | 88 | /** 89 | * @expectedException \FastRoute\BadRouteException 90 | * @expectedExceptionMessage Cannot register two routes matching "/user" for method "GET" 91 | */ 92 | public function testDuplicateStaticRoute() { 93 | \FastRoute\simpleDispatcher(function(RouteCollector $r) { 94 | $r->addRoute('GET', '/user', 'handler0'); 95 | $r->addRoute('GET', '/user', 'handler1'); 96 | }, $this->generateDispatcherOptions()); 97 | } 98 | 99 | /** 100 | * @expectedException \FastRoute\BadRouteException 101 | * @expectedExceptionMessage Static route "/user/nikic" is shadowed by previously defined variable route "/user/([^/]+)" for method "GET" 102 | */ 103 | public function testShadowedStaticRoute() { 104 | \FastRoute\simpleDispatcher(function(RouteCollector $r) { 105 | $r->addRoute('GET', '/user/{name}', 'handler0'); 106 | $r->addRoute('GET', '/user/nikic', 'handler1'); 107 | }, $this->generateDispatcherOptions()); 108 | } 109 | 110 | /** 111 | * @expectedException \FastRoute\BadRouteException 112 | * @expectedExceptionMessage Regex "(en|de)" for parameter "lang" contains a capturing group 113 | */ 114 | public function testCapturing() { 115 | \FastRoute\simpleDispatcher(function(RouteCollector $r) { 116 | $r->addRoute('GET', '/{lang:(en|de)}', 'handler0'); 117 | }, $this->generateDispatcherOptions()); 118 | } 119 | 120 | public function provideFoundDispatchCases() { 121 | $cases = []; 122 | 123 | // 0 --------------------------------------------------------------------------------------> 124 | 125 | $callback = function(RouteCollector $r) { 126 | $r->addRoute('GET', '/resource/123/456', 'handler0'); 127 | }; 128 | 129 | $method = 'GET'; 130 | $uri = '/resource/123/456'; 131 | $handler = 'handler0'; 132 | $argDict = []; 133 | 134 | $cases[] = [$method, $uri, $callback, $handler, $argDict]; 135 | 136 | // 1 --------------------------------------------------------------------------------------> 137 | 138 | $callback = function(RouteCollector $r) { 139 | $r->addRoute('GET', '/handler0', 'handler0'); 140 | $r->addRoute('GET', '/handler1', 'handler1'); 141 | $r->addRoute('GET', '/handler2', 'handler2'); 142 | }; 143 | 144 | $method = 'GET'; 145 | $uri = '/handler2'; 146 | $handler = 'handler2'; 147 | $argDict = []; 148 | 149 | $cases[] = [$method, $uri, $callback, $handler, $argDict]; 150 | 151 | // 2 --------------------------------------------------------------------------------------> 152 | 153 | $callback = function(RouteCollector $r) { 154 | $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); 155 | $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1'); 156 | $r->addRoute('GET', '/user/{name}', 'handler2'); 157 | }; 158 | 159 | $method = 'GET'; 160 | $uri = '/user/rdlowrey'; 161 | $handler = 'handler2'; 162 | $argDict = ['name' => 'rdlowrey']; 163 | 164 | $cases[] = [$method, $uri, $callback, $handler, $argDict]; 165 | 166 | // 3 --------------------------------------------------------------------------------------> 167 | 168 | // reuse $callback from #2 169 | 170 | $method = 'GET'; 171 | $uri = '/user/12345'; 172 | $handler = 'handler1'; 173 | $argDict = ['id' => '12345']; 174 | 175 | $cases[] = [$method, $uri, $callback, $handler, $argDict]; 176 | 177 | // 4 --------------------------------------------------------------------------------------> 178 | 179 | // reuse $callback from #3 180 | 181 | $method = 'GET'; 182 | $uri = '/user/NaN'; 183 | $handler = 'handler2'; 184 | $argDict = ['name' => 'NaN']; 185 | 186 | $cases[] = [$method, $uri, $callback, $handler, $argDict]; 187 | 188 | // 5 --------------------------------------------------------------------------------------> 189 | 190 | // reuse $callback from #4 191 | 192 | $method = 'GET'; 193 | $uri = '/user/rdlowrey/12345'; 194 | $handler = 'handler0'; 195 | $argDict = ['name' => 'rdlowrey', 'id' => '12345']; 196 | 197 | $cases[] = [$method, $uri, $callback, $handler, $argDict]; 198 | 199 | // 6 --------------------------------------------------------------------------------------> 200 | 201 | $callback = function(RouteCollector $r) { 202 | $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler0'); 203 | $r->addRoute('GET', '/user/12345/extension', 'handler1'); 204 | $r->addRoute('GET', '/user/{id:[0-9]+}.{extension}', 'handler2'); 205 | 206 | }; 207 | 208 | $method = 'GET'; 209 | $uri = '/user/12345.svg'; 210 | $handler = 'handler2'; 211 | $argDict = ['id' => '12345', 'extension' => 'svg']; 212 | 213 | $cases[] = [$method, $uri, $callback, $handler, $argDict]; 214 | 215 | // 7 ----- Test GET method fallback on HEAD route miss ------------------------------------> 216 | 217 | $callback = function(RouteCollector $r) { 218 | $r->addRoute('GET', '/user/{name}', 'handler0'); 219 | $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler1'); 220 | $r->addRoute('GET', '/static0', 'handler2'); 221 | $r->addRoute('GET', '/static1', 'handler3'); 222 | $r->addRoute('HEAD', '/static1', 'handler4'); 223 | }; 224 | 225 | $method = 'HEAD'; 226 | $uri = '/user/rdlowrey'; 227 | $handler = 'handler0'; 228 | $argDict = ['name' => 'rdlowrey']; 229 | 230 | $cases[] = [$method, $uri, $callback, $handler, $argDict]; 231 | 232 | // 8 ----- Test GET method fallback on HEAD route miss ------------------------------------> 233 | 234 | // reuse $callback from #7 235 | 236 | $method = 'HEAD'; 237 | $uri = '/user/rdlowrey/1234'; 238 | $handler = 'handler1'; 239 | $argDict = ['name' => 'rdlowrey', 'id' => '1234']; 240 | 241 | $cases[] = [$method, $uri, $callback, $handler, $argDict]; 242 | 243 | // 9 ----- Test GET method fallback on HEAD route miss ------------------------------------> 244 | 245 | // reuse $callback from #8 246 | 247 | $method = 'HEAD'; 248 | $uri = '/static0'; 249 | $handler = 'handler2'; 250 | $argDict = []; 251 | 252 | $cases[] = [$method, $uri, $callback, $handler, $argDict]; 253 | 254 | // 10 ---- Test existing HEAD route used if available (no fallback) -----------------------> 255 | 256 | // reuse $callback from #9 257 | 258 | $method = 'HEAD'; 259 | $uri = '/static1'; 260 | $handler = 'handler4'; 261 | $argDict = []; 262 | 263 | $cases[] = [$method, $uri, $callback, $handler, $argDict]; 264 | 265 | // 11 ---- More specified routes are not shadowed by less specific of another method ------> 266 | 267 | $callback = function(RouteCollector $r) { 268 | $r->addRoute('GET', '/user/{name}', 'handler0'); 269 | $r->addRoute('POST', '/user/{name:[a-z]+}', 'handler1'); 270 | }; 271 | 272 | $method = 'POST'; 273 | $uri = '/user/rdlowrey'; 274 | $handler = 'handler1'; 275 | $argDict = ['name' => 'rdlowrey']; 276 | 277 | $cases[] = [$method, $uri, $callback, $handler, $argDict]; 278 | 279 | // 12 ---- Handler of more specific routes is used, if it occurs first --------------------> 280 | 281 | $callback = function(RouteCollector $r) { 282 | $r->addRoute('GET', '/user/{name}', 'handler0'); 283 | $r->addRoute('POST', '/user/{name:[a-z]+}', 'handler1'); 284 | $r->addRoute('POST', '/user/{name}', 'handler2'); 285 | }; 286 | 287 | $method = 'POST'; 288 | $uri = '/user/rdlowrey'; 289 | $handler = 'handler1'; 290 | $argDict = ['name' => 'rdlowrey']; 291 | 292 | $cases[] = [$method, $uri, $callback, $handler, $argDict]; 293 | 294 | // 13 ---- Route with constant suffix -----------------------------------------------------> 295 | 296 | $callback = function(RouteCollector $r) { 297 | $r->addRoute('GET', '/user/{name}', 'handler0'); 298 | $r->addRoute('GET', '/user/{name}/edit', 'handler1'); 299 | }; 300 | 301 | $method = 'GET'; 302 | $uri = '/user/rdlowrey/edit'; 303 | $handler = 'handler1'; 304 | $argDict = ['name' => 'rdlowrey']; 305 | 306 | $cases[] = [$method, $uri, $callback, $handler, $argDict]; 307 | 308 | // 14 ---- Handle multiple methods with the same handler ----------------------------------> 309 | 310 | $callback = function(RouteCollector $r) { 311 | $r->addRoute(['GET', 'POST'], '/user', 'handlerGetPost'); 312 | $r->addRoute(['DELETE'], '/user', 'handlerDelete'); 313 | $r->addRoute([], '/user', 'handlerNone'); 314 | }; 315 | 316 | $argDict = []; 317 | $cases[] = ['GET', '/user', $callback, 'handlerGetPost', $argDict]; 318 | $cases[] = ['POST', '/user', $callback, 'handlerGetPost', $argDict]; 319 | $cases[] = ['DELETE', '/user', $callback, 'handlerDelete', $argDict]; 320 | 321 | // 15 ---- 322 | 323 | $callback = function(RouteCollector $r) { 324 | $r->addRoute('POST', '/user.json', 'handler0'); 325 | $r->addRoute('GET', '/{entity}.json', 'handler1'); 326 | }; 327 | 328 | $cases[] = ['GET', '/user.json', $callback, 'handler1', ['entity' => 'user']]; 329 | 330 | 331 | // x --------------------------------------------------------------------------------------> 332 | 333 | return $cases; 334 | } 335 | 336 | public function provideNotFoundDispatchCases() { 337 | $cases = []; 338 | 339 | // 0 --------------------------------------------------------------------------------------> 340 | 341 | $callback = function(RouteCollector $r) { 342 | $r->addRoute('GET', '/resource/123/456', 'handler0'); 343 | }; 344 | 345 | $method = 'GET'; 346 | $uri = '/not-found'; 347 | 348 | $cases[] = [$method, $uri, $callback]; 349 | 350 | // 1 --------------------------------------------------------------------------------------> 351 | 352 | // reuse callback from #0 353 | $method = 'POST'; 354 | $uri = '/not-found'; 355 | 356 | $cases[] = [$method, $uri, $callback]; 357 | 358 | // 2 --------------------------------------------------------------------------------------> 359 | 360 | // reuse callback from #1 361 | $method = 'PUT'; 362 | $uri = '/not-found'; 363 | 364 | $cases[] = [$method, $uri, $callback]; 365 | 366 | // 3 --------------------------------------------------------------------------------------> 367 | 368 | $callback = function(RouteCollector $r) { 369 | $r->addRoute('GET', '/handler0', 'handler0'); 370 | $r->addRoute('GET', '/handler1', 'handler1'); 371 | $r->addRoute('GET', '/handler2', 'handler2'); 372 | }; 373 | 374 | $method = 'GET'; 375 | $uri = '/not-found'; 376 | 377 | $cases[] = [$method, $uri, $callback]; 378 | 379 | // 4 --------------------------------------------------------------------------------------> 380 | 381 | $callback = function(RouteCollector $r) { 382 | $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); 383 | $r->addRoute('GET', '/user/{id:[0-9]+}', 'handler1'); 384 | $r->addRoute('GET', '/user/{name}', 'handler2'); 385 | }; 386 | 387 | $method = 'GET'; 388 | $uri = '/not-found'; 389 | 390 | $cases[] = [$method, $uri, $callback]; 391 | 392 | // 5 --------------------------------------------------------------------------------------> 393 | 394 | // reuse callback from #4 395 | $method = 'GET'; 396 | $uri = '/user/rdlowrey/12345/not-found'; 397 | 398 | $cases[] = [$method, $uri, $callback]; 399 | 400 | // 6 --------------------------------------------------------------------------------------> 401 | 402 | // reuse callback from #5 403 | $method = 'HEAD'; 404 | 405 | $cases[] = array($method, $uri, $callback); 406 | 407 | // x --------------------------------------------------------------------------------------> 408 | 409 | return $cases; 410 | } 411 | 412 | public function provideMethodNotAllowedDispatchCases() { 413 | $cases = []; 414 | 415 | // 0 --------------------------------------------------------------------------------------> 416 | 417 | $callback = function(RouteCollector $r) { 418 | $r->addRoute('GET', '/resource/123/456', 'handler0'); 419 | }; 420 | 421 | $method = 'POST'; 422 | $uri = '/resource/123/456'; 423 | $allowedMethods = ['GET']; 424 | 425 | $cases[] = [$method, $uri, $callback, $allowedMethods]; 426 | 427 | // 1 --------------------------------------------------------------------------------------> 428 | 429 | $callback = function(RouteCollector $r) { 430 | $r->addRoute('GET', '/resource/123/456', 'handler0'); 431 | $r->addRoute('POST', '/resource/123/456', 'handler1'); 432 | $r->addRoute('PUT', '/resource/123/456', 'handler2'); 433 | }; 434 | 435 | $method = 'DELETE'; 436 | $uri = '/resource/123/456'; 437 | $allowedMethods = ['GET', 'POST', 'PUT']; 438 | 439 | $cases[] = [$method, $uri, $callback, $allowedMethods]; 440 | 441 | // 2 --------------------------------------------------------------------------------------> 442 | 443 | $callback = function(RouteCollector $r) { 444 | $r->addRoute('GET', '/user/{name}/{id:[0-9]+}', 'handler0'); 445 | $r->addRoute('POST', '/user/{name}/{id:[0-9]+}', 'handler1'); 446 | $r->addRoute('PUT', '/user/{name}/{id:[0-9]+}', 'handler2'); 447 | $r->addRoute('PATCH', '/user/{name}/{id:[0-9]+}', 'handler3'); 448 | }; 449 | 450 | $method = 'DELETE'; 451 | $uri = '/user/rdlowrey/42'; 452 | $allowedMethods = ['GET', 'POST', 'PUT', 'PATCH']; 453 | 454 | $cases[] = [$method, $uri, $callback, $allowedMethods]; 455 | 456 | // 3 --------------------------------------------------------------------------------------> 457 | 458 | $callback = function(RouteCollector $r) { 459 | $r->addRoute('POST', '/user/{name}', 'handler1'); 460 | $r->addRoute('PUT', '/user/{name:[a-z]+}', 'handler2'); 461 | $r->addRoute('PATCH', '/user/{name:[a-z]+}', 'handler3'); 462 | }; 463 | 464 | $method = 'GET'; 465 | $uri = '/user/rdlowrey'; 466 | $allowedMethods = ['POST', 'PUT', 'PATCH']; 467 | 468 | $cases[] = [$method, $uri, $callback, $allowedMethods]; 469 | 470 | // 4 --------------------------------------------------------------------------------------> 471 | 472 | $callback = function(RouteCollector $r) { 473 | $r->addRoute(['GET', 'POST'], '/user', 'handlerGetPost'); 474 | $r->addRoute(['DELETE'], '/user', 'handlerDelete'); 475 | $r->addRoute([], '/user', 'handlerNone'); 476 | }; 477 | 478 | $cases[] = ['PUT', '/user', $callback, ['GET', 'POST', 'DELETE']]; 479 | 480 | // 5 481 | 482 | $callback = function(RouteCollector $r) { 483 | $r->addRoute('POST', '/user.json', 'handler0'); 484 | $r->addRoute('GET', '/{entity}.json', 'handler1'); 485 | }; 486 | 487 | $cases[] = ['PUT', '/user.json', $callback, ['POST', 'GET']]; 488 | 489 | // x --------------------------------------------------------------------------------------> 490 | 491 | return $cases; 492 | } 493 | 494 | } 495 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/test/Dispatcher/GroupCountBasedTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('PHP 5.6 required for MARK support'); 10 | } 11 | } 12 | 13 | protected function getDispatcherClass() { 14 | return 'FastRoute\\Dispatcher\\MarkBased'; 15 | } 16 | 17 | protected function getDataGeneratorClass() { 18 | return 'FastRoute\\DataGenerator\\MarkBased'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/test/RouteParser/StdTest.php: -------------------------------------------------------------------------------- 1 | parse($routeString); 10 | $this->assertSame($expectedRouteDatas, $routeDatas); 11 | } 12 | 13 | /** @dataProvider provideTestParseError */ 14 | public function testParseError($routeString, $expectedExceptionMessage) { 15 | $parser = new Std(); 16 | $this->setExpectedException('FastRoute\\BadRouteException', $expectedExceptionMessage); 17 | $parser->parse($routeString); 18 | } 19 | 20 | public function provideTestParse() { 21 | return [ 22 | [ 23 | '/test', 24 | [ 25 | ['/test'], 26 | ] 27 | ], 28 | [ 29 | '/test/{param}', 30 | [ 31 | ['/test/', ['param', '[^/]+']], 32 | ] 33 | ], 34 | [ 35 | '/te{ param }st', 36 | [ 37 | ['/te', ['param', '[^/]+'], 'st'] 38 | ] 39 | ], 40 | [ 41 | '/test/{param1}/test2/{param2}', 42 | [ 43 | ['/test/', ['param1', '[^/]+'], '/test2/', ['param2', '[^/]+']] 44 | ] 45 | ], 46 | [ 47 | '/test/{param:\d+}', 48 | [ 49 | ['/test/', ['param', '\d+']] 50 | ] 51 | ], 52 | [ 53 | '/test/{ param : \d{1,9} }', 54 | [ 55 | ['/test/', ['param', '\d{1,9}']] 56 | ] 57 | ], 58 | [ 59 | '/test[opt]', 60 | [ 61 | ['/test'], 62 | ['/testopt'], 63 | ] 64 | ], 65 | [ 66 | '/test[/{param}]', 67 | [ 68 | ['/test'], 69 | ['/test/', ['param', '[^/]+']], 70 | ] 71 | ], 72 | [ 73 | '/{param}[opt]', 74 | [ 75 | ['/', ['param', '[^/]+']], 76 | ['/', ['param', '[^/]+'], 'opt'] 77 | ] 78 | ], 79 | [ 80 | '/test[/{name}[/{id:[0-9]+}]]', 81 | [ 82 | ['/test'], 83 | ['/test/', ['name', '[^/]+']], 84 | ['/test/', ['name', '[^/]+'], '/', ['id', '[0-9]+']], 85 | ] 86 | ], 87 | ]; 88 | } 89 | 90 | public function provideTestParseError() { 91 | return [ 92 | [ 93 | '/test[opt', 94 | "Number of opening '[' and closing ']' does not match" 95 | ], 96 | [ 97 | '/test[opt[opt2]', 98 | "Number of opening '[' and closing ']' does not match" 99 | ], 100 | [ 101 | '/testopt]', 102 | "Number of opening '[' and closing ']' does not match" 103 | ], 104 | [ 105 | '/test[]', 106 | "Empty optional part" 107 | ], 108 | [ 109 | '/test[[opt]]', 110 | "Empty optional part" 111 | ], 112 | ]; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /lib/fast-route/vendor/nikic/fast-route/test/bootstrap.php: -------------------------------------------------------------------------------- 1 | =self::$log_level ) 24 | echo '['.posix_getpid().'.'.date("Y-m-d H:i:s").']'.sprintf('%-9s ', "[$log_level_str[$level]]").$msg."\n"; 25 | } 26 | } 27 | 28 | function error_handler($errno, $message, $file, $line){ 29 | $error=array( 30 | 1 => 'Error', 31 | 2 => 'Warning', 32 | 4 => 'Parse', 33 | 8 => 'Notice', 34 | 16 => 'Core Error', 35 | 32 => 'Core Warning', 36 | ); 37 | echo "MYPHP {$error[$errno]}: {$message} in {$file} on line {$line}\n"; 38 | } 39 | -------------------------------------------------------------------------------- /lib/mysql.php: -------------------------------------------------------------------------------- 1 | 'localhost', //mysql主机 9 | 'port' => 3306, //mysql端口 10 | 'user' => 'user', //mysql用户,必须参数 11 | 'password' => 'password', //mysql密码,必须参数 12 | 'database' => 'database', //数据库名称,必须参数 13 | 'persistent' => false, //MySQL长连接 14 | 'charset' => 'utf8', //连接数据库字符集 15 | 'sqls' => 'set wait_timeout=24*60*60*31;set wait_timeout=24*60*60*31' 16 | //连接数据库后需要执行的SQL语句,以';'分隔的多条语句 17 | ) 18 | */ 19 | class mysqldb extends mysqli 20 | { 21 | public $conn = null; 22 | public $config; 23 | private $tabname; 24 | private $wherestr; 25 | 26 | public function __construct($config) 27 | { 28 | if ( !isset($config['host']) ) $config['host'] = 'localhost'; 29 | if ( !isset($config['port']) ) $config['port'] = 3306; 30 | if ( !isset($config['socket']) ) $config['sock'] = '/tmp/mysql.sock'; 31 | $this->config = $config; 32 | if ( isset($config['persistent'])?$config['persistent']:false ) 33 | { 34 | $this->config['host'] = 'p:'.$this->config['host']; 35 | //$host:Prepending host by p: opens a persistent connection,'p:172.16.18.114' 36 | //must is mysqli->close()后持久连接才会被保持 37 | } 38 | } 39 | 40 | public function __get($tabname) { 41 | $this->tabname = $tabname; 42 | return $this; 43 | } 44 | 45 | public function connect($host = NULL, $user = NULL, $password = NULL, $database = NULL, $port = NULL, $socket = NULL) 46 | { 47 | $config = $this->config; 48 | if ( $config['host'] === 'localhost' ) { 49 | @parent::connect($config['host'], $config['user'], $config['password'], $config['database'], 0, $config['socket']); 50 | } else { 51 | @parent::connect($config['host'], $config['user'], $config['password'], $config['database'], $config['port']); 52 | } 53 | if( $this->connect_errno ){ 54 | Log::prn_log(ERROR, "database connect failed: ".$this->connect_error."!"); 55 | return false; 56 | } 57 | Log::prn_log(INFO, "database connect ok ({$config['host']},{$config['port']})!"); 58 | if ( isset($config['charset']) ) { 59 | Log::prn_log(INFO, "set charset names {$config['charset']}"); 60 | $this->query("set names {$config['charset']}"); 61 | } 62 | if ( isset($config['sqls']) ) { 63 | $sqls = explode(";", $config['sqls']); 64 | foreach($sqls as $sql) 65 | { 66 | Log::prn_log(INFO, "$sql"); 67 | $this->query($sql); 68 | } 69 | } 70 | 71 | return true; 72 | } 73 | 74 | /** 75 | * 执行一个SQL语句 76 | * @param string $sql 执行的SQL语句 77 | * @return result(object) | false 78 | */ 79 | public function query($sql) 80 | { 81 | $result = false; 82 | for ($i = 0; $i < 2; $i++) 83 | { 84 | $result = @parent::query($sql); 85 | if ($result === false) 86 | { 87 | if ($this->errno == 2013 or $this->errno == 2006) 88 | { 89 | Log::prn_log(ERROR, "[{$this->errno}]{$this->error}, reconnect ..."); 90 | $r = $this->checkConnection(); 91 | if ($r === true) continue; 92 | } 93 | else 94 | { 95 | return false; 96 | } 97 | } 98 | break; 99 | } 100 | if ($result === false) 101 | { 102 | Log::prn_log(ERROR, "mysql connect lost, again still failed, {$this->errno}, {$this->error}"); 103 | return false; 104 | } 105 | 106 | return $result; 107 | } 108 | 109 | /** 110 | * 检查数据库连接,是否有效,无效则重新建立 111 | */ 112 | protected function checkConnection() 113 | { 114 | if (!@$this->ping()) 115 | { 116 | $this->close(); 117 | return $this->connect(); 118 | } 119 | return true; 120 | } 121 | 122 | /** 123 | * 查询唯一记录 124 | * @param string $sql 执行的SQL语句 125 | * @flag bool 查询不到或查询到多条是否打印ERROR log 126 | * @return row(array) | false 127 | */ 128 | public function select_one($sqlstr,$flag=true){ 129 | if ( !($result = $this->query($sqlstr)) ) { 130 | Log::prn_log(ERROR, "select_one,($sqlstr) error,$this->errno,$this->error!"); 131 | return false; 132 | } 133 | if ( $result->num_rows == 0 ) { 134 | if ($flag) Log::prn_log(ERROR, "select_one,($sqlstr) not found!"); 135 | return false; 136 | } else if ( $result->num_rows > 1 ) { 137 | if ($flag) Log::prn_log(ERROR, "select_one ($sqlstr) mulit found!"); 138 | return false; 139 | } 140 | $row = $result->fetch_assoc(); 141 | Log::prn_log(INFO, 'select_one ok:'.json_encode($row)); 142 | return $row; 143 | } 144 | 145 | /** 146 | * 查询多条记录 147 | * @param string $sql 执行的SQL语句 148 | * @return result(array) | false 149 | */ 150 | public function select_more($sqlstr){ 151 | if ( !($result = $this->query($sqlstr)) ) { 152 | Log::prn_log(ERROR, "select_more,($sqlstr) error,$this->errno,$this->error!"); 153 | return false; 154 | } 155 | for ($res = array(); $tmp = $result->fetch_assoc();) $res[] = $tmp; 156 | 157 | return $res; 158 | } 159 | 160 | /** 161 | * 插入单条记录 162 | * @param array $data 插入数据字段数组['id' => 123, 'name' => 'jfy'] 163 | * @param bool $flag 是否打印成功跟踪,默认为true 164 | * @return id | false 成功返回自增字段ID,失败返回false 165 | * @see mysql->tabname->insert(['id' => 123, 'name' => 'jfy']); 166 | */ 167 | public function insert($data,$flag=true){ 168 | $sql = "insert into $this->tabname ("; 169 | $i = 0; 170 | foreach ($data as $field => $val) { 171 | $sql .= $i==0?$field:','.$field; 172 | $i++; 173 | } 174 | $sql .= ") values ("; 175 | $i = 0; 176 | foreach ($data as $field => $val) { 177 | $val = addslashes($val); 178 | $sql .= $i==0?"'$val'":",'$val'"; 179 | $i++; 180 | } 181 | $sql .= ")"; 182 | 183 | if ( $this->insert_one($sql, $flag) === false ) return false; 184 | return ['id' => $this->insert_id]; 185 | 186 | return true; 187 | } 188 | 189 | /** 190 | * 插入单条记录 191 | * @param string $sql 执行的SQL语句 192 | * @return true | false 193 | * $this->insert_id 为自增字段ID 194 | */ 195 | public function insert_one($sqlstr,$flag=true){ 196 | if ( !($result = $this->query($sqlstr)) ) { 197 | Log::prn_log(ERROR, "insert_one,($sqlstr) error,{$this->errno},{$this->error}!"); 198 | return false; 199 | } 200 | if ($flag) Log::prn_log(INFO, 'insert_one ok: '.$sqlstr); 201 | 202 | return true; 203 | } 204 | 205 | /** 206 | * 更新单条记录 207 | * @param array $data 更新数据字段数组['name' => 'jfy'] 208 | * @param array $cond 更新条件字段数组['id' => 123],顺序与索引顺序相同 209 | * @return true | false 210 | * @see $this->affected_rows 为更新记录数 211 | * @see mysql->tabname->update(['name' => 'jfy'],['id' => 123]); 212 | */ 213 | public function update($data,$cond){ 214 | $sql = "update $this->tabname set "; 215 | $i = 0; 216 | foreach ($data as $field => $val) { 217 | $val = addslashes($val); 218 | $sql .= $i==0?"$field='$val'":",$field='$val'"; 219 | $i++; 220 | } 221 | $sql .= " where "; 222 | $i = 0; 223 | foreach ($cond as $field => $val) { 224 | $val = addslashes($val); 225 | $sql .= $i==0?"$field='$val'":" and $field='$val'"; 226 | $i++; 227 | } 228 | 229 | return $this->update_one($sql); 230 | } 231 | 232 | /** 233 | * 更新单条记录 234 | * @param string $sql 执行的SQL语句 235 | * @return true | false 236 | * @see $this->affected_rows 为更新记录数 237 | */ 238 | public function update_one($sqlstr){ 239 | if ( !($result = $this->query($sqlstr)) ) { 240 | Log::prn_log(ERROR, "update_one,($sqlstr) error,{$this->errno},{$this->error}!"); 241 | return false; 242 | } 243 | $rows = $this->affected_rows; 244 | if ( $rows != 1 ) { 245 | Log::prn_log(ERROR, "update_one ,($sqlstr) affected_rows is $rows!"); 246 | return false; 247 | } 248 | Log::prn_log(INFO, 'update_one ok: '.$sqlstr); 249 | 250 | return true; 251 | } 252 | 253 | /** 254 | * 更新多条记录 255 | * @param string $sql 执行的SQL语句 256 | * @return true | false 257 | * @see $this->affected_rows 为更新记录数 258 | */ 259 | public function update_more($sqlstr){ 260 | if ( !($result = $this->query($sqlstr)) ) { 261 | Log::prn_log(ERROR, "update_more,($sqlstr) error,{$this->errno},{$this->error}!"); 262 | return false; 263 | } 264 | Log::prn_log(INFO, 'update_more ok: '.$sqlstr); 265 | Log::prn_log(INFO, "updated $this->affected_rows row"); 266 | 267 | return true; 268 | } 269 | 270 | /** 271 | * 删除单条记录 272 | * @param array $cond更新条件字段数组['id' => 123],顺序与索引顺序相同 273 | * @return true | false 274 | * @see $this->affected_rows 为删除记录数 275 | * @see mysql->tabname->delete(['id' => 123]); 276 | */ 277 | public function delete($cond){ 278 | $sql = "delete from $this->tabname where "; 279 | $i = 0; 280 | foreach ($cond as $field => $val) { 281 | $val = addslashes($val); 282 | $sql .= $i==0?"$field='$val'":" and $field='$val'"; 283 | $i++; 284 | } 285 | 286 | return $this->delete_one($sql); 287 | } 288 | 289 | /** 290 | * 删除单条记录 291 | * @param string $sql 执行的SQL语句 292 | * @return true | false 293 | * @see $this->affected_rows 为删除记录数 294 | */ 295 | public function delete_one($sqlstr){ 296 | if ( !($result = $this->query($sqlstr)) ) { 297 | Log::prn_log(ERROR, "update_one,($sqlstr) error,{$this->errno},{$this->error}!"); 298 | return false; 299 | } 300 | $rows = $this->affected_rows; 301 | if ( $rows != 1 ) { 302 | Log::prn_log(ERROR, "delete_one ,($sqlstr) affected_rows is $rows!"); 303 | return false; 304 | } 305 | Log::prn_log(INFO, 'delete_one ok: '.$sqlstr); 306 | 307 | return true; 308 | } 309 | 310 | } 311 | 312 | -------------------------------------------------------------------------------- /lib/protocol.php: -------------------------------------------------------------------------------- 1 | config_route = array_merge($config, Route_config::$default_route); 18 | //var_dump($this->config_route); 19 | $this->dispatcher = FastRoute\simpleDispatcher(function(FastRoute\RouteCollector $r) { 20 | foreach($this->config_route as $route){ 21 | log::prn_log(DEBUG, 'addRoute: '. json_encode($route)); 22 | $r->addRoute($route[0], $route[1], [$this, $route[2]]); 23 | } 24 | }); 25 | } 26 | 27 | public function handel_route($method, $uri){ 28 | $route_info = $this->dispatcher->dispatch($method, $uri); 29 | log::prn_log(DEBUG, json_encode($route_info)); 30 | switch ($route_info[0]) { 31 | case FastRoute\Dispatcher::NOT_FOUND: 32 | return 404; 33 | break; 34 | case FastRoute\Dispatcher::METHOD_NOT_ALLOWED: 35 | $allow_methods = $route_info[1]; 36 | return 405; 37 | break; 38 | case FastRoute\Dispatcher::FOUND: 39 | $handler = $route_info[1][1]; 40 | if ( substr($handler,0,9) === '_handler.' ) { 41 | return call_user_func(array($this,substr($handler,9)), $route_info); 42 | } else { 43 | list($ret['class'],$ret['fun']) = explode('.', $handler); 44 | $ret['param'] = $route_info[2]; 45 | return $ret; 46 | } 47 | break; 48 | } 49 | } 50 | 51 | private function controller_action($route_info){ 52 | return [ 53 | 'class' => $route_info[2]['controller'], 54 | 'fun' => $route_info[2]['action'], 55 | ]; 56 | } 57 | 58 | private function controller_action_param($route_info){ 59 | $class = $route_info[2]['controller']; 60 | $fun = $route_info[2]['action']; 61 | unset($route_info[2]['controller']); 62 | unset($route_info[2]['action']); 63 | return [ 64 | 'class' => $class, 65 | 'fun' => $fun, 66 | 'param' => explode('/', $route_info[2]), 67 | ]; 68 | } 69 | 70 | private function controller($route_info){ 71 | return [ 72 | 'class' => $route_info[2]['controller'], 73 | 'fun' => 'index', 74 | ]; 75 | } 76 | 77 | private function controller_param($route_info){ 78 | $class = $route_info[2]['controller']; 79 | unset($route_info[2]['controller']); 80 | return [ 81 | 'class' => $class, 82 | 'fun' => 'index', 83 | 'param' => explode('/', $route_info[2]), 84 | ]; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /lib/swoole.php: -------------------------------------------------------------------------------- 1 | = '5.5') { 27 | cli_set_process_title($title); 28 | } else { 29 | swoole_set_process_name($title); 30 | } 31 | } 32 | 33 | function my_onStart($serv) 34 | { 35 | //替换pid为当前进程的pid,因为daemonize模式父进程会被替换 36 | $this->createPidfile(); 37 | $this->my_set_process_name("{$this->title}: master"); 38 | Log::prn_log(DEBUG,"MasterPid={$serv->master_pid}|Manager_pid={$serv->manager_pid}"); 39 | Log::prn_log(DEBUG,"Server: start.Swoole version is [".SWOOLE_VERSION."]"); 40 | } 41 | 42 | function my_onManagerStart($serv) 43 | { 44 | $this->my_set_process_name("{$this->title}: manager"); 45 | } 46 | 47 | function my_onShutdown($serv) 48 | { 49 | Log::prn_log(NOTICE,"Server: onShutdown"); 50 | if (file_exists($this->pid_file)){ 51 | unlink($this->pid_file); 52 | Log::prn_log(DEBUG, "delete pid file " . $this->pid_file); 53 | } 54 | $this->shutdown = true; 55 | } 56 | 57 | function my_onClose($serv, $fd, $from_id) 58 | { 59 | $this->recv_buf[$fd] = ''; 60 | $this->package_len[$fd] = 0; 61 | 62 | $conninfo=$serv->connection_info($fd); 63 | Log::prn_log(DEBUG, "WorkerClose: client[$fd@{$conninfo['remote_ip']}]!"); 64 | } 65 | 66 | function my_onConnect($serv, $fd, $from_id) 67 | { 68 | $conninfo=$serv->connection_info($fd); 69 | Log::prn_log(DEBUG, "WorkerConnect: client[$fd@{$conninfo['remote_ip']}]!"); 70 | } 71 | 72 | function my_onWorkerStart($serv, $worker_id) 73 | { 74 | $this->reload_set(worker_conf::$config); 75 | Log::prn_log(DEBUG, 'reload ok!'); 76 | 77 | if($worker_id >= $serv->setting['worker_num']) 78 | { 79 | $this->my_set_process_name("{$this->title}: tasker"); 80 | Log::prn_log(DEBUG,"TaskerStart: WorkerId={$serv->worker_id}|WorkerPid={$serv->worker_pid}"); 81 | } 82 | else 83 | { 84 | $this->my_set_process_name("{$this->title}: worker"); 85 | Log::prn_log(DEBUG,"WorkerStart: WorkerId={$serv->worker_id}|WorkerPid={$serv->worker_pid}"); 86 | } 87 | 88 | $this->mysql = new mysqldb($this->config['mysql']); 89 | if ( !$this->mysql->connect() ) { 90 | $this->serv->shutdown(); 91 | } 92 | 93 | if ( $this->protocol === 'http' ) { 94 | $route_conf = isset(worker_conf::$config['route'])?worker_conf::$config['route']:[]; 95 | $this->route = new route($route_conf); 96 | } 97 | 98 | if ( isset($this->on_func['workerstart']) ) call_user_func($this->on_func['workerstart'], $serv, $worker_id); 99 | } 100 | 101 | public function my_onReceive($serv, $fd, $from_id, $data) 102 | { 103 | if ( !isset($this->recv_buf[$fd]) ) $this->recv_buf[$fd] = ''; 104 | if ( !isset($this->package_len[$fd]) ) $this->package_len[$fd] = 0; 105 | $this->recv_buf[$fd] .= $data; 106 | 107 | $parser = $this->protocol.'_protocol'; 108 | 109 | //如果一个请求里包含多个完整的请求包,则循环处理 110 | while(true){ 111 | // 当前包的长度已知 112 | if($this->package_len[$fd]) 113 | { 114 | // 数据不够一个包 115 | if($this->package_len[$fd] > strlen($this->recv_buf[$fd])) break; 116 | } 117 | else 118 | { 119 | // 获得当前包长 120 | $this->package_len[$fd] = $parser::input($this, $fd, $this->recv_buf[$fd]); 121 | // 数据不够,无法获得包长 122 | if($this->package_len[$fd] === 0) break; 123 | elseif($this->package_len[$fd] > 0) 124 | { 125 | // 数据不够一个包 126 | if($this->package_len[$fd] > strlen($this->recv_buf[$fd])) break; 127 | } 128 | // 包错误 129 | else 130 | { 131 | log::prn_log(ERROR, 'error package. package_len'); 132 | $this->recv_buf[$fd] = ''; 133 | $this->package_len[$fd] = 0; 134 | break; 135 | } 136 | } 137 | 138 | // 数据足够一个包长 139 | // 当前包长刚好等于buffer的长度 140 | if(strlen($this->recv_buf[$fd]) === $this->package_len[$fd]) 141 | { 142 | $one_request = $this->recv_buf[$fd]; 143 | $this->recv_buf[$fd] = ''; 144 | } 145 | else 146 | { 147 | // 从缓冲区中获取一个完整的包 148 | $one_request = substr($this->recv_buf[$fd], 0, $this->package_len[$fd]); 149 | // 将当前包从接受缓冲区中去掉 150 | $this->recv_buf[$fd] = substr($this->recv_buf[$fd], $this->package_len[$fd]); 151 | } 152 | // 重置当前包长为0 153 | $this->package_len[$fd] = 0; 154 | $request = $parser::decode($this, $fd, $one_request); 155 | 156 | Log::prn_log(NOTICE, "request:"); 157 | var_dump($request); 158 | 159 | $response = $parser::request($this, $fd, $request); 160 | 161 | Log::prn_log(NOTICE, "response:"); 162 | var_dump($response); 163 | 164 | $serv->send($fd, $parser::encode($this, $fd, $response)); 165 | 166 | if($this->recv_buf[$fd] === '') break; 167 | } 168 | 169 | return; 170 | } 171 | 172 | private function response($response, $status, $result, $header = array()) 173 | { 174 | if ( $status <> 200 ) { 175 | Log::prn_log(ERROR, "$status $result"); 176 | $response->status($status); 177 | $result = json_encode(array('error'=>$result, 'status'=>$status)); 178 | } 179 | 180 | Log::prn_log(NOTICE, "RESPONSE $result"); 181 | 182 | foreach($header as $key => $val) $response->header($key, $val); 183 | $response->end($result); 184 | } 185 | 186 | function my_onRequest(swoole_http_request $request, swoole_http_response $response) 187 | { 188 | //var_dump($request); 189 | 190 | $method = $request->server['request_method']; 191 | $uri = $request->server['request_uri']; 192 | $content = $request->rawContent(); 193 | 194 | Log::prn_log(NOTICE, "REQUEST $method $uri $content"); 195 | 196 | // if ( $request->server['request_method'] <> 'POST' ) { 197 | // return $this->response($response, 405, 'Method Not Allowed, ' . $request->server['request_method']); 198 | // } 199 | // if ( !preg_match('#^/(\w+)/(\w+)$#', $uri, $match) ) { 200 | // return $this->response($response, 404, "'$uri' is not found!"); 201 | // } 202 | // $class = $match[1].'_controller'; 203 | // $fun = $match[2]; 204 | 205 | $route_info = $this->route->handel_route($method, $uri); 206 | if ( $route_info === 405 ) { 207 | return $this->response($response, 405, 'Method Not Allowed, ' . $request->server['request_method']); 208 | } 209 | if ( $route_info === 404 ) { 210 | return $this->response($response, 404, "'$uri' is not found!"); 211 | } 212 | //log::prn_log(DEBUG, json_encode($route_info)); 213 | $class = $route_info['class'].'_controller'; 214 | $fun = $route_info['fun']; 215 | $param = isset($route_info['param'])?$route_info['param']:[]; 216 | 217 | //判断类是否存在 218 | if (! class_exists($class) || !method_exists(($class),($fun))) { 219 | return $this->response($response, 404, " class or fun not found class == $class fun == $fun"); 220 | }; 221 | 222 | if ( $content === false ) $content = ''; 223 | 224 | if ( ($method === 'POST') and ($content === '') ) 225 | { 226 | Log::prn_log(ERROR, $content); 227 | return $this->response($response, 415, 'post content is empty!'); 228 | } 229 | $obj = new $class($this, $request, $param); 230 | return $this->response($response, 200, $obj->$fun(), array('Content-Type' => 'application/json')); 231 | } 232 | 233 | function my_onWorkerStop($serv, $worker_id) 234 | { 235 | Log::prn_log(NOTICE,"WorkerStop: WorkerId={$serv->worker_id}|WorkerPid=".posix_getpid()); 236 | } 237 | 238 | function my_onWorkerError($serv, $worker_id, $worker_pid, $exit_code) 239 | { 240 | Log::prn_log(ERROR,"WorkerError: worker abnormal exit. WorkerId=$worker_id|WorkerPid=$worker_pid|ExitCode=$exit_code"); 241 | } 242 | 243 | private function trans_listener($listen) 244 | { 245 | if(! is_array($listen)) 246 | { 247 | $tmpArr = explode(":", $listen); 248 | $host = isset($tmpArr[1]) ? $tmpArr[0] : '0.0.0.0'; 249 | $port = isset($tmpArr[1]) ? $tmpArr[1] : $tmpArr[0]; 250 | 251 | $this->listen[] = array( 252 | 'host' => $host, 253 | 'port' => $port, 254 | ); 255 | return true; 256 | } 257 | foreach($listen as $v) 258 | { 259 | $this->trans_listener($v); 260 | } 261 | } 262 | 263 | public function __construct() 264 | { 265 | $config = server_conf::$config; 266 | if ( !isset($config['protocol']) ) $config['protocol'] = 'http'; 267 | if ( !isset($config['is_sington']) ) $config['is_sington'] = true; 268 | if ( !isset($config['worker_num']) ) $config['worker_num'] = 6; 269 | if ( !isset($config['daemonize']) ) $config['daemonize'] = true; 270 | $this->config = $config; 271 | 272 | $this->pid_file = self::$info_dir . "swoole_{$config['server_name']}.pid"; 273 | $this->title = 'swoole_'.$config['server_name']; 274 | $this->protocol = $config['protocol']; 275 | 276 | Log::$log_level = $config['log_level']; 277 | 278 | $this->trans_listener($config['listen']); 279 | 280 | $i=0; 281 | foreach($this->listen as $v) { 282 | if ($i==0) { 283 | log::prn_log(INFO, "listen: {$v['host']}:{$v['port']}"); 284 | if ( $this->protocol === 'http' ) { 285 | log::prn_log(NOTICE, "start http server"); 286 | $this->serv = new swoole_http_server($v['host'],$v['port']); 287 | } else { 288 | if(!class_exists($this->protocol.'_protocol')) 289 | { 290 | log::prn_log(ERROR, "protocol class {$this->protocol} not exist!"); 291 | exit; 292 | } 293 | log::prn_log(NOTICE, "start tcp({$this->protocol}) server"); 294 | $this->serv = new swoole_server($v['host'],$v['port']); 295 | } 296 | } else { 297 | log::prn_log(INFO, "listen: {$v['host']}:{$v['port']}"); 298 | $this->serv->addlistener($v['host'],$v['port'],SWOOLE_SOCK_TCP); 299 | } 300 | 301 | $i++; 302 | } 303 | 304 | $this->serv->set($this->config); 305 | $this->serv->on('Start', array($this, 'my_onStart')); 306 | $this->serv->on('Connect', array($this, 'my_onConnect')); 307 | $this->serv->on('Close', array($this, 'my_onClose')); 308 | $this->serv->on('Shutdown', array($this, 'my_onShutdown')); 309 | $this->serv->on('WorkerStart', array($this, 'my_onWorkerStart')); 310 | $this->serv->on('WorkerStop', array($this, 'my_onWorkerStop')); 311 | $this->serv->on('WorkerError', array($this, 'my_onWorkerError')); 312 | $this->serv->on('ManagerStart', array($this, 'my_onManagerStart')); 313 | if ( $this->protocol === 'http' ) { 314 | $this->serv->on('Request', array($this, 'my_onRequest')); 315 | } else { 316 | $this->serv->on('Receive', array($this, 'my_onReceive')); 317 | } 318 | } 319 | 320 | public function on($event, $func) 321 | { 322 | $this->on_func[$event]=$func; 323 | } 324 | 325 | public function reload_set($config){ 326 | $this->config['mysql'] = $config['mysql']; 327 | Log::$log_level = $config['log_level']; 328 | Log::prn_log(DEBUG, 'log_level change to '.Log::$log_level); 329 | } 330 | 331 | //--检测pid是否已经存在 332 | private function checkPidfile(){ 333 | 334 | if (!file_exists($this->pid_file)){ 335 | return true; 336 | } 337 | $pid = file_get_contents($this->pid_file); 338 | $pid = intval($pid); 339 | if ($pid > 0 && posix_kill($pid, 0)){ 340 | Log::prn_log(NOTICE, "the server is already started"); 341 | } 342 | else { 343 | Log::prn_log(WARNING, "the server end abnormally, auto delete pidfile " . $this->pid_file); 344 | unlink($this->pid_file); 345 | return; 346 | } 347 | exit(1); 348 | 349 | } 350 | 351 | //----创建pid 352 | private function createPidfile(){ 353 | if (!is_dir(self::$info_dir)) mkdir(self::$info_dir); 354 | $fp = fopen($this->pid_file, 'w') or die("cannot create pid file"); 355 | fwrite($fp, posix_getpid()); 356 | fclose($fp); 357 | Log::prn_log(DEBUG, "create pid file " . $this->pid_file); 358 | } 359 | 360 | public function start() 361 | { 362 | // 只能单例运行 363 | if ($this->config['is_sington']==true){ 364 | $this->checkPidfile(); 365 | } 366 | $this->createPidfile(); 367 | 368 | $this->serv->start(); 369 | if ( !$this->shutdown) log::prn_log(ERROR, "swoole start error: ".swoole_errno().','.swoole_strerror(swoole_errno())); 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | App Server Framework(ASF) 2 | ========================== 3 | 4 | **简介** 5 | -------- 6 | 7 | - 框架基于PHP-Swoole扩展开发,通过配置文件可以自定义各种应用协议,默认支持http协议。 8 | - 框架本身是一个完整的tcp_server,不再需要apache,nginx,fpm这些,框架已包含log处理,mysql访问封装。 9 | - 框架用fast-route库来做http route处理,直接映射到控制器上,使用者只要写具体的控制器方法就可以实现rest风格的API。 10 | - 至于性能,可以很低调的说:相当高,具体可以参考swoole相关文档: 11 | http://www.swoole.com/ 12 | 13 | **安装运行** 14 | ----------- 15 | 环境:linux2.6+、php5.5+、mysql5.5+、swoole1.7.20+ 16 | 下载:https://github.com/xtjsxtj/asf 17 | ``` 18 | tar zxvf asf.tar.gz 19 | cd asf 20 | php ./bin/asf.php test_http start 21 | 22 | 也可以直接进入具体server目录直接运行入口脚本文件: 23 | cd asf/apps/test_http 24 | php ./index.php 25 | 26 | 查看当前server进程状态: 27 | php asf/bin/asf.php test_http status 28 | 29 | 查看所有server运行状态: 30 | php asf/bin/asf.php list 31 | ``` 32 | 33 | **目录结构** 34 | ----------- 35 | ``` 36 | ASF 37 | ├── apps #示例server或实际应用server(实际应用不限制一定放在该目录) 38 | │ ├── test_http #具体应用server示例,http_server 39 | │ │ ├── config #应用配置文件目录 40 | │ │ │ ├── server_conf.php #server控制进程配置文件 41 | │ │ │ └── worker_conf.php #worker处理进程配置文件 42 | │ │ ├── controller #应用控制器目录 43 | │ │ │ ├── base_controller.php #业务相关控制器的基类 44 | │ │ │ └── index_controller.php #业务相关具体控制器类 45 | │ │ └── index.php #应用入口主文件(不限于该名称),可以单独调用,可以通过bin/asf.php说统一调用 46 | │ │ 47 | │ └── test_tcp #具体应用server示例,http_server 48 | │ ├── config #应用配置文件目录 49 | │ │ ├── server_conf.php #server控制进程配置文件 50 | │ │ └── worker_conf.php #worker处理进程配置文件 51 | │ ├── controller #应用控制器目录 52 | │ │ ├── base_controller.php #业务相关控制器的基类 53 | │ │ └── index_controller.php #业务相关具体控制器类 54 | │ ├── protocol #TCP自定义协议目录 55 | │ │ └── voip_protocol.php #VOIP协议自定义解析类 56 | │ └── index.php #应用入口主文件(不限于该名称),可以单独调用,可以通过bin/asf.php说统一调用 57 | ├── bin 58 | │ ├── asf.php #多server起动状态监控shell脚本 59 | │ └── asf.ini #多server列表配置文件 60 | └── lib #ASF底层代码 61 | ├── fast-route #fast-route库目录 62 | ├── autoload.php #自动加载脚本 63 | ├── config.php #route配置脚本 64 | ├── controller.php #控制器父类 65 | ├── protocol.php #tcp协议解析interface 66 | ├── log.php #日志类 67 | ├── mysql.php #mysql访问类 68 | ├── route.php #http route解析类 69 | └── swoole.php #swoole扩展底层类 70 | ``` 71 | 72 | **http_server开发** 73 | ------------------ 74 | 当protocol为http(不设置则默认为http),server运行为http_server,这种模式下默认不需要做任何额外的配置,系统会按默认的路由规则分发到具体的控制器中处理,开发者只需要写具体的控制器和方法就可以。 75 | 76 | 下面是http_server,test_http的开发流程: 77 | 78 | * server配置文件:apps/test_http/config/server_conf.php 79 | ```php 80 | 'test_http', //server名称 85 | 'log_level' => NOTICE, //跟踪级别 86 | 'listen' => 9501, //listen监听端口 87 | 'log_file' => '/asf/apps/test_http/index.log', //log文件 88 | ); 89 | } 90 | ``` 91 | 92 | * worker配置文件:apps/test_http/config/worker_conf.php 93 | ```php 94 | DEBUG, 99 | 'mysql' => array( 100 | 'socket' => '/tmp/mysql.sock', 101 | 'host' => 'localhost', 102 | 'port' => 3306, 103 | 'user' => 'user', 104 | 'password' => 'password', 105 | 'database' => 'test', 106 | 'charset' => 'utf8', 107 | ), 108 | } 109 | ``` 110 | 111 | * 唯一主入口脚本:apps/test_http/index.php 112 | ```php 113 | 114 | define('BASE_PATH', __DIR__); 115 | require_once BASE_PATH.'/../../lib/autoload.php'; 116 | require_once BASE_PATH.'/config/server_conf.php'; 117 | 118 | $server = new swoole(); 119 | $server->start(); 120 | ``` 121 | 122 | * 控制器:apps/test_http/controller/index_controller.php 123 | 124 | ```php 125 | content)); 130 | log::prn_log(DEBUG, json_encode($this->param)); 131 | 132 | return 'ok'; 133 | } 134 | } 135 | 136 | ``` 137 | index_controller基于父类base_controller实现,而base_controller必须基于lib/controller.php的controller实现。 138 | 139 | * 在这种默认的配置下:访问 140 | http://localhost:9501/index/index 路由将会执行上面index_controller控制器中的index方法,http调用返回的结果是:ok 141 | 142 | 143 | **tcp_server开发** 144 | ----------------- 145 | 当protocol不为http,server就运行为tcp_server,这种模式下需要开发者自行处理TCP协议包的解析。 146 | 开发者可以自定义protocol名称,同时必须自行实现该协议的解析类。 147 | 148 | 下面是tcp_server,test_tcp的开发流程: 149 | 150 | * server配置文件:apps/test_tcp/config/server_conf.php 151 | ```php 152 | 'test_tcp', //server名称 157 | 'protocol' => 'voip', //自定义协议名称 158 | 'log_level' => NOTICE, //跟踪级别 159 | 'listen' => 9511, //listen监听端口 160 | 'log_file' => '/asf/apps/test_tcp/index.log', //log文件 161 | ); 162 | } 163 | ``` 164 | 165 | * worker配置文件:apps/test_tcp/config/worker_conf.php 166 | ```php 167 | DEBUG, 172 | 'mysql' => array( 173 | 'socket' => '/tmp/mysql.sock', 174 | 'host' => 'localhost', 175 | 'port' => 3306, 176 | 'user' => 'user', 177 | 'password' => 'password', 178 | 'database' => 'test', 179 | 'charset' => 'utf8', 180 | ), 181 | } 182 | ``` 183 | 184 | * 唯一主入口文件:apps/test_tcp/index.php 185 | 186 | ```php 187 | 188 | 189 | define('BASE_PATH', __DIR__); 190 | 191 | require_once BASE_PATH.'/../../lib/autoload.php'; 192 | require_once BASE_PATH.'/config/server_conf.php'; 193 | 194 | $server = new swoole(server_conf::$config); 195 | $server->start(); 196 | ``` 197 | 198 | * 自定义voip协议解析类 199 | apps/test_tcp/protocol/voip_protocol.php 200 | 201 | ```php 202 | mysql; 215 | $obj = new index_controller($serv, $data); 216 | return $obj->index(); 217 | } 218 | 219 | public static function encode($serv, $fd, $data){ 220 | return $data; 221 | } 222 | } 223 | ``` 224 | 类名和文件名为server_conf中的{protocol}_protocol,并且必须基于lib/protocol.php的interface实现。 225 | 226 | 227 | **配置文件详解** 228 | --------------- 229 | * ASF框架程序配置分为两部分,一个是系统进程的配置server_conf,不能动态修改。 230 | * 另一个是工作进程的配置worker_conf,可以动态修改,修改后通过asf.php server_name reload生效。 231 | * 下面分开详细介绍。 232 | 233 | **server_conf.php配置文件详解** 234 | ----------------------------- 235 | 236 | * 示例 237 | 238 | ```php 239 | $config=array( 240 | 'server_name' => 'test_http', 241 | 'protocol' => 'http', 242 | 'log_level' => NOTICE, 243 | 'is_sington' => true, 244 | 'listen' => ['0.0.0.0:9501', '172.16.18.116:9502'], 245 | 'worker_num' => 1, 246 | 'daemonize' => true, 247 | 'log_file' => '/asf/apps/test_http/index.log', 248 | ); 249 | ``` 250 | 251 | * server_name 252 | sever名称,必须配置,当起动多个server时,保证每个server_name的唯一。 253 | 254 | * protocol 255 | tcp_server协议类型,必须配置,支持http和各种其它自定义协议,如:voip。 256 | 如果是http以外的协议名称,则开发必须基于lib/protocol.php interface实现自定义协议解析类。 257 | 258 | * log_level 259 | 跟踪级别,必须配置,TRACE,DEBUG,INFO,NOTICE,WARNING,ERROR。 260 | 261 | * is_sington 262 | 是否单例运行,可选配置,默认为true,即单实例运行。 263 | 假如server_name为test_http,那么server启动后默认会在/var/local目录下生成一个唯一的pid文件:swoole_test_http.pid。 264 | 265 | * listen 266 | 监听端口,必须配置,可以是如下几种配置方式: 267 | ```php 268 | 'listen' => 9501 269 | 'listen' => [9501,9502] 270 | 'listen' => ['0.0.0.0:9501', '172.16.18.116:9502'] 271 | ``` 272 | 273 | * worker_num 274 | 工作进程数量,可选配置,默认为6个工作进程 275 | 276 | * daemonize 277 | 是否以守护进程方式运行,可选配置,默认为true,即以后台方式运行。 278 | 279 | * log_file 280 | 跟踪文件,必须配置。 281 | 282 | * 最简配置 283 | 284 | ```php 285 | $config=array( 286 | 'server_name' => 'test_http', //server_name 287 | 'log_level' => NOTICE, //log_level 288 | 'listen' => 9501, //listen port 289 | 'log_file' => '/asf/apps/test_http/index.log', //logfile 290 | ); 291 | ``` 292 | 293 | 294 | **worker_conf.php配置文件详解** 295 | ----------------------------- 296 | 297 | * 示例 298 | 299 | ```php 300 | $config=array( 301 | 'log_level' => DEBUG, 302 | 'mysql' => array( 303 | 'socket' => '/tmp/mysql.sock', 304 | 'host' => 'localhost', 305 | 'port' => 3306, 306 | 'user' => 'user', 307 | 'password' => 'password', 308 | 'database' => 'test', 309 | 'charset' => 'utf8', 310 | ), 311 | 'route' => [ 312 | ['PUT', '/user/{number}/{id:\d+}', 'index.index'], 313 | ['GET', '/{controller}/{number}/{id:\d+}', '_handler.controller_param'], 314 | ['GET', '/{controller}/{number}', '_handler.controller_param'], 315 | ['POST', '/{controller}/{number}/{id:\d+}', '_handler.controller_param'], 316 | ['DELETE', '/{controller}/{number}/{id:\d+}', '_handler.controller_param'], 317 | ], 318 | ); 319 | ``` 320 | 321 | 该配置文件分成三个部分。 322 | * log_level 323 | 工作进程的跟踪级别,取值同server_conf中相同,reload后重新生效。 324 | 325 | * myql 326 | 数据库连接属性配置 327 | ```php 328 | 'socket' => '/tmp/mysql.sock',//sock连接 329 | 'host' => 'localhost', //mysql主机 330 | 'port' => 3306, //mysql端口 331 | 'user' => 'user', //mysql用户,必须参数 332 | 'password' => 'password', //mysql密码,必须参数 333 | 'database' => 'database', //数据库名称,必须参数 334 | 'charset' => 'utf8', //连接数据库字符集 335 | 'sqls' => 'set wait_timeout=24*60*60*31;set wait_timeout=24*60*60*31' 336 | //连接数据库后需要执行的SQL语句,以';'分隔的多条语句 337 | ``` 338 | 339 | * route 340 | 该配置项只在protocol=http时生效,其它自定义协议名称该配置项无效。 341 | 底层根据这里配置的跌幅规则,将http不同的uri请求分配给相应的控制器处理。 342 | 具体规则在下面http_server节中详细说明。 343 | 344 | * 最简配置 345 | 346 | ```php 347 | $config=array( 348 | 'log_level' => DEBUG, 349 | 'mysql' => array( 350 | 'socket' => '/tmp/mysql.sock', 351 | 'host' => 'localhost', 352 | 'port' => 3306, 353 | 'user' => 'user', 354 | 'password' => 'password', 355 | 'database' => 'test', 356 | 'charset' => 'utf8', 357 | ), 358 | ); 359 | ``` 360 | 361 | **http_server的route规则配置** 362 | ----------------------------------- 363 | * 上面的http_server开发流程中,uri请求是按系统默认的路由分发到控制器去执行的。 364 | * 其实ASF在http_server模式下,还可以按开发者的具体业务要求自定义路由规则到具体的控制器中处理。 365 | * 自定义路由规则在worker_conf的route段下设置,下面我们详细介绍下http_server的route规则配置。 366 | * 路由配置示例 367 | 368 | ```php 369 | $config=array( 370 | 'route' => [ 371 | ['PUT', '/user/{number}/{id:\d+}', 'index.index'], 372 | ['GET', '/{controller}/{number}/{id:\d+}', '_handler.controller_param'], 373 | ['GET', '/{controller}/{number}', '_handler.controller_param'], 374 | ['POST', '/{controller}/{number}/{id:\d+}', '_handler.controller_param'], 375 | ['DELETE', '/{controller}/{number}/{id:\d+}', '_handler.controller_param'], 376 | ], 377 | ); 378 | ``` 379 | 380 | * 配置格式 381 | 每一条配置规则为一数组类型: 382 | ``` 383 | ['Method', 'Route_reg', 'Controller.Action'] 384 | 385 | Method POST,GET,PUT,DELETE等,支持['POST','GET']方式 386 | Route_reg 路由规则的正则表达式,用以匹配uri路径 387 | Controller.Action 分配到的处理请求的控制器.方法 388 | ``` 389 | 390 | * 控制器.方法 391 | 底层提供一个默认的控制器_handler,该控制器提供四个方法: 392 | * controller_action 解析'/index/index' uri到相应的控制器的相应方法 393 | * controller 解析'/index' uri到相应的控制器的默认index方法 394 | * controller_param 解析'/index/name/id' uri到相应的控制器的默认index方法,同时附加param参数 395 | * controller_action_param 解析'/index/index/name/id' uri到相应的控制器的相应方法,同时附加param参数 396 | 397 | 398 | * 用户自定义规则示例 399 | ```php 400 | ['PUT', '/user/number/{id:\d+}', 'index.index'], 401 | ['PUT', '/user/{number}/{id:\d+}', 'index.index'], 402 | ``` 403 | 404 | 第1条规则可以匹配以下规则: 405 | PUT http://localhost/user/number,分配到index控制器的index方法,同时控制器的$this->param参数中保存着:{"id":"123"} 406 | 407 | 第2条规则可以匹配以下规则: 408 | PUT http://localhost/user/number,分配到index控制器的index方法,同时控制器的$this->param参数中保存着:{"number":"number","id":"123"} 409 | 410 | * 默认规则 411 | 底层会在用户自定义的路由规则后面增加三条默认的规则: 412 | lib/config.php 413 | 414 | ```php 415 | param参数中保存着:['test', 'name', 'id'] 447 | 448 | * 规则匹配顺序 449 | 系统按照先自定义规则,再默认规则的顺序执行,从上往下依次匹配,匹配到了就返回。 450 | 因此,定义规则时应该保证,具体的匹配规则在上面,通用的在下面。 451 | 452 | **tcp server 协议解析interface** 453 | ------------------------------- 454 | * lib/protocol.php 455 | 456 | ```php 457 | server = $server; 542 | $this->mysql = $server->mysql; 543 | $this->request = $request; 544 | $this->param = $param; 545 | 546 | if (method_exists($this, '_init')) $this->_init(); 547 | } 548 | 549 | public function __destruct() { 550 | if (method_exists($this, '_deinit')) $this->_deinit (); 551 | } 552 | } 553 | ``` 554 | 555 | * 应用server目录结构中的controller下的控制均继承自该底层父类。 556 | 557 | 558 | **系统起动脚本使用** 559 | ------------------ 560 | 561 | * asf.ini 文件说明 562 | 563 | ```ini 564 | [servers] 565 | test_http = /home/jfy/testprog/asf/apps/test_http/index.php 566 | test_tcp = /home/jfy/testprog/asf/apps/test_tcp/index.php 567 | ``` 568 | 每一行格式为: server_name = path/index.php 569 | 这里的server_name必须与每个应用中的server_conf配置文件中的server_name相同。 570 | 571 | * asf.php 脚本使用 572 | ``` 573 | php asf.php list 574 | ``` 575 | 列出asf.ini文件中配置的所有的server_name对应的服务的运行状态 576 | 577 | ``` 578 | php asf.php server_name start|stop|reload|restart|status|init 579 | ``` 580 | reload: 平时重启工作进程,并重新加载worker_conf配置文件内容 581 | restart: 重启整个server 582 | status: 显示server进程状态 583 | init: 根据test_http框架复制一份进应用目录框架 584 | 585 | server_name必须是asf.ini文件中已经定义好的。 586 | 587 | 588 | **log类使用方法** 589 | ---------------- 590 | 591 | * log类以静态类方式使用。 592 | 593 | * 设置跟踪级别: 594 | log::log_level = NOTICE; 595 | 支持:TRACE,DEBUG,INFO,NOTICE,WARNING,ERROR 596 | 597 | * 打印跟踪 598 | log::prn_log(NOTICE, "message"); 599 | 支持:TRACE,DEBUG,INFO,NOTICE,WARNING,ERROR 600 | 601 | **mysql类使用方法** 602 | ------------------ 603 | * 直接在控制器用$this->mysql来操作mysql数据库。 604 | * select_one($sqlstr,$flag=true) 605 | 606 | ``` 607 | /** 608 | * 查询唯一记录 609 | * @param string $sql 执行的SQL语句 610 | * @flag bool 查询不到或查询到多条是否打印ERROR log 611 | * @return row(array) | false 612 | */ 613 | ``` 614 | 615 | * select_more($sqlstr) 616 | 617 | ``` 618 | /** 619 | * 查询多条记录 620 | * @param string $sql 执行的SQL语句 621 | * @return result(array) | false 622 | */ 623 | ``` 624 | 625 | * insert($data,$flag=true) 626 | 627 | ``` 628 | /** 629 | * 插入单条记录 630 | * @param array $data 插入数据字段数组['id' => 123, 'name' => 'jfy'] 631 | * @param bool $flag 是否打印成功跟踪,默认为true 632 | * @return id | false 成功返回自增字段ID,失败返回false 633 | * @see mysql->tabname->insert(['id' => 123, 'name' => 'jfy']); 634 | */ 635 | ``` 636 | 637 | * insert_one($sqlstr,$flag=true) 638 | 639 | ``` 640 | /** 641 | * 插入单条记录 642 | * @param string $sql 执行的SQL语句 643 | * @return true | false 644 | * $this->insert_id 为自增字段ID 645 | */ 646 | ``` 647 | 648 | * update($data,$cond) 649 | 650 | ``` 651 | /** 652 | * 更新单条记录 653 | * @param array $data 更新数据字段数组['name' => 'jfy'] 654 | * @param array $cond 更新条件字段数组['id' => 123],顺序与索引顺序相同 655 | * @return true | false 656 | * @see $this->affected_rows 为更新记录数 657 | * @see mysql->tabname->update(['name' => 'jfy'],['id' => 123]); 658 | */ 659 | ``` 660 | 661 | * update_one($sqlstr) 662 | 663 | ``` 664 | /** 665 | * 更新单条记录 666 | * @param string $sql 执行的SQL语句 667 | * @return true | false 668 | * @see $this->affected_rows 为更新记录数 669 | */ 670 | ``` 671 | 672 | * update_more($sqlstr) 673 | 674 | ``` 675 | /** 676 | * 更新多条记录 677 | * @param string $sql 执行的SQL语句 678 | * @return true | false 679 | * @see $this->affected_rows 为更新记录数 680 | */ 681 | ``` 682 | 683 | * delete($cond) 684 | 685 | ``` 686 | /** 687 | * 删除单条记录 688 | * @param array $cond更新条件字段数组['id' => 123],顺序与索引顺序相同 689 | * @return true | false 690 | * @see $this->affected_rows 为删除记录数 691 | * @see mysql->tabname->delete(['id' => 123]); 692 | */ 693 | ``` 694 | 695 | * delete_one($sqlstr) 696 | 697 | ``` 698 | /** 699 | * 删除单条记录 700 | * @param string $sql 执行的SQL语句 701 | * @return true | false 702 | * @see $this->affected_rows 为删除记录数 703 | */ 704 | ``` 705 | 706 | **控制器操作mysql** 707 | ------------------ 708 | apps/test_http/controller/index_controller.php 709 | ```php 710 | content)); 715 | log::prn_log(DEBUG, json_encode($this->param)); 716 | 717 | $db = $this->mysql; 718 | 719 | $result = $db->gearman_queue->insert([ 720 | 'unique_key' => 'd847233c-1ef2-11e5-9130-2c44fd7aee72', 721 | 'function_name' => 'test', 722 | 'priority' => 1, 723 | 'data' => 'test', 724 | 'when_to_run' => 0, 725 | ]); 726 | if ( $result === false ) return 'error'; 727 | 728 | $result = $db->select_one("select * from gearman_queue where unique_key='d847233c-1ef2-11e5-9130-2c44fd7aee72'"); 729 | if ( $result === false ) return 'error'; 730 | var_dump($result); 731 | 732 | $result = $db->gearman_queue->update([ 733 | 'function_name' => 'testtest', 734 | 'priority' => 100, 735 | 'data' => 'testtesttesttest', 736 | 'when_to_run' => 100, 737 | ],[ 738 | 'unique_key' => 'd847233c-1ef2-11e5-9130-2c44fd7aee72', 739 | ]); 740 | if ( $result === false ) return 'error'; 741 | var_dump($db->select_one("select * from gearman_queue where unique_key='d847233c-1ef2-11e5-9130-2c44fd7aee72'")); 742 | 743 | $result = $db->gearman_queue->delete([ 744 | 'unique_key' => 'd847233c-1ef2-11e5-9130-2c44fd7aee72', 745 | ]); 746 | if ( $result === false ) return 'error'; 747 | 748 | $result = $db->select_more("select * from gearman_queue limti 3"); 749 | if ( $result === false ) return 'error'; 750 | var_dump($result); 751 | 752 | return 'ok'; 753 | } 754 | } 755 | ``` 756 | --------------------------------------------------------------------------------