mindy/router

PHP的快速请求路由器

2.0 2016-08-16 19:36 UTC

This package is not auto-updated.

Last update: 2024-09-14 15:21:54 UTC


README

Build Status

这个库提供了一个基于正则表达式的路由器的快速实现。

  • 超级快速
  • 路由参数和可选路由参数
  • 命名路由和反向路由
  • RESTful控制器路由
  • 路由过滤器和路由分组

归功于 nikic/FastRoute。

虽然库的大部分和广泛的单元测试都是我自己的,但对于正则表达式匹配核心实现和基准测试的功劳归功于 nikic。请去看看nikic的 博客文章,解释了实现的工作原理以及为什么它如此快速。

对核心进行了许多修改以适应新的库包装,并添加了诸如可选路由参数和反向路由等功能,但请前往查看nikic的库以了解核心的起源和工作原理。

安装

通过composer安装

{
    "require": {
        "mindy/router": "2.*"
    }
}

使用

定义路由

通过在 Phroute\RouteCollector 收集器实例上调用 addRoute($method, $route, $handler) 添加路由。

  #NB. You can also call the HTTP method short cuts: 
 $routeCollector->get($route, $handler);    # match only get request methods
 $routeCollector->post($route, $handler);   # match only post request methods
 $routeCollector->delete($route, $handler); # match only delete request methods
 $routeCollector->any($route, $handler);    # match any request method
 etc...

此方法接受路由必须匹配的HTTP方法、路由模式、相关处理程序以及可选的'before'和'after'过滤器数组。处理程序不一定必须是回调(它也可以是控制器类名和方法或任何其他您希望与路由关联的数据)。

默认情况下,使用路由模式语法,其中 {foo} 指定一个名称为 foo 的占位符,匹配字符串 [^/]+。要调整占位符匹配的模式,您可以指定一个自定义模式,例如 {bar:[0-9]+}。但是,也可以通过传递不同的路由解析器来调整模式语法。

$router = new Phroute\RouteCollector(new Phroute\RouteParser);


$router->any('/example', function(){
    return 'This route responds to any method (POST, GET, DELETE etc...) at the URI /example';
});

// or '/page/{id:i}' (see shortcuts)

$router->post('/page/{id:\d+}', function($id){

    // $id contains the url paramter
    
    return 'This route responds to the post method at the URI /page/{param} where param is at least one number';
});

$router->any('/', function(){

    return 'This responds to the default route';
});

// Optional Parameters
// simply add a '?' after the route name to make the parameter optional
// NB. be sure to add a default value for the function argument
$router->addRoute('GET', '/user/{id}?', function($id = null) {
    return 'second';
});

# NB. You can cache this object so you don't have to create the routes each request - massive speed gains
$dispatcher = new Phroute\Dispatcher($router);

$response = $dispatcher->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);
    
// Print out the value returned from the dispatched function
echo $response;

正则表达式快捷方式


:i => :/d+                # numbers only
:a => :[a-zA-Z0-9]+       # alphanumeric
:c => :[a-zA-Z0-9+_-\.]+  # alnumnumeric and + _ - . characters 
:h => :[a-fA-F0-9]+       # hex

use in routes:

'/user/{name:i}'
'/user/{name:a}'

###反向路由的命名路由

传递一个数组作为第一个参数,其中第一个元素是您的路由,第二个元素是稍后要引用的名称。

$router->get(['/user/{name}', 'username'], function($name){
    return 'Hello ' . $name;
})
->get(['/page/{slug}/{id:\d+}', 'page'], function($id){
    return 'You must be authenticated to see this page: ' . $id;
});

// Use the routename and pass in any route parameters to reverse engineer an existing route path
// If you change your route path above, you won't need to go through your code updating any links/references to that route
$router->route('username', 'joe');
// string(9) '/user/joe'

$router->route('page', ['intro', 456]);
// string(15) '/page/intro/456'

###控制器

class Test {
    
    public function anyIndex()
    {
        return 'This is the default page and will respond to /controller and /controller/index';
    }
    
    /**
    * One required paramter and one optional parameter
    */
    public function anyTest($param, $param2 = 'default')
    {
        return 'This will respond to /controller/test/{param}/{param2}? with any method';
    }
    
    public function getTest()
    {
        return 'This will respond to /controller/test with only a GET method';
    }
    
    public function postTest()
    {
        return 'This will respond to /controller/test with only a POST method';
    }
    
    public function putTest()
    {
        return 'This will respond to /controller/test with only a PUT method';
    }
    
    public function deleteTest()
    {
        return 'This will respond to /controller/test with only a DELETE method';
    }
}

$router->controller('/controller', 'Test');

分发URI

通过调用创建的分发器的 dispatch() 方法来分发URI。此方法接受HTTP方法和URI。获取这两部分信息(并适当地进行规范化)是您的工作 - 此库不受PHP Web SAPIs的约束。

$response = (new Phroute\Dispatcher($router)) ->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);

dispatch() 方法将调用匹配的路由,如果没有匹配,则抛出以下异常之一

# Route not found
Phroute\Exception\HttpRouteNotFoundException;

# Route found, but method not allowed
Phroute\Exception\HttpMethodNotAllowedException;

注意:HTTP规范要求包含Allow:头部的405 Method Not Allowed响应详细说明请求资源的可用方法。这些信息可以从抛出的异常的消息内容中获取:看起来像"Allow: HEAD, GET, POST"等...取决于您设置的方法。您应该捕获异常并使用它向客户端发送一个头:header($e->getMessage());

关于HEAD请求的说明

HTTP规范要求服务器[支持GET和HEAD方法][2616-511]

GET和HEAD方法必须由所有通用服务器支持

为了避免强制用户手动为每个资源注册HEAD路由,我们回退到匹配给定资源的可用GET路由。PHP Web SAPI透明地从HEAD响应中删除实体主体,因此这种行为对大多数用户没有影响。

然而,使用Phroute于Web SAPI环境之外(例如自定义服务器)的实现者不得发送对HEAD请求生成的实体体。如果您是非SAPI用户,这是您的责任;Phroute无法阻止您在这种情况下破坏HTTP。

最后,请注意,应用程序始终可以指定特定资源的HEAD方法路由,以完全绕过此行为。

性能

在具有

  • 处理器 2.3 GHz Intel Core i7
  • 内存 8 GB 1600 MHz DDR3

####Phroute

此测试旨在部分说明轻量级路由核心的效率,但主要是与常规库相比,随着路由数量的增加,匹配速度没有降低。

有10条路由,匹配第1条路由(最佳情况)
$ /usr/local/bin/ab -n 1000 -c 100 http://127.0.0.1:9943/

Finished 1000 requests

Time taken for tests:   3.062 seconds
Requests per second:    326.60 [#/sec] (mean)
Time per request:       306.181 [ms] (mean)
Time per request:       3.062 [ms] (mean, across all concurrent requests)
Transfer rate:          37.32 [Kbytes/sec] received

Percentage of the requests served within a certain time (ms)
  50%    306
  66%    307
  75%    307
  80%    308
  90%    309
  95%    309
  98%    310
  99%    310
 100%    310 (longest request)
有10条路由,匹配最后一条路由(最坏情况)

请注意,匹配速度与第一条路由一样快

$ /usr/local/bin/ab -n 1000 -c 100 http://127.0.0.1:9943/thelastroute

Finished 1000 requests

Time taken for tests:   3.079 seconds
Requests per second:    324.80 [#/sec] (mean)
Time per request:       307.880 [ms] (mean)
Time per request:       3.079 [ms] (mean, across all concurrent requests)
Transfer rate:          37.11 [Kbytes/sec] received


Percentage of the requests served within a certain time (ms)
  50%    307
  66%    308
  75%    309
  80%    309
  90%    310
  95%    311
  98%    312
  99%    312
 100%    313 (longest request)
有100条路由,匹配最后一条路由(最坏情况)
$ /usr/local/bin/ab -n 1000 -c 100 http://127.0.0.1:9943/thelastroute

Finished 1000 requests

Time taken for tests:   3.195 seconds
Requests per second:    312.97 [#/sec] (mean)
Time per request:       319.515 [ms] (mean)
Time per request:       3.195 [ms] (mean, across all concurrent requests)
Transfer rate:          35.76 [Kbytes/sec] received


Percentage of the requests served within a certain time (ms)
  50%    318
  66%    319
  75%    320
  80%    320
  90%    322
  95%    323
  98%    323
  99%    324
 100%    324 (longest request)
有1000条路由,匹配最后一条路由(最坏情况)
$ /usr/local/bin/ab -n 1000 -c 100 http://127.0.0.1:9943/thelastroute

Finished 1000 requests

Time taken for tests:   4.497 seconds
Complete requests:      1000
Requests per second:    222.39 [#/sec] (mean)
Time per request:       449.668 [ms] (mean)
Time per request:       4.497 [ms] (mean, across all concurrent requests)
Transfer rate:          25.41 [Kbytes/sec] received

Percentage of the requests served within a certain time (ms)
  50%    445
  66%    447
  75%    448
  80%    449
  90%    454
  95%    456
  98%    457
  99%    458
 100%    478 (longest request)

###与Laravel 4.0路由核心进行比较

请注意,这并不是对Laravel的轻视——它是基于路由循环的,这就是为什么随着路由数量的增加,性能会变差

有10条路由,匹配第1条路由(最佳情况)
$ /usr/local/bin/ab -n 1000 -c 100 http://127.0.0.1:4968/

Finished 1000 requests

Time taken for tests:   13.366 seconds
Requests per second:    74.82 [#/sec] (mean)
Time per request:       1336.628 [ms] (mean)
Time per request:       13.366 [ms] (mean, across all concurrent requests)
Transfer rate:          8.55 [Kbytes/sec] received

Percentage of the requests served within a certain time (ms)
  50%   1336
  66%   1339
  75%   1340
  80%   1341
  90%   1346
  95%   1348
  98%   1349
  99%   1351
 100%   1353 (longest request)
有10条路由,匹配最后一条路由(最坏情况)
$ /usr/local/bin/ab -n 1000 -c 100 http://127.0.0.1:4968/thelastroute

Finished 1000 requests

Time taken for tests:   14.621 seconds
Requests per second:    68.39 [#/sec] (mean)
Time per request:       1462.117 [ms] (mean)
Time per request:       14.621 [ms] (mean, across all concurrent requests)
Transfer rate:          7.81 [Kbytes/sec] received

Percentage of the requests served within a certain time (ms)
  50%   1461
  66%   1465
  75%   1469
  80%   1472
  90%   1476
  95%   1479
  98%   1480
  99%   1482
 100%   1484 (longest request)
有100条路由,匹配最后一条路由(最坏情况)
$ /usr/local/bin/ab -n 1000 -c 100 http://127.0.0.1:4968/thelastroute

Finished 1000 requests

Time taken for tests:   31.254 seconds
Requests per second:    32.00 [#/sec] (mean)
Time per request:       3125.402 [ms] (mean)
Time per request:       31.254 [ms] (mean, across all concurrent requests)
Transfer rate:          3.66 [Kbytes/sec] received

Percentage of the requests served within a certain time (ms)
  50%   3124
  66%   3145
  75%   3154
  80%   3163
  90%   3188
  95%   3219
  98%   3232
  99%   3236
 100%   3241 (longest request)
有1000条路由,匹配最后一条路由(最坏情况)
$ /usr/local/bin/ab -n 1000 -c 100 http://127.0.0.1:5740/thelastroute

Finished 1000 requests

Time taken for tests:   197.366 seconds
Requests per second:    5.07 [#/sec] (mean)
Time per request:       19736.598 [ms] (mean)
Time per request:       197.366 [ms] (mean, across all concurrent requests)
Transfer rate:          0.58 [Kbytes/sec] received

Percentage of the requests served within a certain time (ms)
  50%  19736
  66%  19802
  75%  19827
  80%  19855
  90%  19898
  95%  19918
  98%  19945
  99%  19960
 100%  19975 (longest request)