├── .gitignore ├── MIT-LICENSE.txt ├── README.md ├── composer.json ├── example ├── demo.php ├── demo2.php ├── demo3.php ├── demo4.php ├── demo5.php ├── demo6.php ├── demo7.php ├── file.txt ├── hello.html ├── info.html ├── public │ ├── css │ │ └── default.css │ ├── favicon.ico │ └── images │ │ └── 20264902.jpg ├── table.html └── template │ └── index.html └── src ├── All.php ├── Any.php ├── Application.php ├── Async.php ├── AsyncTask.php ├── CallCC.php ├── Context.php ├── Error.php ├── Gen.php ├── HttpException.php ├── Middleware.php ├── NotFound.php ├── Request.php ├── Response.php ├── Router.php ├── Syscall.php ├── Template.php ├── Timeout.php ├── functions.php └── template ├── 404.html ├── 500.html └── error.html /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009-2015 naka507<171453643@qq.com> and contributors (see https://github.com/naka1205/phpkoa/graphs/contributors) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHPKoa 2 | ================= 3 | [![Latest Stable Version](https://poser.pugx.org/naka1205/phpkoa/version)](https://packagist.org/packages/naka1205/phpkoa) 4 | [![Total Downloads](https://poser.pugx.org/naka1205/phpkoa/downloads)](https://packagist.org/packages/naka1205/phpkoa) 5 | [![License](https://poser.pugx.org/naka1205/phpkoa/license)](https://packagist.org/packages/naka1205/phpkoa) 6 | 7 | PHP异步编程: 8 | 基于 `PHP` 实(chao)现(xi) `NODEJS` web框架 [KOA](https://github.com/koajs/koa) 9 | 10 | 说明 11 | ======= 12 | 偶然间在 `GITHUB` 上看到有赞官方仓库的 [手把手教你实现co与Koa](https://github.com/youzan/php-co-koa) 。由于此前用过 `KOA` ,对于 `KOA` 的洋葱模型叹为观止。不由得心血来潮的看完了整个文档,接着 `CTRL+C`、`CTRL+V` 让代码跑了起来。 13 | 文档中是基于 `swoole` 扩展进行开发,而 `swoole` 对 `WINDOWS` 并不友好,向来习惯在 `WINDOWS` 下开发的我一鼓作气,将[Workerman](https://github.com/walkor/Workerman) 改写并兼容了此项目。 14 | 15 | 体验 16 | ======= 17 | 1. [PHPKoa Demo](https://github.com/naka1205/phpkoa_demo) 是使用 `PHPKoa` 开发 `HTTP SERVER` 的一个简单示例! 18 | 2. [PHP Krpano](https://github.com/naka1205/phpkrpano) PHP 全景图片生成! 19 | 3. [PKBook](https://github.com/naka1205/pkbook) 静态博客发布程序! 20 | 4. [H5Make](https://github.com/naka1205/H5Make) H5编辑器! 21 | 22 | 建议 23 | ======= 24 | 欢迎各位大神提交[issues](https://github.com/naka1205/phpkoa/issues/1) 25 | 26 | 安装 27 | ======= 28 | ``` 29 | composer require naka1205/phpkoa 30 | ``` 31 | 32 | 使用 33 | ======= 34 | 35 | ### Hello World 36 | ```php 37 | υse(function(Context $ctx) { 46 | $ctx->status = 200; 47 | $ctx->body = "

Hello World

"; 48 | }); 49 | 50 | $app->listen(3000,function(){ 51 | echo "PHPKoa is listening in 3000\n"; 52 | }); 53 | 54 | ``` 55 | ### Error 56 | ```php 57 | υse(new Error()); 70 | $app->υse(new NotFound()); 71 | $app->υse(new Timeout(3)); //设置3秒超时 72 | 73 | $router = new Router(); 74 | 75 | //正常访问 76 | $router->get('/hello', function(Context $ctx, $next) { 77 | $ctx->status = 200; 78 | $ctx->body = "

Hello World

"; 79 | }); 80 | 81 | //访问超时 82 | $router->get('/timeout', function(Context $ctx, $next) { 83 | yield async_sleep(5); 84 | }); 85 | 86 | //访问出错 87 | $router->get('/error', function(Context $ctx, $next) { 88 | $ctx->thrοw(500, "Internal Error"); 89 | yield; 90 | }); 91 | 92 | $app->υse($router->routes()); 93 | 94 | $app->listen(3000); 95 | 96 | ``` 97 | ### Router 98 | ```php 99 | υse(new Error()); 112 | $app->υse(new Timeout(5)); 113 | 114 | $router = new Router(); 115 | $router->get('/demo1', function(Context $ctx, $next) { 116 | $ctx->body = "demo1"; 117 | }); 118 | $router->get('/demo2', function(Context $ctx, $next) { 119 | $ctx->body = "demo2"; 120 | }); 121 | $router->get('/demo3/(\d+)', function(Context $ctx, $next, $vars) { 122 | $ctx->status = 200; 123 | $ctx->body = "demo3={$vars[0]}"; 124 | }); 125 | $router->get('/demo4', function(Context $ctx, $next) { 126 | $ctx->redirect("/demo2"); 127 | }); 128 | 129 | //RESTful API 130 | $router->post('/demo3/(\d+)', function(Context $ctx, $next, $vars) { 131 | //设置 session 132 | $ctx->setSession('demo3',$vars[0]); 133 | //设置 cookie 134 | $ctx->setCookie('demo3',$vars[0]); 135 | $ctx->status = 200; 136 | $ctx->body = "post:demo3={$vars[0]}"; 137 | }); 138 | $router->put('/demo3/(\d+)', function(Context $ctx, $next, $vars) { 139 | 140 | //获取单个 cookie 141 | $cookie_demo3 = $ctx->getCookie('demo3'); 142 | //或者 143 | $cookies = $ctx->cookies['demo3']; 144 | 145 | //获取单个 session 146 | $session_demo3 = $ctx->getSession('demo3'); 147 | //或者 148 | $session = $ctx->session['demo3']; 149 | 150 | $ctx->status = 200; 151 | $ctx->body = "put:demo3={$vars[0]}"; 152 | }); 153 | $router->delete('/demo3/(\d+)', function(Context $ctx, $next, $vars) { 154 | //清除所有 cookie 155 | $ctx->clearCookie(); 156 | //清除所有 session 157 | $ctx->clearSession(); 158 | $ctx->status = 200; 159 | $ctx->body = "delete:demo3={$vars[0]}"; 160 | }); 161 | 162 | //文件上传 163 | $router->post('/files/(\d+)', function(Context $ctx, $next, $vars) { 164 | $upload_path = __DIR__ . DS . "uploads" . DS; 165 | if ( !is_dir($upload_path) ) { 166 | mkdir ($upload_path , 0777, true); 167 | } 168 | $files = []; 169 | foreach ( $ctx->request->files as $key => $value) { 170 | if ( !$value['file_name'] || !$value['file_data'] ) { 171 | continue; 172 | } 173 | $file_path = $upload_path . $value['file_name']; 174 | file_put_contents($file_path, $value['file_data']); 175 | $value['file_path'] = $file_path; 176 | $files[] = $value; 177 | } 178 | 179 | $ctx->status = 200; 180 | $ctx->body = json_encode($files); 181 | }); 182 | 183 | 184 | $app->υse($router->routes()); 185 | 186 | $app->listen(3000); 187 | 188 | ``` 189 | 190 | ```php 191 | mount('/curl', function() use ($router) { 205 | $client = new Client(); 206 | $router->get('/get', function( Context $ctx, $next ) use ($client) { 207 | $r = (yield $client->request('GET', 'http://127.0.0.1:3000/demo3/1')); 208 | $ctx->status = $r->getStatusCode(); 209 | $ctx->body = $r->getBody(); 210 | }); 211 | 212 | $router->get('/post', function(Context $ctx, $next ) use ($client){ 213 | $r = (yield $client->request('POST', 'http://127.0.0.1:3000/demo3/2')); 214 | $ctx->status = $r->getStatusCode(); 215 | $ctx->body = $r->getBody(); 216 | }); 217 | 218 | $router->get('/put', function( Context $ctx, $next ) use ($client){ 219 | $r = (yield $client->request('PUT', 'http://127.0.0.1:3000/demo3/3')); 220 | $ctx->status = $r->getStatusCode(); 221 | $ctx->body = $r->getBody(); 222 | }); 223 | 224 | $router->get('/delete', function( Context $ctx, $next ) use ($client){ 225 | $r = (yield $client->request('DELETE', 'http://127.0.0.1:3000/demo3/4')); 226 | $ctx->status = $r->getStatusCode(); 227 | $ctx->body = $r->getBody(); 228 | }); 229 | }); 230 | 231 | //http://127.0.0.1:5000/files 232 | $router->get('/files', function(Context $ctx, $next ) { 233 | $client = new Client(); 234 | $r = ( yield $client->request('POST', 'http://127.0.0.1:3000/files/2', [ 235 | 'multipart' => [ 236 | [ 237 | 'name' => 'file_name', 238 | 'contents' => fopen( __DIR__ . '/file.txt', 'r') 239 | ], 240 | [ 241 | 'name' => 'other_file', 242 | 'contents' => 'hello', 243 | 'filename' => 'filename.txt', 244 | 'headers' => [ 245 | 'X-Foo' => 'this is an extra header to include' 246 | ] 247 | ] 248 | ] 249 | ])); 250 | 251 | $ctx->status = $r->getStatusCode(); 252 | $ctx->body = $r->getBody(); 253 | }); 254 | 255 | // $router->get('/curl/(\w+)', function(Context $ctx, $next, $vars) { 256 | // $method = strtoupper($vars[0]); 257 | // $client = new Client(); 258 | // $r = (yield $client->request($method, 'http://127.0.0.1:3000/demo3/123')); 259 | // $ctx->status = $r->getStatusCode(); 260 | // $ctx->body = $r->getBody(); 261 | // }); 262 | 263 | $app->υse($router->routes()); 264 | $app->listen(5000); 265 | ``` 266 | 267 | ### Template 268 | 269 | ```html 270 | 271 |

{title}

272 |

{time}

273 | 274 | ``` 275 | ```php 276 | υse(new Error()); 287 | $app->υse(new Timeout(5)); 288 | 289 | $router = new Router(); 290 | $router->get('/hello', function(Context $ctx) { 291 | $ctx->status = 200; 292 | $ctx->state["title"] = "HELLO WORLD"; 293 | $ctx->state["time"] = date("Y-m-d H:i:s", time());; 294 | yield $ctx->render(__DIR__ . "/hello.html"); 295 | }); 296 | $app->υse($router->routes()); 297 | 298 | $app->listen(3000); 299 | 300 | ``` 301 | ```html 302 | 303 |

{title}

304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 |
NameAge
{name} {age}
315 | 316 | ``` 317 | ```php 318 | get('/info', function(Context $ctx) { 323 | $info = array("name" => "小明", "age" => 15); 324 | $ctx->status = 200; 325 | $ctx->state["title"] = "这是一个学生信息"; 326 | $ctx->state["info"] = $info; 327 | yield $ctx->render(__DIR__ . "/info.html"); 328 | }); 329 | ``` 330 | ```html 331 | 332 |

{title}

333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 |
NameAge
{name} {age}
344 | 345 | ``` 346 | ```php 347 | get('/table', function(Context $ctx) { 352 | $table = array( 353 | array("name" => "小明", "age" => 15), 354 | array("name" => "小花", "age" => 13), 355 | array("name" => "小刚", "age" => 17) 356 | ); 357 | $ctx->status = 200; 358 | $ctx->state["title"] = "这是一个学生名单"; 359 | $ctx->state["table"] = $table; 360 | yield $ctx->render(__DIR__ . "/table.html"); 361 | }); 362 | ``` 363 | 364 | 中间件 365 | ======= 366 | 静态文件处理 中间件 [PHPKoa Static](https://github.com/naka1205/phpkoa_static) 367 | 368 | ```html 369 | 370 | 371 | 372 | 373 | PHPkoa Static 374 | 375 | 376 | 377 | 378 | 379 | 380 | ``` 381 | ```php 382 | υse(new Error()); 398 | $app->υse(new Timeout(5)); 399 | $app->υse(new NotFound()); 400 | $app->υse(new StaticFiles(__DIR__ . DS . "static" )); 401 | 402 | $router = new Router(); 403 | 404 | $router->get('/index', function(Context $ctx, $next) { 405 | $ctx->status = 200; 406 | yield $ctx->render(__DIR__ . "/index.html"); 407 | }); 408 | 409 | $app->υse($router->routes()); 410 | 411 | $app->listen(3000); 412 | ``` 413 | 使用 第三方ORM 414 | ``` 415 | composer require topthink/think-orm 416 | ``` 417 | 创建 MYSQL 数据表 418 | ```mysql 419 | CREATE TABLE `too_user` ( 420 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户ID', 421 | `account` varchar(64) NOT NULL DEFAULT '' COMMENT '账号', 422 | `password` char(32) NOT NULL DEFAULT '' COMMENT '登录密码', 423 | `nickname` varchar(32) NOT NULL DEFAULT '' COMMENT '用户昵称', 424 | `status` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '状态', 425 | `create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间', 426 | `update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间', 427 | PRIMARY KEY (`id`) 428 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 429 | ``` 430 | 自定义数据模型 431 | ```php 432 | accept,'json'); 453 | if ( $pos !== false ) { 454 | $ctx->type = 'application/json'; 455 | $result = [ "code" => 0, "msg" => '操作失败']; 456 | $data = $ctx->body; 457 | if ( $data ) { 458 | $result['code'] = 200; 459 | $result['msg'] = '操作成功'; 460 | $result['data'] = $data; 461 | } 462 | $ctx->body = json_encode( $result ); 463 | } 464 | 465 | } 466 | } 467 | ``` 468 | 动态模板 使用JQ 进行AJAX 请求 469 | ```html 470 | 471 |

/api/user/{id}

472 |

473 | 474 | 490 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "naka1205/phpkoa", 3 | "description": "php koa", 4 | "type": "library", 5 | "authors": [ 6 | { 7 | "name": "naka507", 8 | "email": "171453643@qq.com" 9 | } 10 | ], 11 | "license": "MIT", 12 | "require": { 13 | "php": ">=5.4", 14 | "naka1205/phpsocket": "^1.9", 15 | "naka1205/phpkoa_static": "^1.2" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "Naka507\\Koa\\": "src/" 20 | }, 21 | "files": ["src/functions.php"] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/demo.php: -------------------------------------------------------------------------------- 1 | υse(function(Context $ctx) { 10 | $ctx->status = 200; 11 | $ctx->body = "

Hello World

"; 12 | }); 13 | 14 | $app->listen(3000,function(){ 15 | echo "PHPKoa is listening in 3000\n"; 16 | }); -------------------------------------------------------------------------------- /example/demo2.php: -------------------------------------------------------------------------------- 1 | υse(new Error()); 14 | $app->υse(new NotFound()); 15 | $app->υse(new Timeout(3)); //设置3秒超时 16 | 17 | $router = new Router(); 18 | 19 | $router->get('/hello', function(Context $ctx, $next) { 20 | $ctx->status = 200; 21 | $ctx->body = "

Hello World

"; 22 | }); 23 | 24 | //访问超时 25 | $router->get('/timeout', function(Context $ctx, $next) { 26 | yield async_sleep(5); 27 | }); 28 | 29 | //访问出错 30 | $router->get('/error', function(Context $ctx, $next) { 31 | $ctx->thrοw(500, "Internal Error"); 32 | yield; 33 | }); 34 | 35 | $app->υse($router->routes()); 36 | 37 | $app->listen(3000); -------------------------------------------------------------------------------- /example/demo3.php: -------------------------------------------------------------------------------- 1 | υse(new Error()); 15 | $app->υse(new Timeout(5)); 16 | $app->υse(new NotFound()); 17 | 18 | $router = new Router(); 19 | 20 | $router->get('/demo1', function(Context $ctx, $next) { 21 | $ctx->status = 200; 22 | $ctx->body = "demo1"; 23 | }); 24 | $router->get('/demo2', function(Context $ctx, $next) { 25 | $ctx->status = 200; 26 | $ctx->body = "demo2"; 27 | }); 28 | $router->get('/demo3/(\d+)', function(Context $ctx, $next, $vars) { 29 | $ctx->status = 200; 30 | $ctx->body = "demo3={$vars[0]}"; 31 | }); 32 | $router->get('/demo4', function(Context $ctx, $next) { 33 | $ctx->redirect("/demo2"); 34 | }); 35 | 36 | //RESTful API 37 | $router->post('/demo3/(\d+)', function(Context $ctx, $next, $vars) { 38 | $ctx->status = 200; 39 | $ctx->body = "post:demo3={$vars[0]}"; 40 | }); 41 | $router->put('/demo3/(\d+)', function(Context $ctx, $next, $vars) { 42 | $ctx->status = 200; 43 | $ctx->body = "put:demo3={$vars[0]}"; 44 | }); 45 | $router->delete('/demo3/(\d+)', function(Context $ctx, $next, $vars) { 46 | $ctx->status = 200; 47 | $ctx->body = "delete:demo3={$vars[0]}"; 48 | }); 49 | 50 | //文件上传 51 | $router->post('/files/(\d+)', function(Context $ctx, $next, $vars) { 52 | $upload_path = __DIR__ . DS . "uploads" . DS; 53 | if ( !is_dir($upload_path) ) { 54 | mkdir ($upload_path , 0777, true); 55 | } 56 | $files = []; 57 | foreach ( $ctx->request->files as $key => $value) { 58 | if ( !$value['file_name'] || !$value['file_data'] ) { 59 | continue; 60 | } 61 | $file_path = $upload_path . $value['file_name']; 62 | file_put_contents($file_path, $value['file_data']); 63 | $value['file_path'] = $file_path; 64 | $files[] = $value; 65 | } 66 | 67 | $ctx->status = 200; 68 | $ctx->body = json_encode($files); 69 | }); 70 | $app->υse($router->routes()); 71 | 72 | $app->listen(3000); -------------------------------------------------------------------------------- /example/demo4.php: -------------------------------------------------------------------------------- 1 | υse(new Error()); 12 | $app->υse(new Timeout(5)); 13 | 14 | $router = new Router(); 15 | $router->get('/hello', function(Context $ctx) { 16 | $ctx->status = 200; 17 | $ctx->state["title"] = "HELLO WORLD"; 18 | $ctx->state["time"] = date("Y-m-d H:i:s", time());; 19 | yield $ctx->render(__DIR__ . "/hello.html"); 20 | }); 21 | 22 | //一维数组 23 | $router->get('/info', function(Context $ctx) { 24 | $info = array("name" => "小明", "age" => 15); 25 | $ctx->status = 200; 26 | $ctx->state["title"] = "这是一个学生信息"; 27 | $ctx->state["info"] = $info; 28 | yield $ctx->render(__DIR__ . "/info.html"); 29 | }); 30 | //二维数组 31 | $router->get('/table', function(Context $ctx) { 32 | $table = array( 33 | array("name" => "小明", "age" => 15), 34 | array("name" => "小花", "age" => 13), 35 | array("name" => "小刚", "age" => 17) 36 | ); 37 | $ctx->status = 200; 38 | $ctx->state["title"] = "这是多个学生信息"; 39 | $ctx->state["table"] = $table; 40 | yield $ctx->render(__DIR__ . "/table.html"); 41 | }); 42 | 43 | $app->υse($router->routes()); 44 | 45 | $app->listen(3000); -------------------------------------------------------------------------------- /example/demo5.php: -------------------------------------------------------------------------------- 1 | υse(new Error()); 14 | $app->υse(new Timeout(10)); 15 | 16 | $router = new Router(); 17 | 18 | //路由分组 19 | //http://127.0.0.1:5000/curl/get 20 | //http://127.0.0.1:5000/curl/post 21 | //http://127.0.0.1:5000/curl/put 22 | //http://127.0.0.1:5000/curl/delete 23 | $router->mount('/curl', function() use ($router) { 24 | $client = new Client(); 25 | $router->get('/get', function( Context $ctx, $next ) use ($client) { 26 | $client = new Client(); 27 | $r = (yield $client->request('GET', 'http://127.0.0.1:3000/demo3/1')); 28 | $ctx->status = $r->getStatusCode(); 29 | $ctx->body = $r->getBody(); 30 | }); 31 | 32 | $router->get('/post', function(Context $ctx, $next ) use ($client){ 33 | $r = (yield $client->request('POST', 'http://127.0.0.1:3000/demo3/2')); 34 | $ctx->status = $r->getStatusCode(); 35 | $ctx->body = $r->getBody(); 36 | }); 37 | 38 | $router->get('/put', function( Context $ctx, $next ) use ($client){ 39 | $r = (yield $client->request('PUT', 'http://127.0.0.1:3000/demo3/3')); 40 | $ctx->status = $r->getStatusCode(); 41 | $ctx->body = $r->getBody(); 42 | }); 43 | 44 | $router->get('/delete', function( Context $ctx, $next ) use ($client){ 45 | $r = (yield $client->request('DELETE', 'http://127.0.0.1:3000/demo3/4')); 46 | $ctx->status = $r->getStatusCode(); 47 | $ctx->body = $r->getBody(); 48 | }); 49 | }); 50 | 51 | //http://127.0.0.1:5000/files 52 | $router->get('/files', function(Context $ctx, $next ) { 53 | $client = new Client(); 54 | $r = ( yield $client->request('POST', 'http://127.0.0.1:3000/files/2', [ 55 | 'multipart' => [ 56 | [ 57 | 'name' => 'file_name', 58 | 'contents' => fopen( __DIR__ . '/file.txt', 'r') 59 | ], 60 | [ 61 | 'name' => 'other_file', 62 | 'contents' => 'hello', 63 | 'filename' => 'filename.txt', 64 | 'headers' => [ 65 | 'X-Foo' => 'this is an extra header to include' 66 | ] 67 | ] 68 | ] 69 | ])); 70 | 71 | $ctx->status = $r->getStatusCode(); 72 | $ctx->body = $r->getBody(); 73 | }); 74 | 75 | // $router->get('/curl/(\w+)', function(Context $ctx, $next, $vars) { 76 | // $method = strtoupper($vars[0]); 77 | // $client = new Client(); 78 | // $r = (yield $client->request($method, 'http://127.0.0.1:3000/demo3/123')); 79 | // $ctx->status = $r->getStatusCode(); 80 | // $ctx->body = $r->getBody(); 81 | // }); 82 | 83 | $app->υse($router->routes()); 84 | 85 | $app->listen(5000); -------------------------------------------------------------------------------- /example/demo6.php: -------------------------------------------------------------------------------- 1 | υse(new Error()); 16 | $app->υse(new Timeout(5)); 17 | $app->υse(new NotFound()); 18 | 19 | $public_path = __DIR__ . DS . "public" ; 20 | $app->υse(new StaticFiles( $public_path )); 21 | 22 | $router = new Router(); 23 | 24 | $router->get('/index', function(Context $ctx, $next) { 25 | $ctx->status = 200; 26 | yield $ctx->render(__DIR__ . "/template/index.html"); 27 | }); 28 | 29 | //文件上传 30 | $router->post('/files', function(Context $ctx, $next) use ($public_path) { 31 | $upload_path = "uploads"; 32 | $save_path = $public_path . DS . $upload_path . DS; 33 | 34 | if ( !is_dir($save_path) ) { 35 | mkdir ($save_path , 0777, true); 36 | } 37 | $files = []; 38 | foreach ( $ctx->request->files as $key => $value) { 39 | if ( !$value['file_name'] || !$value['file_data'] ) { 40 | continue; 41 | } 42 | $ext = explode(".",$value['file_name'])[1]; 43 | $file_name = time() . "." . $ext; 44 | $file_path = $save_path . $file_name; 45 | file_put_contents($file_path, $value['file_data']); 46 | $value['file_path'] = "/" . $upload_path . "/" . $file_name ; 47 | unset($value['file_data']); 48 | $files[] = $value; 49 | } 50 | $ctx->status = 200; 51 | $ctx->body = json_encode($files); 52 | }); 53 | 54 | $app->υse($router->routes()); 55 | 56 | $app->listen(3000); -------------------------------------------------------------------------------- /example/demo7.php: -------------------------------------------------------------------------------- 1 | υse(new Error()); 16 | $app->υse(new Timeout(5)); 17 | $app->υse(new NotFound()); 18 | 19 | $public_path = __DIR__ . DS . "public" ; 20 | $app->υse(new StaticFiles( $public_path )); 21 | 22 | $router = new Router(); 23 | 24 | //http://127.0.0.1:3000/get/cookies 25 | //http://127.0.0.1:3000/get/session 26 | $router->get('/get/(\w+)', function(Context $ctx, $next, $vars) { 27 | $name = ''; 28 | if ( $vars[0] == 'session' ) { 29 | $name = $ctx->getSession('name1'); 30 | }else{ 31 | $name = $ctx->getCookie('name1'); 32 | } 33 | $ctx->status = 200; 34 | $ctx->body = $name ? $name : ''; 35 | }); 36 | //http://127.0.0.1:3000/set/1234 37 | $router->get('/set/(\d+)', function(Context $ctx, $next, $vars) { 38 | $ctx->setSession('name1',$vars[0]); 39 | $ctx->setCookie('name1',$vars[0]); 40 | $ctx->status = 200; 41 | $ctx->body = $vars[0]; 42 | }); 43 | //http://127.0.0.1:3000/clear/cookies 44 | //http://127.0.0.1:3000/clear/session 45 | $router->get('/clear/(\w+)', function(Context $ctx, $next, $vars) { 46 | if ( $vars[0] == 'cookies' ) { 47 | $ctx->clearCookie(); 48 | }else{ 49 | $ctx->clearSession(); 50 | } 51 | $ctx->status = 200; 52 | $ctx->body = 'OK'; 53 | }); 54 | //http://127.0.0.1:3000/session 55 | $router->get('/session', function(Context $ctx, $next) { 56 | $session = $ctx->session; 57 | $ctx->status = 200; 58 | $ctx->body = json_encode($session); 59 | }); 60 | //http://127.0.0.1:3000/cookies 61 | $router->get('/cookies', function(Context $ctx, $next) { 62 | $cookies = $ctx->cookies; 63 | $ctx->status = 200; 64 | $ctx->body = json_encode($cookies); 65 | }); 66 | 67 | $app->υse($router->routes()); 68 | 69 | $app->listen(3000); -------------------------------------------------------------------------------- /example/file.txt: -------------------------------------------------------------------------------- 1 | upload file -------------------------------------------------------------------------------- /example/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Index 8 | 9 | 10 |

{title}

11 |

{time}

12 | 13 | -------------------------------------------------------------------------------- /example/info.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Table 8 | 9 | 10 |

{title}

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
NameAge
{name} {age}
22 | 23 | -------------------------------------------------------------------------------- /example/public/css/default.css: -------------------------------------------------------------------------------- 1 | html{ 2 | background: white; 3 | } 4 | 5 | img{ 6 | width: 100px; 7 | height: 100px; 8 | } -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naka1205/phpkoa/2aadefa2996bab1b3a6bd09f5fa645f619a22422/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/images/20264902.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naka1205/phpkoa/2aadefa2996bab1b3a6bd09f5fa645f619a22422/example/public/images/20264902.jpg -------------------------------------------------------------------------------- /example/table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Table 8 | 9 | 10 |

{title}

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
NameAge
{name} {age}
22 | 23 | -------------------------------------------------------------------------------- /example/template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | PHPkoa Static 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /src/All.php: -------------------------------------------------------------------------------- 1 | tasks = $tasks; 13 | $this->parent = $parent; 14 | $this->n = count($tasks); 15 | $this->results = []; 16 | $this->done = false; 17 | } 18 | public function begin(callable $continuation) { 19 | $this->continuation = $continuation; 20 | foreach ($this->tasks as $id => $task) { 21 | (new AsyncTask($task, $this->parent))->begin($this->continuation($id)); 22 | }; 23 | } 24 | private function continuation($id) { 25 | return function($r, $ex = null) use($id) { 26 | if ($this->done) { 27 | return; 28 | } 29 | if($ex){ 30 | $this->done = true; 31 | $k = $this->continuation; 32 | $k(null,$ex); 33 | return; 34 | } 35 | $this->results[$id] = $r; 36 | if(--$this->n === 0){ 37 | $this->done = true; 38 | $k = $this->continuation; 39 | $k($this->results); 40 | } 41 | }; 42 | } 43 | } -------------------------------------------------------------------------------- /src/Any.php: -------------------------------------------------------------------------------- 1 | tasks = $tasks; 14 | $this->parent = $parent; 15 | $this->done = false; 16 | } 17 | 18 | public function begin(callable $continuation) 19 | { 20 | $this->continuation = $continuation; 21 | foreach ($this->tasks as $id => $task) { 22 | (new AsyncTask($task, $this->parent))->begin($this->continuation($id)); 23 | }; 24 | } 25 | 26 | private function continuation($id) 27 | { 28 | return function($r, $ex = null) use($id) { 29 | if ($this->done) { 30 | return; 31 | } 32 | $this->done = true; 33 | 34 | if ($this->continuation) { 35 | $k = $this->continuation; 36 | $k($r, $ex); 37 | } 38 | }; 39 | } 40 | } -------------------------------------------------------------------------------- /src/Application.php: -------------------------------------------------------------------------------- 1 | context = new Context(); 15 | $this->context->app = $this; 16 | } 17 | 18 | public function υse(callable $fn) 19 | { 20 | $this->middleware[] = $fn; 21 | return $this; 22 | } 23 | 24 | public function listen($port = 8000,callable $fn = null) 25 | { 26 | $this->fn = compose($this->middleware); 27 | $this->httpServer = new Server($port); 28 | 29 | if ( $fn != null ) { 30 | $this->httpServer->onWorkerStart = $fn; 31 | } 32 | 33 | $this->httpServer->onConnect = [$this,'onConnect']; 34 | $this->httpServer->onMessage = [$this,'onRequest']; 35 | $this->httpServer->start(); 36 | 37 | } 38 | 39 | public function onConnect(){ 40 | 41 | } 42 | 43 | public function onRequest( $req, $res) 44 | { 45 | $ctx = $this->createContext($req, $res); 46 | $reqHandler = $this->makeRequest($ctx); 47 | $resHandler = $this->makeResponse($ctx); 48 | spawn($reqHandler, $resHandler); 49 | } 50 | 51 | protected function makeRequest(Context $ctx) 52 | { 53 | return function() use($ctx) { 54 | yield setCtx("ctx", $ctx); 55 | $ctx->res->status(404); 56 | $fn = $this->fn; 57 | yield $fn($ctx); 58 | }; 59 | } 60 | 61 | protected function makeResponse(Context $ctx) 62 | { 63 | return function($r = null, \Exception $ex = null) use($ctx) { 64 | if ($ex) { 65 | $this->error($ctx, $ex); 66 | } else { 67 | $this->respond($ctx); 68 | } 69 | }; 70 | } 71 | 72 | protected function error(Context $ctx, \Exception $ex = null) 73 | { 74 | if ($ex === null) { 75 | return; 76 | } 77 | 78 | if ($ex && $ex->getCode() !== 404) { 79 | 80 | } 81 | 82 | $msg = $ex->getCode(); 83 | if ($ex instanceof HttpException) { 84 | $status = $ex->status ?: 500; 85 | $ctx->res->status($status); 86 | $msg = $ex->getMessage(); 87 | 88 | } else { 89 | $ctx->res->status(500); 90 | } 91 | 92 | $ctx->res->header("Content-Type", "text"); 93 | $ctx->res->write($msg); 94 | $ctx->res->end(); 95 | } 96 | 97 | protected function respond(Context $ctx) 98 | { 99 | if ($ctx->respond === false) return; 100 | 101 | $body = $ctx->body; 102 | $code = $ctx->status; 103 | 104 | if ($code !== null) { 105 | $ctx->res->status($code); 106 | } 107 | 108 | if ($body !== null) { 109 | $ctx->res->write($body); 110 | } 111 | 112 | $ctx->res->end(); 113 | } 114 | 115 | protected function createContext( $req, $res) 116 | { 117 | $context = clone $this->context; 118 | 119 | $request = $context->request = new Request($this, $context, $req, $res); 120 | $response = $context->response = new Response($this, $context, $req, $res); 121 | 122 | $context->app = $this; 123 | $context->req = $req; 124 | $context->res = $res; 125 | 126 | $request->response = $response; 127 | $response->request = $request; 128 | 129 | $request->originalUrl = $req->server["request_uri"]; 130 | $request->ip = $req->server["remote_addr"]; 131 | 132 | return $context; 133 | } 134 | } -------------------------------------------------------------------------------- /src/Async.php: -------------------------------------------------------------------------------- 1 | gen = new Gen($gen); 12 | $this->parent = $parent; 13 | } 14 | 15 | public function begin(callable $continuation) 16 | { 17 | $this->continuation = $continuation; 18 | $this->next(); 19 | } 20 | 21 | public function next($result = null, $ex = null) 22 | { 23 | try { 24 | if ($ex) { 25 | $value = $this->gen->throws($ex); 26 | } else { 27 | $value = $this->gen->send($result); 28 | } 29 | 30 | if ($this->gen->valid()) { 31 | if ($value instanceof Syscall) { 32 | $value = $value($this); 33 | } 34 | 35 | if ($value instanceof \Generator) { 36 | $value = new self($value, $this); 37 | } 38 | 39 | if ($value instanceof Async) { 40 | $cc = [$this, "next"]; 41 | $value->begin($cc); 42 | } else { 43 | $this->next($value, null); 44 | } 45 | } else { 46 | $cc = $this->continuation; 47 | $cc($result, null); 48 | } 49 | } catch (\Exception $ex) { 50 | if ($this->gen->valid()) { 51 | $this->next(null, $ex); 52 | } else { 53 | $cc = $this->continuation; 54 | $cc($result, $ex); 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/CallCC.php: -------------------------------------------------------------------------------- 1 | fun = $fun; 10 | } 11 | 12 | public function begin(callable $continuation) 13 | { 14 | $fun = $this->fun; 15 | $fun($continuation); 16 | } 17 | } -------------------------------------------------------------------------------- /src/Context.php: -------------------------------------------------------------------------------- 1 | funcs)){ 20 | return $this->funcs[$name][1](...$arguments); 21 | } 22 | $fn = [$this->response, $name]; 23 | return $fn(...$arguments); 24 | } 25 | 26 | public function __get($name) 27 | { 28 | return $this->request->$name; 29 | } 30 | 31 | public function __set($name, $value) 32 | { 33 | $this->response->$name = $value; 34 | } 35 | 36 | public function add($func, $callback){ 37 | $this->funcs[$func] = array($func, $callback); 38 | } 39 | 40 | public function accept($name) 41 | { 42 | return strpos($this->request->server['http_accept'],$name) === false ? false : true; 43 | } 44 | 45 | public function thrοw($status, $message) 46 | { 47 | if ($message instanceof \Exception) { 48 | $ex = $message; 49 | throw new HttpException($status, $ex->getMessage(), $ex->getCode(), $ex->getPrevious()); 50 | } else { 51 | throw new HttpException($status, $message); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/Error.php: -------------------------------------------------------------------------------- 1 | getCode() ?: 0; 12 | $msg = "Internal Error"; 13 | 14 | if ( $ex instanceof HttpException ) { 15 | $status = $ex->getStatus(); 16 | $msg = $ex->getMessage(); 17 | } 18 | 19 | $err = [ "code" => $code, "msg" => $msg ]; 20 | if ( $ctx->accept("json") ) { 21 | $ctx->status = 200; 22 | $ctx->body = json_encode($err); 23 | } else { 24 | $ctx->status = $status; 25 | if ($status === 404) { 26 | $ctx->body = (yield Template::render(__DIR__ . "/template/404.html")); 27 | } else if ($status === 500) { 28 | $ctx->body = (yield Template::render(__DIR__ . "/template/500.html")); 29 | } else { 30 | $ctx->body = (yield Template::render(__DIR__ . "/template/error.html",$err)); 31 | } 32 | } 33 | 34 | 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/Gen.php: -------------------------------------------------------------------------------- 1 | id = $id; 11 | $this->generator = $generator; 12 | } 13 | 14 | public function throws(\Exception $ex){ 15 | return $this->generator->throw($ex); 16 | } 17 | 18 | public function valid(){ 19 | return $this->generator->valid(); 20 | } 21 | 22 | public function send($value = null){ 23 | 24 | if( $this->isfirst ){ 25 | $this->isfirst = false; 26 | $result = $this->generator->current(); 27 | }else{ 28 | $result = $this->generator->send($value); 29 | } 30 | return $result; 31 | } 32 | } -------------------------------------------------------------------------------- /src/HttpException.php: -------------------------------------------------------------------------------- 1 | status = $status; 11 | $this->headers = $headers; 12 | 13 | parent::__construct($message, $code, $previous); 14 | } 15 | 16 | public function getStatus() 17 | { 18 | return $this->status; 19 | } 20 | 21 | public function getHeaders() 22 | { 23 | return $this->headers; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Middleware.php: -------------------------------------------------------------------------------- 1 | status !== 404 || $ctx->body){ 12 | return; 13 | } 14 | $ctx->status = 404; 15 | $ctx->body = (yield Template::render(__DIR__ . "/template/404.html")); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Request.php: -------------------------------------------------------------------------------- 1 | app = $app; 16 | $this->ctx = $ctx; 17 | $this->req = $req; 18 | $this->res = $res; 19 | } 20 | 21 | public function __get($name) 22 | { 23 | switch ($name) { 24 | case "rawcontent": 25 | return $this->req->rawContent(); 26 | case "post": 27 | return isset($this->req->post) ? $this->req->post : []; 28 | case "get": 29 | return isset($this->req->get) ? $this->req->get : []; 30 | case "cookie": 31 | case "cookies": 32 | return isset($this->req->cookie) ? $this->req->cookie : []; 33 | case "request": 34 | return isset($this->req->request) ? $this->req->request : []; 35 | case "header": 36 | case "headers": 37 | return isset($this->req->header) ? $this->req->header : []; 38 | case "files": 39 | return isset($this->req->files) ? $this->req->files : []; 40 | case "accept": 41 | return $this->req->server['http_accept']; 42 | case "method": 43 | return $this->req->server["request_method"]; 44 | case "url": 45 | case "origin": 46 | return $this->req->server["request_uri"]; 47 | case "path": 48 | return isset($this->req->server["path_info"]) ? $this->req->server["path_info"] : ""; 49 | case "query": 50 | case "querystring": 51 | return isset($this->req->server["query_string"]) ? $this->req->server["query_string"] : ""; 52 | case "host": 53 | case "hostname": 54 | return isset($this->req->header["host"]) ? $this->req->header["host"] : ""; 55 | case "protocol": 56 | return $this->req->server["server_protocol"]; 57 | default: 58 | return $this->req->$name; 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/Response.php: -------------------------------------------------------------------------------- 1 | app = $app; 15 | $this->ctx = $ctx; 16 | $this->req = $req; 17 | $this->res = $res; 18 | } 19 | 20 | public function __call($name, $arguments) 21 | { 22 | $fn = [$this->res, $name]; 23 | return $fn(...$arguments); 24 | } 25 | 26 | public function __get($name) 27 | { 28 | return $this->res->$name; 29 | } 30 | 31 | public function __set($name, $value) 32 | { 33 | switch ($name) { 34 | case "type": 35 | return $this->res->header("Content-Type", $value); 36 | case "lastModified": 37 | return $this->res->header("Last-Modified", $value); 38 | case "etag": 39 | return $this->res->header("ETag", $value); 40 | case "length": 41 | return $this->res->header("Content-Length", $value); 42 | default: 43 | return $this->res->header($name, $value); 44 | } 45 | } 46 | 47 | public function end($html = "") 48 | { 49 | if ($this->isEnd) { 50 | return false; 51 | } 52 | $this->isEnd = true; 53 | return $this->res->end($html); 54 | } 55 | 56 | public function redirect($url, $status = 302) 57 | { 58 | $this->res->header("Location", $url); 59 | $this->res->header("Content-Type", "text/plain; charset=utf-8"); 60 | $this->ctx->status = $status; 61 | $this->ctx->body = "Redirecting to $url."; 62 | } 63 | 64 | public function render($file) 65 | { 66 | $this->ctx->body = ( yield Template::render($file, $this->ctx->state) ); 67 | } 68 | } -------------------------------------------------------------------------------- /src/Router.php: -------------------------------------------------------------------------------- 1 | requestedMethod = $this->getRequestMethod(); 33 | 34 | $handled = 0; 35 | $quitAfterRun = false; 36 | 37 | if (isset($this->beforeRoutes[$this->requestedMethod])) { 38 | $before = $this->handle($this->beforeRoutes[$this->requestedMethod]); 39 | if ($before['handled'] != 0 && is_callable($before['fn'])) { 40 | yield $before['fn']($ctx, $next, $before['vars']); 41 | // 状态码已写入 终止 42 | if ( $ctx->status ) { 43 | return; 44 | } 45 | } 46 | } 47 | 48 | if (isset($this->afterRoutes[$this->requestedMethod])) { 49 | $route = ( yield $this->handle($this->afterRoutes[$this->requestedMethod]) ); 50 | $handled = $route['handled']; 51 | } 52 | switch ($handled) { 53 | case 0: 54 | // 状态码写入Context 55 | $ctx->status = 404; 56 | yield $next; 57 | break; 58 | 59 | default: 60 | $fn = $route['fn']; 61 | $vars = $route['vars']; 62 | // 从路由表提取处理器 63 | yield $fn($ctx, $next, $vars); 64 | break; 65 | } 66 | } 67 | 68 | 69 | public function before($methods, $pattern, $fn) 70 | { 71 | $pattern .= $this->suffix; 72 | $pattern = $this->baseRoute.'/'.trim($pattern, '/'); 73 | $pattern = $this->baseRoute ? rtrim($pattern, '/') : $pattern; 74 | foreach (explode('|', $methods) as $method) { 75 | $this->beforeRoutes[$method][] = array( 76 | 'pattern' => $pattern, 77 | 'fn' => $fn, 78 | ); 79 | } 80 | } 81 | 82 | public function match($methods, $pattern, $fn) 83 | { 84 | $pattern .= $this->suffix; 85 | $pattern = $this->baseRoute.'/'.trim($pattern, '/'); 86 | $pattern = $this->baseRoute ? rtrim($pattern, '/') : $pattern; 87 | foreach (explode('|', $methods) as $method) { 88 | $this->afterRoutes[$method][] = array( 89 | 'pattern' => $pattern, 90 | 'fn' => $fn, 91 | ); 92 | } 93 | } 94 | 95 | public function all($pattern, $fn) 96 | { 97 | $this->match('GET|POST|PUT|DELETE|OPTIONS|PATCH|HEAD', $pattern, $fn); 98 | } 99 | 100 | public function get($pattern, $fn) 101 | { 102 | $this->match('GET', $pattern, $fn); 103 | } 104 | 105 | public function post($pattern, $fn) 106 | { 107 | $this->match('POST', $pattern, $fn); 108 | } 109 | 110 | public function patch($pattern, $fn) 111 | { 112 | $this->match('PATCH', $pattern, $fn); 113 | } 114 | 115 | public function delete($pattern, $fn) 116 | { 117 | $this->match('DELETE', $pattern, $fn); 118 | } 119 | 120 | public function put($pattern, $fn) 121 | { 122 | $this->match('PUT', $pattern, $fn); 123 | } 124 | 125 | public function options($pattern, $fn) 126 | { 127 | $this->match('OPTIONS', $pattern, $fn); 128 | } 129 | 130 | public function mount($baseRoute, $fn) 131 | { 132 | $curBaseRoute = $this->baseRoute; 133 | $this->baseRoute .= $baseRoute; 134 | call_user_func($fn); 135 | $this->baseRoute = $curBaseRoute; 136 | } 137 | 138 | public function suffix($str = '') 139 | { 140 | $this->suffix = $str; 141 | } 142 | 143 | public function getRequestHeaders() 144 | { 145 | $headers = array(); 146 | if (function_exists('getallheaders')) { 147 | $headers = getallheaders(); 148 | if ($headers !== false) { 149 | return $headers; 150 | } 151 | } 152 | foreach ($_SERVER as $name => $value) { 153 | if ((substr($name, 0, 5) == 'HTTP_') || ($name == 'CONTENT_TYPE') || ($name == 'CONTENT_LENGTH')) { 154 | $headers[str_replace(array(' ', 'Http'), array('-', 'HTTP'), ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; 155 | } 156 | } 157 | return $headers; 158 | } 159 | 160 | public function getRequestMethod() 161 | { 162 | $method = $_SERVER['REQUEST_METHOD']; 163 | if ($_SERVER['REQUEST_METHOD'] == 'HEAD') { 164 | ob_start(); 165 | $method = 'GET'; 166 | }elseif ($_SERVER['REQUEST_METHOD'] == 'POST') { 167 | $headers = $this->getRequestHeaders(); 168 | if (isset($headers['X-HTTP-Method-Override']) && in_array($headers['X-HTTP-Method-Override'], array('PUT', 'DELETE', 'PATCH'))) { 169 | $method = $headers['X-HTTP-Method-Override']; 170 | } 171 | } 172 | return $method; 173 | } 174 | 175 | public function notFound($fn) 176 | { 177 | $this->notFoundCallback = $fn; 178 | } 179 | 180 | private function handle($routes) 181 | { 182 | $fn = null; 183 | $vars = null; 184 | $handled = 0; 185 | $uri = $this->getCurrentUri(); 186 | foreach ($routes as $route) { 187 | $route['pattern'] = preg_replace('/{([A-Za-z]*?)}/', '(\w+)', $route['pattern']); 188 | if (preg_match_all('#^'.$route['pattern'].'$#', $uri, $matches, PREG_OFFSET_CAPTURE)) { 189 | $matches = array_slice($matches, 1); 190 | $params = array_map(function ($match, $index) use ($matches) { 191 | if (isset($matches[$index + 1]) && isset($matches[$index + 1][0]) && is_array($matches[$index + 1][0])) { 192 | return trim(substr($match[0][0], 0, $matches[$index + 1][0][1] - $match[0][1]), '/'); 193 | }else { 194 | return isset($match[0][0]) ? trim($match[0][0], '/') : null; 195 | } 196 | }, $matches, array_keys($matches)); 197 | 198 | $fn = $route['fn']; 199 | $vars = $params; 200 | ++$handled; 201 | break; 202 | } 203 | } 204 | return ['handled' => $handled,'fn' => $fn,'vars' => $vars]; 205 | } 206 | 207 | protected function getCurrentUri() 208 | { 209 | $uri = substr($_SERVER['REQUEST_URI'], strlen($this->getBasePath())); 210 | if (strstr($uri, '?')) { 211 | $uri = substr($uri, 0, strpos($uri, '?')); 212 | } 213 | return '/'.trim($uri, '/'); 214 | } 215 | 216 | protected function getBasePath() 217 | { 218 | if ($this->serverBasePath === null) { 219 | $this->serverBasePath = implode('/', array_slice(explode('/', $_SERVER['SCRIPT_NAME']), 0, -1)).'/'; 220 | } 221 | return $this->serverBasePath; 222 | } 223 | } -------------------------------------------------------------------------------- /src/Syscall.php: -------------------------------------------------------------------------------- 1 | fun = $fun; 10 | } 11 | 12 | public function __invoke(AsyncTask $task) 13 | { 14 | $cb = $this->fun; 15 | return $cb($task); 16 | } 17 | } -------------------------------------------------------------------------------- /src/Template.php: -------------------------------------------------------------------------------- 1 | accurate = $accurate; 18 | $this->loadfile(".", $filename); 19 | } 20 | 21 | public function addFile($varname, $filename){ 22 | if(!$this->exists($varname)) throw new \InvalidArgumentException("addFile: var $varname does not exist"); 23 | $this->loadfile($varname, $filename); 24 | } 25 | 26 | public function __set($varname, $value){ 27 | if(!$this->exists($varname)) throw new \RuntimeException("var $varname does not exist"); 28 | $stringValue = $value; 29 | if(is_object($value)){ 30 | $this->instances[$varname] = $value; 31 | if(!isset($this->properties[$varname])) $this->properties[$varname] = array(); 32 | if(method_exists($value, "__toString")) $stringValue = $value->__toString(); 33 | else $stringValue = "Object"; 34 | } 35 | $this->setValue($varname, $stringValue); 36 | return $value; 37 | } 38 | 39 | public function __get($varname){ 40 | if(isset($this->values["{".$varname."}"])) return $this->values["{".$varname."}"]; 41 | elseif(isset($this->instances[$varname])) return $this->instances[$varname]; 42 | throw new \RuntimeException("var $varname does not exist"); 43 | } 44 | 45 | public function exists($varname){ 46 | return in_array($varname, $this->vars); 47 | } 48 | 49 | private function loadfile($varname, $filename) { 50 | if (!file_exists($filename)) throw new \InvalidArgumentException("file $filename does not exist"); 51 | if($this->isPHP($filename)){ 52 | ob_start(); 53 | require $filename; 54 | $str = ob_get_contents(); 55 | ob_end_clean(); 56 | $this->setValue($varname, $str); 57 | } else { 58 | $str = preg_replace("//smi", "", file_get_contents($filename)); 59 | if (empty($str)) throw new \InvalidArgumentException("file $filename is empty"); 60 | $this->setValue($varname, $str); 61 | $blocks = $this->identify($str, $varname); 62 | $this->createBlocks($blocks); 63 | } 64 | } 65 | 66 | private function isPHP($filename){ 67 | foreach(array('.php', '.php5', '.cgi') as $php){ 68 | if(0 == strcasecmp($php, substr($filename, strripos($filename, $php)))) return true; 69 | } 70 | return false; 71 | } 72 | 73 | private function identify(&$content, $varname){ 74 | $blocks = array(); 75 | $queued_blocks = array(); 76 | $this->identifyVars($content); 77 | $lines = explode("\n", $content); 78 | if(1==sizeof($lines)){ 79 | $content = str_replace('-->', "-->\n", $content); 80 | $lines = explode("\n", $content); 81 | } 82 | foreach (explode("\n", $content) as $line) { 83 | if (strpos($line, "/sm"; 90 | preg_match($reg, $line, $m); 91 | if (1==preg_match($reg, $line, $m)){ 92 | if (0==sizeof($queued_blocks)) $parent = $varname; 93 | else $parent = end($queued_blocks); 94 | if (!isset($blocks[$parent])){ 95 | $blocks[$parent] = array(); 96 | } 97 | $blocks[$parent][] = $m[1]; 98 | $queued_blocks[] = $m[1]; 99 | } 100 | $reg = "//sm"; 101 | if (1==preg_match($reg, $line)) array_pop($queued_blocks); 102 | } 103 | 104 | private function identifyVars(&$content){ 105 | $r = preg_match_all("/{(".self::$REG_NAME.")((\-\>(".self::$REG_NAME."))*)?((\|.*?)*)?}/", $content, $m); 106 | if ($r){ 107 | for($i=0; $i<$r; $i++){ 108 | if($m[3][$i] && (!isset($this->properties[$m[1][$i]]) || !in_array($m[3][$i], $this->properties[$m[1][$i]]))){ 109 | $this->properties[$m[1][$i]][] = $m[3][$i]; 110 | } 111 | if($m[7][$i] && (!isset($this->modifiers[$m[1][$i]]) || !in_array($m[7][$i], $this->modifiers[$m[1][$i].$m[3][$i]]))){ 112 | $this->modifiers[$m[1][$i].$m[3][$i]][] = $m[1][$i].$m[3][$i].$m[7][$i]; 113 | } 114 | if(!in_array($m[1][$i], $this->vars)){ 115 | $this->vars[] = $m[1][$i]; 116 | } 117 | } 118 | } 119 | } 120 | 121 | private function createBlocks(&$blocks) { 122 | $this->parents = array_merge($this->parents, $blocks); 123 | foreach($blocks as $parent => $block){ 124 | foreach($block as $chield){ 125 | if(in_array($chield, $this->blocks)) throw new \UnexpectedValueException("duplicated block: $chield"); 126 | $this->blocks[] = $chield; 127 | $this->setBlock($parent, $chield); 128 | } 129 | } 130 | } 131 | 132 | private function setBlock($parent, $block) { 133 | $name = $block.'_value'; 134 | $str = $this->getVar($parent); 135 | if($this->accurate){ 136 | $str = str_replace("\r\n", "\n", $str); 137 | $reg = "/\t*\n*(\s*.*?\n?)\t*\n*((\s*.*?\n?)\t*\n?)?/sm"; 138 | } 139 | else $reg = "/\s*(\s*.*?\s*)\s*((\s*.*?\s*))?\s*/sm"; 140 | if(1!==preg_match($reg, $str, $m)) throw new \UnexpectedValueException("mal-formed block $block"); 141 | $this->setValue($name, ''); 142 | $this->setValue($block, $m[1]); 143 | $this->setValue($parent, preg_replace($reg, "{".$name."}", $str)); 144 | if(isset($m[3])) $this->finally[$block] = $m[3]; 145 | } 146 | 147 | protected function setValue($varname, $value) { 148 | $this->values['{'.$varname.'}'] = $value; 149 | } 150 | 151 | private function getVar($varname) { 152 | return $this->values['{'.$varname.'}']; 153 | } 154 | 155 | public function clear($varname) { 156 | $this->setValue($varname, ""); 157 | } 158 | 159 | public function setParent($parent, $block){ 160 | $this->parents[$parent][] = $block; 161 | } 162 | 163 | private function substModifiers($value, $exp){ 164 | $statements = explode('|', $exp); 165 | for($i=1; $ivalues), $this->values, $value); 176 | foreach($this->modifiers as $var => $expressions){ 177 | if(false!==strpos($s, "{".$var."|")) foreach($expressions as $exp){ 178 | if(false===strpos($var, "->") && isset($this->values['{'.$var.'}'])){ 179 | $s = str_replace('{'.$exp.'}', $this->substModifiers($this->values['{'.$var.'}'], $exp), $s); 180 | } 181 | } 182 | } 183 | foreach($this->instances as $var => $instance){ 184 | foreach($this->properties[$var] as $properties){ 185 | if(false!==strpos($s, "{".$var.$properties."}") || false!==strpos($s, "{".$var.$properties."|")){ 186 | $pointer = $instance; 187 | $property = explode("->", $properties); 188 | for($i = 1; $i < sizeof($property); $i++){ 189 | if(!is_null($pointer)){ 190 | $obj = strtolower(str_replace('_', '', $property[$i])); 191 | if(method_exists($pointer, "get$obj")) $pointer = $pointer->{"get$obj"}(); 192 | elseif(method_exists($pointer, "__get")) $pointer = $pointer->__get($property[$i]); 193 | elseif(property_exists($pointer, $obj)) $pointer = $pointer->$obj; 194 | else { 195 | $className = $property[$i-1] ? $property[$i-1] : get_class($instance); 196 | $class = is_null($pointer) ? "NULL" : get_class($pointer); 197 | throw new \BadMethodCallException("no accessor method in class ".$class." for ".$className."->".$property[$i]); 198 | } 199 | } else { 200 | $pointer = $instance->get($obj); 201 | } 202 | } 203 | if(is_object($pointer)){ 204 | $pointer = method_exists($pointer, "__toString") ? $pointer->__toString() : "Object"; 205 | } elseif(is_array($pointer)){ 206 | $value = ""; 207 | for($i=0; list($key, $val) = each($pointer); $i++){ 208 | $value.= "$key => $val"; 209 | if($imodifiers[$var.$properties])){ 215 | foreach($this->modifiers[$var.$properties] as $exp){ 216 | $s = str_replace('{'.$exp.'}', $this->substModifiers($pointer, $exp), $s); 217 | } 218 | } 219 | } 220 | } 221 | } 222 | return $s; 223 | } 224 | 225 | public function block($block, $append = true) { 226 | if(!in_array($block, $this->blocks)) throw new \InvalidArgumentException("block $block does not exist"); 227 | if(isset($this->parents[$block])) foreach($this->parents[$block] as $child){ 228 | if(isset($this->finally[$child]) && !in_array($child, $this->parsed)){ 229 | $this->setValue($child.'_value', $this->subst($this->finally[$child])); 230 | $this->parsed[] = $block; 231 | } 232 | } 233 | if ($append) { 234 | $this->setValue($block.'_value', $this->getVar($block.'_value') . $this->subst($this->getVar($block))); 235 | } else { 236 | $this->setValue($block.'_value', $this->getVar($block.'_value')); 237 | } 238 | if(!in_array($block, $this->parsed)) $this->parsed[] = $block; 239 | if(isset($this->parents[$block])) foreach($this->parents[$block] as $child) $this->clear($child.'_value'); 240 | } 241 | 242 | public function parse() { 243 | foreach(array_reverse($this->parents) as $parent => $children){ 244 | foreach($children as $block){ 245 | if(in_array($parent, $this->blocks) && in_array($block, $this->parsed) && !in_array($parent, $this->parsed)){ 246 | $this->setValue($parent.'_value', $this->subst($this->getVar($parent))); 247 | $this->parsed[] = $parent; 248 | } 249 | } 250 | } 251 | foreach($this->finally as $block => $content){ 252 | if(!in_array($block, $this->parsed)){ 253 | $this->setValue($block.'_value', $this->subst($content)); 254 | } 255 | } 256 | return preg_replace("/{(".self::$REG_NAME.")((\-\>(".self::$REG_NAME."))*)?((\|.*?)*)?}/", "", $this->subst($this->getVar("."))); 257 | } 258 | 259 | public static function render($file,$data=[]) { 260 | $tpl = new Template($file); 261 | try { 262 | foreach ($data as $key => $value) { 263 | if ( !is_array($value) ) { 264 | $tpl->$key = $value; 265 | continue; 266 | } 267 | if ( sizeof($value) <= 0 ) { 268 | $tpl->block(strtoupper($key). '_EMPTY'); 269 | continue; 270 | } 271 | $bool = false; 272 | foreach($value as $ke => $val){ 273 | if ( !is_array($val) ) { 274 | $tpl->$ke = $val; 275 | }else{ 276 | $bool = true; 277 | foreach ($val as $k => $v) { 278 | $tpl->$k = $v; 279 | } 280 | $tpl->block(strtoupper($key)); 281 | } 282 | } 283 | if ( !$bool ) { 284 | $tpl->block(strtoupper($key)); 285 | continue; 286 | } 287 | } 288 | } catch (\Exception $e){ 289 | 290 | return "Template Error!"; 291 | 292 | } 293 | return $tpl->parse(); 294 | } 295 | } -------------------------------------------------------------------------------- /src/Timeout.php: -------------------------------------------------------------------------------- 1 | times = $times; 12 | if ($ex === null) { 13 | $this->exception = new HttpException(408, "Request timeout"); 14 | } else { 15 | $this->exception = $ex; 16 | } 17 | } 18 | 19 | public function __invoke(Context $ctx, $next) 20 | { 21 | yield race([ 22 | callcc(function($k) { 23 | Timer::add($this->times, function() use($k) { 24 | $k(null, $this->exception); 25 | },[],false); 26 | }), 27 | function() use($next) { 28 | yield $next; 29 | } 30 | ]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | $v) { 43 | $task->$k = $v; 44 | } 45 | (new AsyncTask($task, $parent))->begin($continuation); 46 | } else { 47 | $continuation($task, null); 48 | } 49 | } 50 | 51 | function await($task, ...$args) 52 | { 53 | if ($task instanceof \Generator) { 54 | return $task; 55 | } 56 | 57 | if (is_callable($task)) { 58 | $gen = function() use($task, $args) { yield $task(...$args); }; 59 | } else { 60 | $gen = function() use($task) { yield $task; }; 61 | } 62 | return $gen(); 63 | } 64 | 65 | function race(array $tasks) 66 | { 67 | $tasks = array_map(__NAMESPACE__ . "\\await", $tasks); 68 | 69 | return new Syscall(function(AsyncTask $parent) use($tasks) { 70 | if (empty($tasks)) { 71 | return null; 72 | } else { 73 | return new Any($tasks, $parent); 74 | } 75 | }); 76 | } 77 | 78 | function all(array $tasks){ 79 | 80 | $tasks = array_map(__NAMESPACE__."\\await",$tasks); 81 | 82 | return new SysCall(function(AsyncTask $parent) use ($tasks){ 83 | if(empty($tasks)){ 84 | return null; 85 | }else{ 86 | return new All($tasks,$parent); 87 | } 88 | }); 89 | } 90 | 91 | function timeout($ms) 92 | { 93 | return callcc(function($k) use($ms) { 94 | 95 | Timer::add($ms, function() use($k) { 96 | $k(null, new \Exception("timeout")); 97 | },[],false); 98 | 99 | }); 100 | } 101 | 102 | function async_sleep($ms){ 103 | return callcc( function($k) use($ms){ 104 | 105 | Timer::add($ms, function() use($k) { 106 | $k(null); 107 | },[],false); 108 | 109 | }); 110 | } 111 | 112 | function async_http_curl( $method,$host,$params = []){ 113 | 114 | } 115 | 116 | function callcc(callable $fun, $timeout = 0) 117 | { 118 | if ($timeout > 0) { 119 | $fun = timeoutWrapper($fun, $timeout); 120 | } 121 | return new CallCC($fun); 122 | } 123 | 124 | 125 | function timeoutWrapper(callable $fun,$ms){ 126 | return function ($k) use($fun,$ms){ 127 | $k = once($k); 128 | $fun($k); 129 | 130 | Timer::add($ms, function() use($k) { 131 | $k(null, new \Exception("timeout")); 132 | },[],false); 133 | 134 | }; 135 | } 136 | 137 | function getCtx($key, $default = null) 138 | { 139 | return new Syscall(function(AsyncTask $task) use($key, $default) { 140 | while($task->parent && $task = $task->parent); 141 | if (isset($task->gen->generator->$key)) { 142 | return $task->gen->generator->$key; 143 | } else { 144 | return $default; 145 | } 146 | }); 147 | } 148 | 149 | function setCtx($key, $val) 150 | { 151 | return new Syscall(function(AsyncTask $task) use($key, $val) { 152 | while($task->parent && $task = $task->parent); 153 | $task->gen->generator->$key = $val; 154 | }); 155 | } 156 | 157 | function array_right_reduce(array $input, callable $function, $initial = null) 158 | { 159 | return array_reduce(array_reverse($input, true), $function, $initial); 160 | } 161 | 162 | function compose(array $middleware) 163 | { 164 | return function(Context $ctx = null) use($middleware) { 165 | $ctx = $ctx ?: new Context(); 166 | return array_right_reduce($middleware, function($rightNext, $leftFn) use($ctx) { 167 | return $leftFn($ctx, $rightNext); 168 | }, null); 169 | }; 170 | } -------------------------------------------------------------------------------- /src/template/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 404 Error 8 | 9 | 10 |

页面丢失了!

11 | 12 | -------------------------------------------------------------------------------- /src/template/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 500 Error 8 | 9 | 10 |

服务器出错了!

11 | 12 | -------------------------------------------------------------------------------- /src/template/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Error 8 | 9 | 10 |

页面出错了!

11 |

错误代码: {code}

12 |

错误消息: {msg}

13 | 14 | --------------------------------------------------------------------------------