Initial work
This commit is contained in:
parent
4879210fba
commit
d7412d7191
59
SegmentRouter.php
Normal file
59
SegmentRouter.php
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class SegmentRouter
|
||||||
|
{
|
||||||
|
private array $routes = [];
|
||||||
|
|
||||||
|
public function add(string $method, string $route, callable $handler)
|
||||||
|
{
|
||||||
|
// Expand the route into segments
|
||||||
|
$segments = explode('/', trim($route, '/'));
|
||||||
|
|
||||||
|
// Push each segment into the routes array as a node
|
||||||
|
$node = &$this->routes;
|
||||||
|
foreach ($segments as $segment) $node = &$node[$segment];
|
||||||
|
|
||||||
|
// Add the handler to the last node
|
||||||
|
$node[$method] = $handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function lookup(string $method, string $uri): int
|
||||||
|
{
|
||||||
|
// Expand the URI into segments
|
||||||
|
$uriSegments = explode('/', trim($uri, '/'));
|
||||||
|
$node = $this->routes;
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
// Traverse the routes array to find the handler
|
||||||
|
foreach ($uriSegments as $segment) {
|
||||||
|
// Check if the segment exists in the node, or if there's a dynamic segment
|
||||||
|
if (!isset($node[$segment])) {
|
||||||
|
$dynamicSegment = $this->matchDynamicSegment($node, $segment);
|
||||||
|
if ($dynamicSegment) {
|
||||||
|
$params[] = $segment;
|
||||||
|
$node = $node[$dynamicSegment];
|
||||||
|
} else {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$node = $node[$segment];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the HTTP method is not supported, return 405
|
||||||
|
if (!isset($node[$method])) return 405;
|
||||||
|
|
||||||
|
// Call the handler
|
||||||
|
$handler = $node[$method];
|
||||||
|
call_user_func_array($handler, $params);
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function matchDynamicSegment(array $node, string $segment)
|
||||||
|
{
|
||||||
|
foreach ($node as $key => $value) {
|
||||||
|
if (strpos($key, ':') === 0) return $key;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
71
TrieRouter.php
Normal file
71
TrieRouter.php
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class TrieRouter
|
||||||
|
{
|
||||||
|
private array $GET = [];
|
||||||
|
private array $POST = [];
|
||||||
|
private array $PUT = [];
|
||||||
|
private array $DELETE = [];
|
||||||
|
|
||||||
|
// Add route to the trie
|
||||||
|
public function add(string $method, string $route, callable $handler)
|
||||||
|
{
|
||||||
|
$node = &$this->{strtoupper($method)};
|
||||||
|
$segments = explode('/', trim($route, '/'));
|
||||||
|
|
||||||
|
foreach ($segments as $segment) {
|
||||||
|
if (!isset($node[$segment])) {
|
||||||
|
$node[$segment] = ['_children' => [], '_handler' => null];
|
||||||
|
}
|
||||||
|
$node = &$node[$segment]['_children'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$node['_handler'] = $handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find and handle the route
|
||||||
|
public function handleRequest(string $method, string $uri)
|
||||||
|
{
|
||||||
|
$node = &$this->{strtoupper($method)};
|
||||||
|
$segments = explode('/', trim($uri, '/'));
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
foreach ($segments as $segment) {
|
||||||
|
if (isset($node[$segment])) {
|
||||||
|
$node = &$node[$segment]['_children'];
|
||||||
|
} else {
|
||||||
|
// Handle dynamic parameters (e.g., :id)
|
||||||
|
$dynamicSegment = $this->matchDynamicSegment($node, $segment);
|
||||||
|
if ($dynamicSegment) {
|
||||||
|
$params[] = $segment;
|
||||||
|
$node = &$node[$dynamicSegment]['_children'];
|
||||||
|
} else {
|
||||||
|
return $this->notFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a handler exists for the current node
|
||||||
|
if (isset($node['_handler'])) {
|
||||||
|
return call_user_func_array($node['_handler'], $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match dynamic route segments like ':id'
|
||||||
|
private function matchDynamicSegment(array $node, string $segment)
|
||||||
|
{
|
||||||
|
foreach ($node as $key => $value) {
|
||||||
|
if (strpos($key, ':') === 0) return $key;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default 404 handler
|
||||||
|
private function notFound()
|
||||||
|
{
|
||||||
|
echo "404 Not Found";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
61
tests/array.php
Normal file
61
tests/array.php
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test fiel puts PHP's array to the test by running a million lookups on a handful of routes.
|
||||||
|
The routes are read from two files, blog.txt and github.txt, and added to two separate arrays.
|
||||||
|
The lookups are then run in two separate loops, one for each array.
|
||||||
|
Each lookup is timed and the memory usage is also printed out at regular intervals.
|
||||||
|
The requests are randomly picked from the array of routes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function readAndAddRoutes(string $file): array
|
||||||
|
{
|
||||||
|
$array = [];
|
||||||
|
$routes = file($file);
|
||||||
|
foreach ($routes as $route) {
|
||||||
|
[$method, $path] = explode(' ', $route);
|
||||||
|
$path = trim($path);
|
||||||
|
$array[] = [$method, $path, function() {
|
||||||
|
return true;
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
$blog = readAndAddRoutes('blog.txt');
|
||||||
|
$github = readAndAddRoutes('github.txt');
|
||||||
|
|
||||||
|
function echoMemoryAndTime(int $i, float $start) {
|
||||||
|
echo "($i lookups) M: " . round(memory_get_usage() / 1024, 1) . "kb - T: ". number_format(microtime(true) - $start, 10) ." seconds\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
function runIterations(int $iterations, array $routes) {
|
||||||
|
echo "\n";
|
||||||
|
echo "===============================================================\n";
|
||||||
|
echo "\n";
|
||||||
|
echo "Running $iterations iterations\n";
|
||||||
|
$start = microtime(true);
|
||||||
|
$interval = $iterations / 10;
|
||||||
|
for ($i = 0; $i < $iterations; $i++) {
|
||||||
|
// pick a random route from the array
|
||||||
|
[$method, $uri, $handler] = $routes[array_rand($routes)];
|
||||||
|
$res = $handler();
|
||||||
|
if ($i !== 0 && $i % ($interval) === 0) echoMemoryAndTime($i, $start);
|
||||||
|
}
|
||||||
|
echo "Time taken: " . number_format(microtime(true) - $start, 10) . " seconds\n";
|
||||||
|
// echo the average time per request
|
||||||
|
echo "Average time per lookup: " . number_format((microtime(true) - $start) / $iterations, 10) . " seconds\n";
|
||||||
|
echo "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Starting blog lookups\n";
|
||||||
|
runIterations(100000, $blog);
|
||||||
|
runIterations(1000000, $blog);
|
||||||
|
|
||||||
|
echo "\n";
|
||||||
|
echo "===============================================================\n";
|
||||||
|
echo "\n";
|
||||||
|
echo "Starting github lookups\n";
|
||||||
|
runIterations(10000, $github);
|
||||||
|
runIterations(100000, $github);
|
||||||
|
runIterations(1000000, $github);
|
1000
tests/big.txt
Normal file
1000
tests/big.txt
Normal file
File diff suppressed because it is too large
Load Diff
4
tests/blog.txt
Normal file
4
tests/blog.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
GET /
|
||||||
|
GET /:slug
|
||||||
|
GET /tags
|
||||||
|
GET /tag/:tag
|
203
tests/github.txt
Normal file
203
tests/github.txt
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
GET /authorizations
|
||||||
|
GET /authorizations/:id
|
||||||
|
POST /authorizations
|
||||||
|
DELETE /authorizations/:id
|
||||||
|
GET /applications/:client_id/tokens/:access_token
|
||||||
|
DELETE /applications/:client_id/tokens
|
||||||
|
DELETE /applications/:client_id/tokens/:access_token
|
||||||
|
GET /events
|
||||||
|
GET /repos/:owner/:repo/events
|
||||||
|
GET /networks/:owner/:repo/events
|
||||||
|
GET /orgs/:org/events
|
||||||
|
GET /users/:user/received_events
|
||||||
|
GET /users/:user/received_events/public
|
||||||
|
GET /users/:user/events
|
||||||
|
GET /users/:user/events/public
|
||||||
|
GET /users/:user/events/orgs/:org
|
||||||
|
GET /feeds
|
||||||
|
GET /notifications
|
||||||
|
GET /repos/:owner/:repo/notifications
|
||||||
|
PUT /notifications
|
||||||
|
PUT /repos/:owner/:repo/notifications
|
||||||
|
GET /notifications/threads/:id
|
||||||
|
GET /notifications/threads/:id/subscription
|
||||||
|
PUT /notifications/threads/:id/subscription
|
||||||
|
DELETE /notifications/threads/:id/subscription
|
||||||
|
GET /repos/:owner/:repo/stargazers
|
||||||
|
GET /users/:user/starred
|
||||||
|
GET /user/starred
|
||||||
|
GET /user/starred/:owner/:repo
|
||||||
|
PUT /user/starred/:owner/:repo
|
||||||
|
DELETE /user/starred/:owner/:repo
|
||||||
|
GET /repos/:owner/:repo/subscribers
|
||||||
|
GET /users/:user/subscriptions
|
||||||
|
GET /user/subscriptions
|
||||||
|
GET /repos/:owner/:repo/subscription
|
||||||
|
PUT /repos/:owner/:repo/subscription
|
||||||
|
DELETE /repos/:owner/:repo/subscription
|
||||||
|
GET /user/subscriptions/:owner/:repo
|
||||||
|
PUT /user/subscriptions/:owner/:repo
|
||||||
|
DELETE /user/subscriptions/:owner/:repo
|
||||||
|
GET /users/:user/gists
|
||||||
|
GET /gists
|
||||||
|
GET /gists/:id
|
||||||
|
POST /gists
|
||||||
|
PUT /gists/:id/star
|
||||||
|
DELETE /gists/:id/star
|
||||||
|
GET /gists/:id/star
|
||||||
|
POST /gists/:id/forks
|
||||||
|
DELETE /gists/:id
|
||||||
|
GET /repos/:owner/:repo/git/blobs/:sha
|
||||||
|
POST /repos/:owner/:repo/git/blobs
|
||||||
|
GET /repos/:owner/:repo/git/commits/:sha
|
||||||
|
POST /repos/:owner/:repo/git/commits
|
||||||
|
GET /repos/:owner/:repo/git/refs
|
||||||
|
POST /repos/:owner/:repo/git/refs
|
||||||
|
GET /repos/:owner/:repo/git/tags/:sha
|
||||||
|
POST /repos/:owner/:repo/git/tags
|
||||||
|
GET /repos/:owner/:repo/git/trees/:sha
|
||||||
|
POST /repos/:owner/:repo/git/trees
|
||||||
|
GET /issues
|
||||||
|
GET /user/issues
|
||||||
|
GET /orgs/:org/issues
|
||||||
|
GET /repos/:owner/:repo/issues
|
||||||
|
GET /repos/:owner/:repo/issues/:number
|
||||||
|
POST /repos/:owner/:repo/issues
|
||||||
|
GET /repos/:owner/:repo/assignees
|
||||||
|
GET /repos/:owner/:repo/assignees/:assignee
|
||||||
|
GET /repos/:owner/:repo/issues/:number/comments
|
||||||
|
POST /repos/:owner/:repo/issues/:number/comments
|
||||||
|
GET /repos/:owner/:repo/issues/:number/events
|
||||||
|
GET /repos/:owner/:repo/labels
|
||||||
|
GET /repos/:owner/:repo/labels/:name
|
||||||
|
POST /repos/:owner/:repo/labels
|
||||||
|
DELETE /repos/:owner/:repo/labels/:name
|
||||||
|
GET /repos/:owner/:repo/issues/:number/labels
|
||||||
|
POST /repos/:owner/:repo/issues/:number/labels
|
||||||
|
DELETE /repos/:owner/:repo/issues/:number/labels/:name
|
||||||
|
PUT /repos/:owner/:repo/issues/:number/labels
|
||||||
|
DELETE /repos/:owner/:repo/issues/:number/labels
|
||||||
|
GET /repos/:owner/:repo/milestones/:number/labels
|
||||||
|
GET /repos/:owner/:repo/milestones
|
||||||
|
GET /repos/:owner/:repo/milestones/:number
|
||||||
|
POST /repos/:owner/:repo/milestones
|
||||||
|
DELETE /repos/:owner/:repo/milestones/:number
|
||||||
|
GET /emojis
|
||||||
|
GET /gitignore/templates
|
||||||
|
GET /gitignore/templates/:name
|
||||||
|
POST /markdown
|
||||||
|
POST /markdown/raw
|
||||||
|
GET /meta
|
||||||
|
GET /rate_limit
|
||||||
|
GET /users/:user/orgs
|
||||||
|
GET /user/orgs
|
||||||
|
GET /orgs/:org
|
||||||
|
GET /orgs/:org/members
|
||||||
|
GET /orgs/:org/members/:user
|
||||||
|
DELETE /orgs/:org/members/:user
|
||||||
|
GET /orgs/:org/public_members
|
||||||
|
GET /orgs/:org/public_members/:user
|
||||||
|
PUT /orgs/:org/public_members/:user
|
||||||
|
DELETE /orgs/:org/public_members/:user
|
||||||
|
GET /orgs/:org/teams
|
||||||
|
GET /teams/:id
|
||||||
|
POST /orgs/:org/teams
|
||||||
|
DELETE /teams/:id
|
||||||
|
GET /teams/:id/members
|
||||||
|
GET /teams/:id/members/:user
|
||||||
|
PUT /teams/:id/members/:user
|
||||||
|
DELETE /teams/:id/members/:user
|
||||||
|
GET /teams/:id/repos
|
||||||
|
GET /teams/:id/repos/:owner/:repo
|
||||||
|
PUT /teams/:id/repos/:owner/:repo
|
||||||
|
DELETE /teams/:id/repos/:owner/:repo
|
||||||
|
GET /user/teams
|
||||||
|
GET /repos/:owner/:repo/pulls
|
||||||
|
GET /repos/:owner/:repo/pulls/:number
|
||||||
|
POST /repos/:owner/:repo/pulls
|
||||||
|
GET /repos/:owner/:repo/pulls/:number/commits
|
||||||
|
GET /repos/:owner/:repo/pulls/:number/files
|
||||||
|
GET /repos/:owner/:repo/pulls/:number/merge
|
||||||
|
PUT /repos/:owner/:repo/pulls/:number/merge
|
||||||
|
GET /repos/:owner/:repo/pulls/:number/comments
|
||||||
|
PUT /repos/:owner/:repo/pulls/:number/comments
|
||||||
|
GET /user/repos
|
||||||
|
GET /users/:user/repos
|
||||||
|
GET /orgs/:org/repos
|
||||||
|
GET /repositories
|
||||||
|
POST /user/repos
|
||||||
|
POST /orgs/:org/repos
|
||||||
|
GET /repos/:owner/:repo
|
||||||
|
GET /repos/:owner/:repo/contributors
|
||||||
|
GET /repos/:owner/:repo/languages
|
||||||
|
GET /repos/:owner/:repo/teams
|
||||||
|
GET /repos/:owner/:repo/tags
|
||||||
|
GET /repos/:owner/:repo/branches
|
||||||
|
GET /repos/:owner/:repo/branches/:branch
|
||||||
|
DELETE /repos/:owner/:repo
|
||||||
|
GET /repos/:owner/:repo/collaborators
|
||||||
|
GET /repos/:owner/:repo/collaborators/:user
|
||||||
|
PUT /repos/:owner/:repo/collaborators/:user
|
||||||
|
DELETE /repos/:owner/:repo/collaborators/:user
|
||||||
|
GET /repos/:owner/:repo/comments
|
||||||
|
GET /repos/:owner/:repo/commits/:sha/comments
|
||||||
|
POST /repos/:owner/:repo/commits/:sha/comments
|
||||||
|
GET /repos/:owner/:repo/comments/:id
|
||||||
|
DELETE /repos/:owner/:repo/comments/:id
|
||||||
|
GET /repos/:owner/:repo/commits
|
||||||
|
GET /repos/:owner/:repo/commits/:sha
|
||||||
|
GET /repos/:owner/:repo/readme
|
||||||
|
GET /repos/:owner/:repo/keys
|
||||||
|
GET /repos/:owner/:repo/keys/:id
|
||||||
|
POST /repos/:owner/:repo/keys
|
||||||
|
DELETE /repos/:owner/:repo/keys/:id
|
||||||
|
GET /repos/:owner/:repo/downloads
|
||||||
|
GET /repos/:owner/:repo/downloads/:id
|
||||||
|
DELETE /repos/:owner/:repo/downloads/:id
|
||||||
|
GET /repos/:owner/:repo/forks
|
||||||
|
POST /repos/:owner/:repo/forks
|
||||||
|
GET /repos/:owner/:repo/hooks
|
||||||
|
GET /repos/:owner/:repo/hooks/:id
|
||||||
|
POST /repos/:owner/:repo/hooks
|
||||||
|
POST /repos/:owner/:repo/hooks/:id/tests
|
||||||
|
DELETE /repos/:owner/:repo/hooks/:id
|
||||||
|
POST /repos/:owner/:repo/merges
|
||||||
|
GET /repos/:owner/:repo/releases
|
||||||
|
GET /repos/:owner/:repo/releases/:id
|
||||||
|
POST /repos/:owner/:repo/releases
|
||||||
|
DELETE /repos/:owner/:repo/releases/:id
|
||||||
|
GET /repos/:owner/:repo/releases/:id/assets
|
||||||
|
GET /repos/:owner/:repo/stats/contributors
|
||||||
|
GET /repos/:owner/:repo/stats/commit_activity
|
||||||
|
GET /repos/:owner/:repo/stats/code_frequency
|
||||||
|
GET /repos/:owner/:repo/stats/participation
|
||||||
|
GET /repos/:owner/:repo/stats/punch_card
|
||||||
|
GET /repos/:owner/:repo/statuses/:ref
|
||||||
|
POST /repos/:owner/:repo/statuses/:ref
|
||||||
|
GET /search/repositories
|
||||||
|
GET /search/code
|
||||||
|
GET /search/issues
|
||||||
|
GET /search/users
|
||||||
|
GET /legacy/issues/search/:owner/:repository/:state/:keyword
|
||||||
|
GET /legacy/repos/search/:keyword
|
||||||
|
GET /legacy/user/search/:keyword
|
||||||
|
GET /legacy/user/email/:email
|
||||||
|
GET /users/:user
|
||||||
|
GET /user
|
||||||
|
GET /users
|
||||||
|
GET /user/emails
|
||||||
|
POST /user/emails
|
||||||
|
DELETE /user/emails
|
||||||
|
GET /users/:user/followers
|
||||||
|
GET /user/followers
|
||||||
|
GET /users/:user/following
|
||||||
|
GET /user/following
|
||||||
|
GET /user/following/:user
|
||||||
|
GET /users/:user/following/:target_user
|
||||||
|
PUT /user/following/:user
|
||||||
|
DELETE /user/following/:user
|
||||||
|
GET /users/:user/keys
|
||||||
|
GET /user/keys
|
||||||
|
GET /user/keys/:id
|
||||||
|
POST /user/keys
|
||||||
|
DELETE /user/keys/:id
|
45
tests/makeRoutes.php
Normal file
45
tests/makeRoutes.php
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$routes = [];
|
||||||
|
|
||||||
|
$methods = ['GET', 'POST', 'PUT', 'DELETE'];
|
||||||
|
$apis = ['blog', 'github', 'dragonknight', 'ecchi', 'hentai', 'harem', 'isekai', 'mecha', 'romance', 'shoujo', 'shounen', 'slice-of-life', 'supernatural', 'yuri'];
|
||||||
|
$params = ['id', 'slug', 'page', 'extra', 'foo', 'string', 'number', 'bar', 'baz', 'qux', 'quux', 'corge', 'grault', 'garply', 'waldo', 'fred', 'plugh', 'xyzzy', 'thud'];
|
||||||
|
$endpoint = ['edit', 'create', 'delete', 'view', 'change', 'modify', 'generate', 'lift', 'lower', 'raise', 'drop', 'pick', 'choose', 'select', 'deselect', 'unselect', 'reselect', 'pick', 'unpick', 'repick', 'reselect', 'reunpick', 'rechoose', 'reselect'];
|
||||||
|
$midpoint = ['do', 'cause', 'effect', 'affect', 'impact', 'influence', 'change', 'modify', 'transform', 'alter', 'shift', 'adjust', 'adapt', 'convert', 'translate'];
|
||||||
|
|
||||||
|
// Generate routes
|
||||||
|
for ($i = 0; $i < 1000; $i++) {
|
||||||
|
$routes[] = makeRoute();
|
||||||
|
// write the routes array to a file
|
||||||
|
file_put_contents('big.txt', implode("\n", $routes));
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeRoute(): string
|
||||||
|
{
|
||||||
|
global $methods, $apis, $params, $endpoint, $midpoint;
|
||||||
|
|
||||||
|
$method = $methods[array_rand($methods)];
|
||||||
|
$api = $apis[array_rand($apis)];
|
||||||
|
$route = "/$api";
|
||||||
|
$length = rand(1, 8);
|
||||||
|
$options = ['params', 'endpoint', 'midpoint'];
|
||||||
|
for ($i = 0; $i < $length; $i++) {
|
||||||
|
$option = $options[array_rand($options)];
|
||||||
|
switch ($option) {
|
||||||
|
case 'params':
|
||||||
|
$param = $params[array_rand($params)];
|
||||||
|
$route .= "/:$param";
|
||||||
|
break;
|
||||||
|
case 'endpoint':
|
||||||
|
$end = $endpoint[array_rand($endpoint)];
|
||||||
|
$route .= "/$end";
|
||||||
|
break;
|
||||||
|
case 'midpoint':
|
||||||
|
$mid = $midpoint[array_rand($midpoint)];
|
||||||
|
$route .= "/$mid";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $method . ' ' . $route;
|
||||||
|
}
|
79
tests/segment.php
Normal file
79
tests/segment.php
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test file puts the SegmentRouter to the test by running a million lookups on a handful
|
||||||
|
of routes. The routes are read from two files, blog.txt and github.txt, and added to two separate
|
||||||
|
routers. The lookups are then run in two separate loops, one for each router.
|
||||||
|
Each lookup is timed and the memory usage is also printed out at regular intervals.
|
||||||
|
The requests are randomly picked from the array of routes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../SegmentRouter.php';
|
||||||
|
require_once 'tools.php';
|
||||||
|
|
||||||
|
// Blog router
|
||||||
|
$b = new SegmentRouter();
|
||||||
|
$blog = readAndAddRoutes('blog.txt', $b);
|
||||||
|
|
||||||
|
// Github router
|
||||||
|
$g = new SegmentRouter();
|
||||||
|
$github = readAndAddRoutes('github.txt', $g);
|
||||||
|
|
||||||
|
// Big router
|
||||||
|
$big = new SegmentRouter();
|
||||||
|
$bigRoutes = readAndAddRoutes('big.txt', $big);
|
||||||
|
|
||||||
|
echoTitle("Starting github lookups");
|
||||||
|
runIterations(100000, $b, $blog);
|
||||||
|
runIterations(1000000, $b, $blog);
|
||||||
|
|
||||||
|
echoTitle("Starting github lookups");
|
||||||
|
runIterations(10000, $g, $github);
|
||||||
|
runIterations(100000, $g, $github);
|
||||||
|
runIterations(1000000, $g, $github);
|
||||||
|
|
||||||
|
echoTitle("Starting big lookups");
|
||||||
|
runIterations(10000, $big, $bigRoutes);
|
||||||
|
runIterations(100000, $big, $bigRoutes);
|
||||||
|
runIterations(1000000, $big, $bigRoutes);
|
||||||
|
|
||||||
|
echoTitle("Testing parameters");
|
||||||
|
|
||||||
|
$routes = [
|
||||||
|
['GET', '/blog/:id', function($id) {
|
||||||
|
echo $id."\n";
|
||||||
|
}],
|
||||||
|
['GET', '/blog/:id/:slug', function($id, $slug) {
|
||||||
|
echo $id . ' - ' . $slug."\n";
|
||||||
|
}],
|
||||||
|
['GET', '/blog/:id/:slug/:page', function($id, $slug, $page) {
|
||||||
|
echo $id . ' - ' . $slug . ' - ' . $page."\n";
|
||||||
|
}],
|
||||||
|
['GET', '/blog/:id/:slug/:page/:extra', function($id, $slug, $page, $extra) {
|
||||||
|
echo $id . ' - ' . $slug . ' - ' . $page . ' - ' . $extra."\n";
|
||||||
|
}],
|
||||||
|
];
|
||||||
|
|
||||||
|
$r = new SegmentRouter();
|
||||||
|
foreach ($routes as $route) {
|
||||||
|
[$method, $path, $handler] = $route;
|
||||||
|
$r->add($method, $path, $handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 0; $i < 10; $i++) {
|
||||||
|
[$method, $uri] = $routes[array_rand($routes)];
|
||||||
|
|
||||||
|
// Generate some random parameters
|
||||||
|
$uri = str_replace(':id', rand(1, 100), $uri);
|
||||||
|
$uri = str_replace(':slug', 'slug-' . rand(1, 100), $uri);
|
||||||
|
$uri = str_replace(':page', rand(1, 100), $uri);
|
||||||
|
$uri = str_replace(':extra', 'extra-' . rand(1, 100), $uri);
|
||||||
|
|
||||||
|
$res = $r->lookup($method, $uri);
|
||||||
|
if ($res !== 200) {
|
||||||
|
echo "Failed to handle request for $uri - $res\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(0);
|
117
tests/tools.php
Normal file
117
tests/tools.php
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// A simple class to return a string wrapped in ANSI color codes for terminal output
|
||||||
|
class Color {
|
||||||
|
const RESET = "\033[0m";
|
||||||
|
const BOLD = "\033[1m";
|
||||||
|
const UNDERLINE = "\033[4m";
|
||||||
|
const INVERSE = "\033[7m";
|
||||||
|
const BLACK = "\033[30m";
|
||||||
|
const RED = "\033[31m";
|
||||||
|
const GREEN = "\033[32m";
|
||||||
|
const YELLOW = "\033[33m";
|
||||||
|
const BLUE = "\033[34m";
|
||||||
|
const MAGENTA = "\033[35m";
|
||||||
|
const CYAN = "\033[36m";
|
||||||
|
const WHITE = "\033[37m";
|
||||||
|
|
||||||
|
private static function format(string $color, string $string): string {
|
||||||
|
return $color . $string . self::RESET;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function bold(string $string): string {
|
||||||
|
return self::format(self::BOLD, $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function underline(string $string): string {
|
||||||
|
return self::format(self::UNDERLINE, $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function inverse(string $string): string {
|
||||||
|
return self::format(self::INVERSE, $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function black(string $string): string {
|
||||||
|
return self::format(self::BLACK, $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function red(string $string): string {
|
||||||
|
return self::format(self::RED, $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function green(string $string): string {
|
||||||
|
return self::format(self::GREEN, $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function yellow(string $string): string {
|
||||||
|
return self::format(self::YELLOW, $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function blue(string $string): string {
|
||||||
|
return self::format(self::BLUE, $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function magenta(string $string): string {
|
||||||
|
return self::format(self::MAGENTA, $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function cyan(string $string): string {
|
||||||
|
return self::format(self::CYAN, $string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function white(string $string): string {
|
||||||
|
return self::format(self::WHITE, $string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function echoMemoryAndTime(int $i, float $start) {
|
||||||
|
echo "(".Color::green("$i lookups").") M: " .
|
||||||
|
Color::blue(round(memory_get_usage() / 1024, 1) . " kb") .
|
||||||
|
" - T: ". Color::blue(number_format(microtime(true) - $start, 10) ." s") .
|
||||||
|
"\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
function echoTitle(string $title) {
|
||||||
|
echo "\n";
|
||||||
|
echo Color::bold(Color::black("===============================================================")) . "\n";
|
||||||
|
echo "\n";
|
||||||
|
echo Color::bold(Color::blue($title))."\n";
|
||||||
|
echo "\n";
|
||||||
|
echo Color::bold(Color::black("===============================================================")) . "\n";
|
||||||
|
echo "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
function readAndAddRoutes(string $file, &$r): array
|
||||||
|
{
|
||||||
|
$array = [];
|
||||||
|
$routes = file($file);
|
||||||
|
foreach ($routes as $route) {
|
||||||
|
[$method, $path] = explode(' ', $route);
|
||||||
|
$path = trim($path);
|
||||||
|
$array[] = [$method, $path];
|
||||||
|
$r->add($method, $path, function() {
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
function runIterations(int $iterations, $r, array $routes) {
|
||||||
|
echo "Running $iterations iterations\n";
|
||||||
|
$start = microtime(true);
|
||||||
|
$interval = $iterations / 10;
|
||||||
|
for ($i = 0; $i < $iterations; $i++) {
|
||||||
|
// pick a random route from the array
|
||||||
|
[$method, $uri] = $routes[array_rand($routes)];
|
||||||
|
$res = $r->lookup($method, $uri);
|
||||||
|
if ($res !== 200) {
|
||||||
|
echo Color::red("Failed to handle request for $uri - $res\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if ($i !== 0 && $i % ($interval) === 0) echoMemoryAndTime($i, $start);
|
||||||
|
}
|
||||||
|
echo "Time: " . Color::cyan(number_format(microtime(true) - $start, 10) . " s\n");
|
||||||
|
// echo the average time per request
|
||||||
|
echo "Avg/lookup: " . Color::yellow(number_format((microtime(true) - $start) / $iterations, 10) . " s\n");
|
||||||
|
echo "\n";
|
||||||
|
}
|
60
tests/trie.php
Normal file
60
tests/trie.php
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test file puts the PATRICIA trie to the test by running a million lookups on a handful
|
||||||
|
of routes. The routes are read from two files, blog.txt and github.txt, and added to two separate
|
||||||
|
routers and two separate arrays. The lookups are then run in two separate loops, one for each router.
|
||||||
|
Each lookup is timed and the memory usage is also printed out at regular intervals.
|
||||||
|
The requests are randomly picked from the array of routes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../TrieRouter.php';
|
||||||
|
require_once 'tools.php';
|
||||||
|
|
||||||
|
// Blog router
|
||||||
|
$b = new TrieRouter();
|
||||||
|
$blog = readAndAddRoutes('blog.txt', $b);
|
||||||
|
|
||||||
|
// Github router
|
||||||
|
$g = new TrieRouter();
|
||||||
|
$github = readAndAddRoutes('github.txt', $g);
|
||||||
|
|
||||||
|
// Big router
|
||||||
|
$big = new TrieRouter();
|
||||||
|
$bigRoutes = readAndAddRoutes('big.txt', $big);
|
||||||
|
|
||||||
|
function runIterations(int $iterations, TrieRouter $r, array $routes) {
|
||||||
|
echo "Running $iterations iterations\n";
|
||||||
|
$start = microtime(true);
|
||||||
|
$interval = $iterations / 10;
|
||||||
|
for ($i = 0; $i < $iterations; $i++) {
|
||||||
|
// pick a random route from the array
|
||||||
|
[$method, $uri] = $routes[array_rand($routes)];
|
||||||
|
$res = $r->handleRequest($method, $uri);
|
||||||
|
if ($res !== true) {
|
||||||
|
echo "Failed to handle request for $uri\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if ($i !== 0 && $i % ($interval) === 0) echoMemoryAndTime($i, $start);
|
||||||
|
}
|
||||||
|
echo "Time: " . Color::cyan(number_format(microtime(true) - $start, 10) . " s\n");
|
||||||
|
// echo the average time per request
|
||||||
|
echo "Avg/lookup: " . Color::yellow(number_format((microtime(true) - $start) / $iterations, 10) . " s\n");
|
||||||
|
echo "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
echoTitle("Starting blog lookups");
|
||||||
|
runIterations(100000, $b, $blog);
|
||||||
|
runIterations(1000000, $b, $blog);
|
||||||
|
|
||||||
|
echoTitle("Starting github lookups");
|
||||||
|
runIterations(10000, $g, $github);
|
||||||
|
runIterations(100000, $g, $github);
|
||||||
|
runIterations(1000000, $g, $github);
|
||||||
|
|
||||||
|
echoTitle("Starting big lookups");
|
||||||
|
runIterations(10000, $big, $bigRoutes);
|
||||||
|
runIterations(100000, $big, $bigRoutes);
|
||||||
|
runIterations(1000000, $big, $bigRoutes);
|
||||||
|
|
||||||
|
exit(0);
|
Loading…
Reference in New Issue
Block a user