2016 September 5
Request routers are not created equal and some actually fail to do their job, like TreeRoute: a router I discovered because it was advertised as being faster than FastRoute. Find out if the request router you're using works correctly with these simple tests.
Before I started the development of my router, I wrote assurance tests as a guideline to ensure that it would work correctly at the end of its development. I compiled some of them as Basic Router Test. You can benefit from them the same way I did if you are developing or have written your own router. Also if you are deciding on which router to use from a selection of routers.
Due to the importance of a request router as the first thing that gets executed in any web application, I believe there should be a standard test for all request routers. Basic Router Test can be the first step in that direction.
I selected several routers to undergo my test and the results are not what I expected. Find out how well they manage below.
Router name | Correctness Test 1 | Correctness Test 2 | Trailing Slash Test |
---|---|---|---|
ReiRouter | PASSED | PASSED | PASSED |
FastRoute | PASSED | PASSED | PASSED |
Phroute | PASSED | PASSED | FAILED |
Pux | PASSED | UNSUPPORTED | PASSED |
Symfony Routing | PASSED | PASSED | PASSED |
TreeRoute | FAILED | UNSUPPORTED | FAILED |
The purpose of this test is to find out if a router can correctly resolve requests to static and dynamic routes, which is the most basic function of a general purpose router. A router that fails this test is unreliable because the failure indicates basic design problems.
This test involves the following routes:
/route/one
/route/{one}/two
/route/{one}/{two}/three
/route/{one}/two/{three}/four
Test subject must be able to resolve the following requests:
/route/one
to Route1./route/one/two
to Route2./route/one/two/three
to Route3./route/one/two/three/four
to Route4.// Example ReiRouter code, adjust as needed for other routers $router->add('/route/one','Route1'); $router->add('/route/:one/two','Route2'); $router->add('/route/:one/:two/three','Route3'); $router->add('/route/:one/two/:three/four','Route4'); $results = [ $router->find('/route/one'), $router->find('/route/one/two'), $router->find('/route/one/two/three'), $router->find('/route/one/two/three/four'), ];
Test results:
Router name | Results | Verdict |
---|---|---|
ReiRouter | Route1, Route2, Route3, Route4 | PASSED |
FastRoute | Route1, Route2, Route3, Route4 | PASSED |
Phroute | Route1, Route2, Route3, Route4 | PASSED |
Pux | Route1, Route2, Route3, Route4 | PASSED |
Symfony Routing | Route1, Route2, Route3, Route4 | PASSED |
TreeRoute | Route1, (not found), (not found), (not found) | FAILED |
Any regex-based router should be able to pass this test easily and they did. Because this test focuses only on the most basic function, I am surprised that any router failed this test.
I included TreeRoute in my test because I found it advertised as being faster than FastRoute but in my opinion being faster with bugs is pointless. If you are using TreeRoute, I strongly recommend replacing it with another router until the problem is fixed.
This test simply adds a catch-all route to the previous test. The purpose of this test is to find out if a router can correctly resolve requests to catch-all routes. A router that claims to support catch-all routes but fails this test is unreliable because the failure also indicates design problems.
This test involves the same routes as Correctness Test 1 with one addition:
/route/*
Test subject must be able to resolve the following request:
/route/one/two/three/four/five
to Route5.// Example ReiRouter code, adjust as needed for other routers $router->add('/route/one','Route1'); $router->add('/route/:one/two','Route2'); $router->add('/route/:one/:two/three','Route3'); $router->add('/route/:one/two/:three/four','Route4'); $router->add('/route/*','Route5'); $result = $router->find('/route/one/two/three/four/five');
Test results:
Router name | Result | Verdict |
---|---|---|
ReiRouter | Route5 | PASSED |
FastRoute | Route5 | PASSED |
Phroute | Route5 | PASSED |
Pux | (not tested) | UNSUPPORTED |
Symfony Routing | Route5 | PASSED |
TreeRoute | (not tested) | UNSUPPORTED |
This is also an easy test for any regex-based router and the result shows. For Pux and TreeRoute, maybe I didn't look hard enough but I can't seem to find a catch-all syntax. It seems to be unsupported, so I don't consider it a failure.
The purpose of this test is to find out if a router can handle a path with trailing slash and a path without trailing slash as distinct. A router that fails this test cannot be used in applications that require distinct handling of trailing slash. Because application developers may have their own requirements regarding trailing slash, a router must not impose its own specific handling. That decision is the right of the application developer, not the router.
This test involves the following routes:
/static/route
/static/route/
/dynamic/{route}
/dynamic/{route}/
Test subject must be able to resolve the following requests:
/static/route
to Route1/static/route/
to Route2/dynamic/route
to Route3/dynamic/route/
to Route4// Example Cog Router code, adjust as needed for other routers $router->add('/static/route', 'Route1'); $router->add('/static/route/', 'Route2'); $router->add('/dynamic/:route', 'Route3'); $router->add('/dynamic/:route/', 'Route4'); $results = [ $router->find('/static/route'), $router->find('/static/route/'), $router->find('/dynamic/route'), $router->find('/dynamic/route/'), ];
Test results:
Router name | Results | Verdict |
---|---|---|
ReiRouter | Route1, Route2, Route3, Route4 | PASSED |
FastRoute | Route1, Route2, Route3, Route4 | PASSED |
Phroute | Route1, Route1, Route3, Route3 | FAILED |
Pux | Route1, Route2, Route3, Route4 | PASSED |
Symfony Routing | Route1, Route2, Route3, Route4 | PASSED |
TreeRoute | Route2, Route2, Route4, Route4 | FAILED |
Without dissecting the source code, I can only guess why Phroute and TreeRoute failed this test. Also, it is surprising to see that Phroute failed because according to its documentation its route matching core is based on FastRoute, which handled this test without a problem.
When I started experimenting on other routers, I was merely confirming my assumption about how they work so I did not expect to release the results. Of course, the results surprised me and now I think there are more routers out there that aren't up to standard. Feel free to use Basic Router Test to weed them out.