Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 41 additions & 4 deletions tests/test-router.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ struct RouterFixture {
YaHTTP::Router::Get("/test/<object>/<attribute>.<format>", rth.ObjectHandler, "object_attribute_format_get");
YaHTTP::Router::Patch("/test/<object>/<attribute>.<format>", rth.Handler, "object_attribute_format_update");
YaHTTP::Router::Get("/test", rth.Handler, "test_index");
YaHTTP::Router::Put("/test", rth.Handler, "test_put");
YaHTTP::Router::Post("/test", rth.Handler, "test_post");
YaHTTP::Router::Get("/", rth.Handler, "root_path");

// reset routes to false
Expand Down Expand Up @@ -74,7 +76,7 @@ BOOST_AUTO_TEST_CASE( test_router_basic ) {
YaHTTP::THandlerFunction func = rth.NonHandler;
req.setup("get", "http://test.org/");

BOOST_CHECK(YaHTTP::Router::Route(&req, func));
BOOST_CHECK(YaHTTP::Router::Route(&req, func) == YaHTTP::RouteFound);
func(&req, &resp);

// check if it was hit
Expand All @@ -88,7 +90,7 @@ BOOST_AUTO_TEST_CASE( test_router_object ) {
YaHTTP::THandlerFunction func = rth.NonHandler;
req.setup("get", "http://test.org/test/1234/name.json");

BOOST_CHECK(YaHTTP::Router::Route(&req, func));
BOOST_CHECK(YaHTTP::Router::Route(&req, func) == YaHTTP::RouteFound);
func(&req, &resp);

// check if it was hit
Expand All @@ -101,13 +103,37 @@ BOOST_AUTO_TEST_CASE( test_router_glob ) {
YaHTTP::THandlerFunction func = rth.NonHandler;
req.setup("get", "http://test.org/glob/truly and really/everything/there/is.json");

BOOST_CHECK(YaHTTP::Router::Route(&req, func));
BOOST_CHECK(YaHTTP::Router::Route(&req, func) == YaHTTP::RouteFound);
func(&req, &resp);

// check if it was hit
BOOST_CHECK(rth.routes["glob_get"]);
};

BOOST_AUTO_TEST_CASE( test_router_method ) {
YaHTTP::Request req;
YaHTTP::Response resp;
YaHTTP::THandlerFunction func = rth.NonHandler;

req.setup("get", "http://test.org/test");
BOOST_CHECK(YaHTTP::Router::Route(&req, func) == YaHTTP::RouteFound);
func(&req, &resp);
BOOST_CHECK(rth.routes["test_index"]);

req.setup("post", "http://test.org/test");
BOOST_CHECK(YaHTTP::Router::Route(&req, func) == YaHTTP::RouteFound);
func(&req, &resp);
BOOST_CHECK(rth.routes["test_post"]);

req.setup("put", "http://test.org/test");
BOOST_CHECK(YaHTTP::Router::Route(&req, func) == YaHTTP::RouteFound);
func(&req, &resp);
BOOST_CHECK(rth.routes["test_put"]);

req.setup("delete", "http://test.org/test");
BOOST_CHECK(YaHTTP::Router::Route(&req, func) == YaHTTP::RouteNoMethod);
};

BOOST_AUTO_TEST_CASE( test_router_urlFor ) {
YaHTTP::strstr_map_t params;

Expand All @@ -134,7 +160,16 @@ BOOST_AUTO_TEST_CASE( test_router_missing_route ) {
YaHTTP::THandlerFunction func = rth.NonHandler;
req.setup("get", "http://test.org/missing/route");

BOOST_CHECK(!YaHTTP::Router::Route(&req, func));
BOOST_CHECK(YaHTTP::Router::Route(&req, func) == YaHTTP::RouteNotFound);
}

BOOST_AUTO_TEST_CASE( test_router_method_mismatch ) {
YaHTTP::Request req;
YaHTTP::Response resp;
YaHTTP::THandlerFunction func = rth.NonHandler;
req.setup("post", "http://test.org/");

BOOST_CHECK(YaHTTP::Router::Route(&req, func) == YaHTTP::RouteNoMethod);
}

BOOST_AUTO_TEST_CASE( test_router_missing_urlFor ) {
Expand All @@ -152,6 +187,8 @@ BOOST_AUTO_TEST_CASE( test_router_print_routes ) {
GET/test/<object>/<attribute>.<format>object_attribute_format_get\n\
PATCH/test/<object>/<attribute>.<format>object_attribute_format_update\n\
GET/testtest_index\n\
PUT/testtest_put\n\
POST/testtest_post\n\
GET/root_path\n\
");
};
Expand Down
26 changes: 20 additions & 6 deletions yahttp/router.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ namespace YaHTTP {
routes.push_back(funcptr::make_tuple(method2, url, handler, name));
};

bool Router::route(Request *req, THandlerFunction& handler) {
RoutingResult Router::route(Request *req, THandlerFunction& handler) {
std::map<std::string, TDelim> params;
int pos1,pos2;
bool matched = false;
bool seen = false;
std::string rname;

// iterate routes
Expand All @@ -36,8 +37,8 @@ namespace YaHTTP {
std::string pname;
std::string method, url;
funcptr::tie(method, url, handler, rname) = *i;

if (method.empty() == false && req->method != method) continue; // no match on method
matched = false;

// see if we can't match the url
params.clear();
// simple matcher func
Expand Down Expand Up @@ -79,10 +80,23 @@ namespace YaHTTP {
matched = false;
else
matched = true;

if (matched && method.empty() == false && req->method != method) {
// method did not match, record it though so we can return correct result
matched = false;
seen = true;
continue;
}
}

if (!matched) {
if (seen)
return RouteNoMethod;
// no route
return RouteNotFound;
}

if (!matched) { return false; } // no route
req->parameters.clear();
req->parameters.clear();

for(std::map<std::string, TDelim>::iterator i = params.begin(); i != params.end(); i++) {
int p1,p2;
Expand All @@ -94,7 +108,7 @@ namespace YaHTTP {

req->routeName = rname;

return true;
return RouteFound;
};

void Router::printRoutes(std::ostream &os) {
Expand Down
10 changes: 8 additions & 2 deletions yahttp/router.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ namespace funcptr = boost;
#include <utility>

namespace YaHTTP {
enum RoutingResult {
RouteFound = 1,
RouteNotFound = 0,
RouteNoMethod = -1,
};

typedef funcptr::function <void(Request* req, Response* resp)> THandlerFunction; //!< Handler function pointer
typedef funcptr::tuple<std::string, std::string, THandlerFunction, std::string> TRoute; //!< Route tuple (method, urlmask, handler, name)
typedef std::vector<TRoute> TRouteList; //!< List of routes in order of evaluation
Expand All @@ -44,7 +50,7 @@ is consumed but not stored. Note that only path is matched, scheme, host and url
static Router router; //<! Singleton instance of Router
public:
void map(const std::string& method, const std::string& url, THandlerFunction handler, const std::string& name); //<! Instance method for mapping urls
bool route(Request *req, THandlerFunction& handler); //<! Instance method for performing routing
RoutingResult route(Request *req, THandlerFunction& handler); //<! Instance method for performing routing
void printRoutes(std::ostream &os); //<! Instance method for printing routes
std::pair<std::string, std::string> urlFor(const std::string &name, const strstr_map_t& arguments); //<! Instance method for generating paths

Expand All @@ -59,7 +65,7 @@ If method is left empty, it will match any method. Name is also optional, but ne
static void Delete(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("DELETE", url, handler, name); }; //<! Helper for mapping DELETE
static void Any(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("", url, handler, name); }; //<! Helper for mapping any method

static bool Route(Request *req, THandlerFunction& handler) { return router.route(req, handler); }; //<! Performs routing based on req->url.path
static RoutingResult Route(Request *req, THandlerFunction& handler) { return router.route(req, handler); }; //<! Performs routing based on req->url.path, returns RouteFound if route is found and method matches, RouteNoMethod if route is seen but method did match, and RouteNotFound if not found.
static void PrintRoutes(std::ostream &os) { router.printRoutes(os); }; //<! Prints all known routes to given output stream

static std::pair<std::string, std::string> URLFor(const std::string &name, const strstr_map_t& arguments) { return router.urlFor(name,arguments); }; //<! Generates url from named route and arguments. Missing arguments are assumed empty
Expand Down