From fdc1701cc8e25766f24c846feb3d22158ed272b8 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Sun, 19 Jun 2016 17:53:48 +0100 Subject: Add and test a request router --- icespider/compile/routeCompiler.cpp | 2 +- icespider/core/core.cpp | 69 ++++++++++++++++++++++++++++++++ icespider/core/core.h | 28 +++++++++++++ icespider/core/ihttpRequest.cpp | 5 +++ icespider/core/ihttpRequest.h | 10 +++++ icespider/core/irouteHandler.cpp | 9 +++-- icespider/core/irouteHandler.h | 10 ++--- icespider/core/paths.cpp | 47 ++++++++++++++++++++++ icespider/core/paths.h | 45 +++++++++++++++++++++ icespider/unittests/testApp.cpp | 78 +++++++++++++++++++++++++++++++++++++ icespider/unittests/testCompile.cpp | 9 +---- icespider/unittests/testRoutes.json | 2 +- 12 files changed, 295 insertions(+), 19 deletions(-) create mode 100644 icespider/core/core.cpp create mode 100644 icespider/core/core.h create mode 100644 icespider/core/paths.cpp create mode 100644 icespider/core/paths.h diff --git a/icespider/compile/routeCompiler.cpp b/icespider/compile/routeCompiler.cpp index 1bf61a7..4881129 100644 --- a/icespider/compile/routeCompiler.cpp +++ b/icespider/compile/routeCompiler.cpp @@ -221,7 +221,7 @@ namespace IceSpider { fprintbf(4, output, "auto _p_%s(request->get%sParam<%s>(_pn_%s));\n", p->name, getEnumString(p->source), Slice::typeToString(ip->type()), p->name); } - fprintbf(4, output, "auto prx = getProxy<%s>();\n", proxyName); + fprintbf(4, output, "auto prx = getProxy<%s>(request);\n", proxyName); if (o->returnsData()) { fprintbf(4, output, "request->response(prx->%s(", operation); } diff --git a/icespider/core/core.cpp b/icespider/core/core.cpp new file mode 100644 index 0000000..7c501c5 --- /dev/null +++ b/icespider/core/core.cpp @@ -0,0 +1,69 @@ +#include "core.h" +#include +#include + +namespace ba = boost::algorithm; + +namespace IceSpider { + Core::Core() + { + // Big enough to map all the request methods + routes.resize(UserIceSpider::HttpMethod::OPTIONS + 1); + // Initialize routes + for (const auto & rp : AdHoc::PluginManager::getDefault()->getAll()) { + auto r = rp->implementation(); + auto & mroutes = routes[r->method]; + if (mroutes.size() <= r->pathElementCount()) { + mroutes.resize(r->pathElementCount() + 1); + } + mroutes[r->pathElementCount()].push_back(r); + } + } + + Core::~Core() + { + } + + void + Core::process(IHttpRequest * request) const + { + auto routeHandler = findRoute(request); + routeHandler->execute(request); + } + + const IRouteHandler * + Core::findRoute(const IHttpRequest * request) const + { + auto path = request->getRequestPath().substr(1); + const auto & mroutes = routes[request->getRequestMethod()]; + std::vector pathparts; + if (!path.empty()) { + ba::split(pathparts, path, ba::is_any_of("/"), ba::token_compress_off); + } + if (pathparts.size() > mroutes.size()) { + // Not found error + return NULL; + } + const auto & routeSet = mroutes[pathparts.size()]; + auto ri = std::find_if(routeSet.begin(), routeSet.end(), [&pathparts](const auto & r) { + auto rpi = r->parts.begin(); + for (auto ppi = pathparts.begin(); ppi != pathparts.end(); ++ppi, ++rpi) { + if (!(*rpi)->matches(*ppi)) return false; + } + return true; + }); + if (ri == routeSet.end()) { + // Not found error + return NULL; + } + return (*ri); + } + + Ice::ObjectPrx + Core::getProxy(const char * type) const + { + fprintf(stderr, "request for proxy type %s\n", type); + return NULL; + } +} + diff --git a/icespider/core/core.h b/icespider/core/core.h new file mode 100644 index 0000000..afca336 --- /dev/null +++ b/icespider/core/core.h @@ -0,0 +1,28 @@ +#ifndef ICESPIDER_CORE_CORE_H +#define ICESPIDER_CORE_CORE_H + +#include +#include +#include "irouteHandler.h" + +namespace IceSpider { + class DLL_PUBLIC Core { + public: + typedef std::vector Routes; + typedef std::vector LengthRoutes; + typedef std::vector MethodRoutes; + + Core(); + ~Core(); + + void process(IHttpRequest *) const; + const IRouteHandler * findRoute(const IHttpRequest *) const; + + Ice::ObjectPrx getProxy(const char * type) const; + + MethodRoutes routes; + }; +} + +#endif + diff --git a/icespider/core/ihttpRequest.cpp b/icespider/core/ihttpRequest.cpp index 423e439..0e14957 100644 --- a/icespider/core/ihttpRequest.cpp +++ b/icespider/core/ihttpRequest.cpp @@ -1,6 +1,11 @@ #include "ihttpRequest.h" namespace IceSpider { + IHttpRequest::IHttpRequest(const Core * c) : + core(c) + { + } + Ice::Context IHttpRequest::getContext() const { diff --git a/icespider/core/ihttpRequest.h b/icespider/core/ihttpRequest.h index 3ea877e..2193a67 100644 --- a/icespider/core/ihttpRequest.h +++ b/icespider/core/ihttpRequest.h @@ -5,11 +5,19 @@ #include #include #include +#include namespace IceSpider { + class Core; + class DLL_PUBLIC IHttpRequest { public: + IHttpRequest(const Core *); + Ice::Context getContext() const; + virtual std::string getRequestPath() const = 0; + virtual UserIceSpider::HttpMethod getRequestMethod() const = 0; + template T getURLParam(const std::string & key) const { (void)key; return T(); } template @@ -20,6 +28,8 @@ namespace IceSpider { T getHeaderParam(const std::string & key) const { (void)key; return T(); } template void response(const T &) const { } + + const Core * core; }; } diff --git a/icespider/core/irouteHandler.cpp b/icespider/core/irouteHandler.cpp index 2107ff7..3007772 100644 --- a/icespider/core/irouteHandler.cpp +++ b/icespider/core/irouteHandler.cpp @@ -1,19 +1,20 @@ #include "irouteHandler.h" +#include "core.h" #include INSTANTIATEPLUGINOF(IceSpider::IRouteHandler); namespace IceSpider { IRouteHandler::IRouteHandler(UserIceSpider::HttpMethod m, const std::string & p) : - method(m), - path(p) + Path(p), + method(m) { } Ice::ObjectPrx - IRouteHandler::getProxy(const char *) const + IRouteHandler::getProxy(IHttpRequest * request, const char * type) const { - return NULL; + return request->core->getProxy(type); } } diff --git a/icespider/core/irouteHandler.h b/icespider/core/irouteHandler.h index e02d0a3..416048e 100644 --- a/icespider/core/irouteHandler.h +++ b/icespider/core/irouteHandler.h @@ -2,26 +2,26 @@ #define ICESPIDER_IROUTEHANDLER_H #include "ihttpRequest.h" +#include "paths.h" #include #include #include namespace IceSpider { - class DLL_PUBLIC IRouteHandler : public AdHoc::AbstractPluginImplementation { + class DLL_PUBLIC IRouteHandler : public AdHoc::AbstractPluginImplementation, public Path { public: IRouteHandler(UserIceSpider::HttpMethod, const std::string & path); virtual void execute(IHttpRequest * request) const = 0; const UserIceSpider::HttpMethod method; - const std::string path; protected: - Ice::ObjectPrx getProxy(const char *) const; + Ice::ObjectPrx getProxy(IHttpRequest *, const char *) const; template - typename Interface::ProxyType getProxy() const + typename Interface::ProxyType getProxy(IHttpRequest * request) const { - return Interface::ProxyType::uncheckedCast(getProxy(typeid(Interface).name())); + return Interface::ProxyType::uncheckedCast(getProxy(request, typeid(Interface).name())); } }; typedef AdHoc::PluginOf RouteHandlers; diff --git a/icespider/core/paths.cpp b/icespider/core/paths.cpp new file mode 100644 index 0000000..f4bad0e --- /dev/null +++ b/icespider/core/paths.cpp @@ -0,0 +1,47 @@ +#include "paths.h" +#include + +namespace ba = boost::algorithm; + +namespace IceSpider { + Path::Path(const std::string & p) : + path(p) + { + auto relp = p.substr(1); + if (relp.empty()) return; + for (auto pi = ba::make_split_iterator(relp, ba::first_finder("/", ba::is_equal())); pi != decltype(pi)(); ++pi) { + std::string pp(pi->begin(), pi->end()); + if (pp.front() == '{' && pp.back() == '}') { + parts.push_back(PathPartPtr(new PathParameter())); + } + else { + parts.push_back(PathPartPtr(new PathLiteral(pp))); + } + } + } + + unsigned int + Path::pathElementCount() const + { + return parts.size(); + } + + PathLiteral::PathLiteral(const std::string & p) : + value(p) + { + + } + + bool + PathLiteral::matches(const std::string & v) const + { + return value == v; + } + + bool + PathParameter::matches(const std::string &) const + { + return true; + } +} + diff --git a/icespider/core/paths.h b/icespider/core/paths.h new file mode 100644 index 0000000..0999593 --- /dev/null +++ b/icespider/core/paths.h @@ -0,0 +1,45 @@ +#ifndef ICESPIDER_CORE_PATHS_H +#define ICESPIDER_CORE_PATHS_H + +#include +#include +#include + +namespace IceSpider { + + class PathPart { + public: + virtual bool matches(const std::string &) const = 0; + }; + typedef std::shared_ptr PathPartPtr; + + class PathLiteral : public PathPart { + public: + PathLiteral(const std::string & v); + + bool matches(const std::string &) const; + + private: + const std::string value; + }; + + class PathParameter : public PathPart { + public: + bool matches(const std::string &) const; + }; + class Path { + public: + typedef std::vector PathParts; + + Path(const std::string &); + + const std::string path; + + unsigned int pathElementCount() const; + + PathParts parts; + }; +} + +#endif + diff --git a/icespider/unittests/testApp.cpp b/icespider/unittests/testApp.cpp index 01249b7..2c4e1b2 100644 --- a/icespider/unittests/testApp.cpp +++ b/icespider/unittests/testApp.cpp @@ -3,9 +3,87 @@ #include #include +#include + +using namespace UserIceSpider; BOOST_AUTO_TEST_CASE( testLoadConfiguration ) { BOOST_REQUIRE_EQUAL(4, AdHoc::PluginManager::getDefault()->getAll().size()); } +BOOST_FIXTURE_TEST_SUITE(c, IceSpider::Core); + +BOOST_AUTO_TEST_CASE( testCoreSettings ) +{ + BOOST_REQUIRE_EQUAL(6, routes.size()); + BOOST_REQUIRE_EQUAL(4, routes[HttpMethod::GET].size()); + BOOST_REQUIRE_EQUAL(1, routes[HttpMethod::GET][0].size()); + BOOST_REQUIRE_EQUAL(0, routes[HttpMethod::GET][1].size()); + BOOST_REQUIRE_EQUAL(0, routes[HttpMethod::GET][2].size()); + BOOST_REQUIRE_EQUAL(1, routes[HttpMethod::GET][3].size()); + BOOST_REQUIRE_EQUAL(0, routes[HttpMethod::HEAD].size()); + BOOST_REQUIRE_EQUAL(2, routes[HttpMethod::POST].size()); + BOOST_REQUIRE_EQUAL(0, routes[HttpMethod::POST][0].size()); + BOOST_REQUIRE_EQUAL(1, routes[HttpMethod::POST][1].size()); + BOOST_REQUIRE_EQUAL(0, routes[HttpMethod::PUT].size()); + BOOST_REQUIRE_EQUAL(2, routes[HttpMethod::DELETE].size()); + BOOST_REQUIRE_EQUAL(0, routes[HttpMethod::DELETE][0].size()); + BOOST_REQUIRE_EQUAL(1, routes[HttpMethod::DELETE][1].size()); + BOOST_REQUIRE_EQUAL(0, routes[HttpMethod::OPTIONS].size()); +} + +class TestRequest : public IceSpider::IHttpRequest { + public: + TestRequest(const IceSpider::Core * c, HttpMethod m, const std::string & p) : + IHttpRequest(c), + method(m), + path(p) + { + } + + std::string getRequestPath() const override + { + return path; + } + + HttpMethod getRequestMethod() const override + { + return method; + } + + const HttpMethod method; + const std::string path; +}; + +BOOST_AUTO_TEST_CASE( testFindRoutes ) +{ + TestRequest requestGetIndex(this, HttpMethod::GET, "/"); + BOOST_REQUIRE(findRoute(&requestGetIndex)); + + TestRequest requestPostIndex(this, HttpMethod::POST, "/"); + BOOST_REQUIRE(!findRoute(&requestPostIndex)); + + TestRequest requestPostUpdate(this, HttpMethod::POST, "/something"); + BOOST_REQUIRE(findRoute(&requestPostUpdate)); + + TestRequest requestGetUpdate(this, HttpMethod::GET, "/something"); + BOOST_REQUIRE(!findRoute(&requestGetUpdate)); + + TestRequest requestGetItem(this, HttpMethod::GET, "/view/something/something"); + BOOST_REQUIRE(findRoute(&requestGetItem)); + + TestRequest requestGetItemLong(this, HttpMethod::GET, "/view/something/something/extra"); + BOOST_REQUIRE(!findRoute(&requestGetItemLong)); + + TestRequest requestGetItemShort(this, HttpMethod::GET, "/view/missingSomething"); + BOOST_REQUIRE(!findRoute(&requestGetItemShort)); + + TestRequest requestGetNothing(this, HttpMethod::GET, "/badview/something/something"); + BOOST_REQUIRE(!findRoute(&requestGetNothing)); + + TestRequest requestDeleteThing(this, HttpMethod::DELETE, "/something"); + BOOST_REQUIRE(findRoute(&requestDeleteThing)); +} + +BOOST_AUTO_TEST_SUITE_END(); diff --git a/icespider/unittests/testCompile.cpp b/icespider/unittests/testCompile.cpp index d46a33d..b7ce0f5 100644 --- a/icespider/unittests/testCompile.cpp +++ b/icespider/unittests/testCompile.cpp @@ -37,7 +37,7 @@ BOOST_AUTO_TEST_CASE( testLoadConfiguration ) BOOST_REQUIRE_EQUAL(0, cfg->routes[0]->params.size()); BOOST_REQUIRE_EQUAL("item", cfg->routes[1]->name); - BOOST_REQUIRE_EQUAL("/{s}/{i}", cfg->routes[1]->path); + BOOST_REQUIRE_EQUAL("/view/{s}/{i}", cfg->routes[1]->path); BOOST_REQUIRE_EQUAL(2, cfg->routes[1]->params.size()); BOOST_REQUIRE_EQUAL("del", cfg->routes[2]->name); @@ -77,10 +77,6 @@ BOOST_AUTO_TEST_CASE( testCompile ) BOOST_REQUIRE_EQUAL(0, compileResult); } -class DuffRequest : public IceSpider::IHttpRequest { - -}; - BOOST_AUTO_TEST_CASE( testLoad ) { auto outputso = binDir / "testRoutes.so"; @@ -93,9 +89,6 @@ BOOST_AUTO_TEST_CASE( testLoad ) { auto route = AdHoc::PluginManager::getDefault()->get("common::index"); BOOST_REQUIRE(route); - BOOST_REQUIRE_THROW({ - route->implementation()->execute(new DuffRequest()); - }, IceUtil::NullHandleException); } BOOST_REQUIRE_EQUAL(0, dlclose(lib)); diff --git a/icespider/unittests/testRoutes.json b/icespider/unittests/testRoutes.json index b4eb54f..2b7b48d 100644 --- a/icespider/unittests/testRoutes.json +++ b/icespider/unittests/testRoutes.json @@ -9,7 +9,7 @@ }, { "name": "item", - "path": "/{s}/{i}", + "path": "/view/{s}/{i}", "method": "GET", "operation": "TestIceSpider.TestApi.withParams" }, -- cgit v1.2.3