diff --git a/examples/simple_example/api_v1_ApiTest.cc b/examples/simple_example/api_v1_ApiTest.cc index 614e0fdc..3da5f79a 100644 --- a/examples/simple_example/api_v1_ApiTest.cc +++ b/examples/simple_example/api_v1_ApiTest.cc @@ -442,4 +442,16 @@ void ApiTest::attributesTest( callback(HttpResponse::newHttpJsonResponse(ret)); return; +} + +void ApiTest::regexTest(const HttpRequestPtr &req, + std::function &&callback, + int p1, + std::string &&p2) +{ + Json::Value ret; + ret["p1"] = p1; + ret["p2"] = std::move(p2); + auto resp = HttpResponse::newHttpJsonResponse(std::move(ret)); + callback(resp); } \ No newline at end of file diff --git a/examples/simple_example/api_v1_ApiTest.h b/examples/simple_example/api_v1_ApiTest.h index e748606b..a921e255 100644 --- a/examples/simple_example/api_v1_ApiTest.h +++ b/examples/simple_example/api_v1_ApiTest.h @@ -34,6 +34,7 @@ class ApiTest : public drogon::HttpController METHOD_ADD(ApiTest::jsonTest, "/json", Post); METHOD_ADD(ApiTest::formTest, "/form", Post); METHOD_ADD(ApiTest::attributesTest, "/attrs", Get); + ADD_METHOD_VIA_REGEX(ApiTest::regexTest, "/reg/([0-9]*)/(.*)", Get); METHOD_LIST_END void get(const HttpRequestPtr &req, @@ -61,6 +62,10 @@ class ApiTest : public drogon::HttpController void attributesTest( const HttpRequestPtr &req, std::function &&callback); + void regexTest(const HttpRequestPtr &req, + std::function &&callback, + int p1, + std::string &&p2); public: ApiTest() diff --git a/examples/simple_example_test/main.cc b/examples/simple_example_test/main.cc index 9d692074..44117e67 100644 --- a/examples/simple_example_test/main.cc +++ b/examples/simple_example_test/main.cc @@ -560,6 +560,34 @@ void doTest(const HttpClientPtr &client, } }); + req = HttpRequest::newHttpRequest(); + req->setMethod(drogon::Get); + req->setPath("/reg/123/rest/of/the/path"); + client->sendRequest( + req, [=](ReqResult result, const HttpResponsePtr &resp) { + if (result == ReqResult::Ok && resp->getJsonObject()) + { + auto &json = resp->getJsonObject(); + if (json->isMember("p1") && json->get("p1", 0).asInt() == 123 && + json->isMember("p2") && + json->get("p2", "").asString() == "rest/of/the/path") + { + outputGood(req, isHttps); + } + else + { + LOG_DEBUG << resp->getBody(); + LOG_ERROR << "Error!"; + exit(1); + } + } + else + { + LOG_ERROR << "Error!"; + exit(1); + } + }); + req = HttpRequest::newHttpRequest(); req->setMethod(drogon::Get); req->setPath("/api/v1/apitest/static"); diff --git a/lib/inc/drogon/HttpAppFramework.h b/lib/inc/drogon/HttpAppFramework.h index 7bfab08d..535988ed 100644 --- a/lib/inc/drogon/HttpAppFramework.h +++ b/lib/inc/drogon/HttpAppFramework.h @@ -388,6 +388,58 @@ class HttpAppFramework : public trantor::NonCopyable pathPattern, binder, validMethods, filters, handlerName); return *this; } + /** + * @brief Register a handler into the framework via a regular expression. + * + * @param regExp A regular expression string, when the path of a http + * request matches the regular expression, the handler indicated by the + * function parameter is called. + * @note When the match is successful, Each string that matches a + * subexpression is sequentially mapped to a handler parameter. + * @param function indicates any type of callable object with a valid + * processing interface. + * @param filtersAndMethods is the same as the third parameter in the above + * method. + * @param handlerName a name for the handler. + * @return HttpAppFramework& + */ + template + HttpAppFramework ®isterHandlerViaRegex( + const std::string ®Exp, + FUNCTION &&function, + const std::vector &filtersAndMethods = + std::vector{}, + const std::string &handlerName = "") + { + LOG_TRACE << "regex:" << regExp; + internal::HttpBinderBasePtr binder; + + binder = std::make_shared>( + std::forward(function)); + + std::vector validMethods; + std::vector filters; + for (auto const &filterOrMethod : filtersAndMethods) + { + if (filterOrMethod.type() == internal::ConstraintType::HttpFilter) + { + filters.push_back(filterOrMethod.getFilterName()); + } + else if (filterOrMethod.type() == + internal::ConstraintType::HttpMethod) + { + validMethods.push_back(filterOrMethod.getHttpMethod()); + } + else + { + LOG_ERROR << "Invalid controller constraint type"; + exit(1); + } + } + registerHttpControllerViaRegex( + regExp, binder, validMethods, filters, handlerName); + return *this; + } /// Register a WebSocketController into the framework. /** @@ -947,6 +999,12 @@ class HttpAppFramework : public trantor::NonCopyable const std::vector &validMethods = std::vector(), const std::vector &filters = std::vector(), const std::string &handlerName = "") = 0; + virtual void registerHttpControllerViaRegex( + const std::string ®Exp, + const internal::HttpBinderBasePtr &binder, + const std::vector &validMethods, + const std::vector &filters, + const std::string &handlerName) = 0; }; /// A wrapper of the instance() method diff --git a/lib/inc/drogon/HttpController.h b/lib/inc/drogon/HttpController.h index c11b382a..6083ad35 100644 --- a/lib/inc/drogon/HttpController.h +++ b/lib/inc/drogon/HttpController.h @@ -34,6 +34,9 @@ #define ADD_METHOD_TO(method, path_pattern, filters...) \ registerMethod(&method, path_pattern, {filters}, false, #method) +#define ADD_METHOD_VIA_REGEX(method, regex, filters...) \ + registerMethodViaRegex(&method, regex, {filters}, #method) + #define METHOD_LIST_END \ return; \ } @@ -108,6 +111,20 @@ class HttpController : public DrObject, public HttpControllerBase } } + template + static void registerMethodViaRegex( + FUNCTION &&function, + const std::string ®Exp, + const std::vector &filtersAndMethods = + std::vector{}, + const std::string &handlerName = "") + { + app().registerHandlerViaRegex(regExp, + std::forward(function), + filtersAndMethods, + handlerName); + } + private: class methodRegistrator { diff --git a/lib/src/HttpAppFrameworkImpl.cc b/lib/src/HttpAppFrameworkImpl.cc index 0d7b5ea7..a29cafe5 100644 --- a/lib/src/HttpAppFrameworkImpl.cc +++ b/lib/src/HttpAppFrameworkImpl.cc @@ -222,6 +222,21 @@ void HttpAppFrameworkImpl::registerHttpController( httpCtrlsRouterPtr_->addHttpPath( pathPattern, binder, validMethods, filters, handlerName); } + +void HttpAppFrameworkImpl::registerHttpControllerViaRegex( + const std::string ®Exp, + const internal::HttpBinderBasePtr &binder, + const std::vector &validMethods, + const std::vector &filters, + const std::string &handlerName) +{ + assert(!regExp.empty()); + assert(binder); + assert(!running_); + httpCtrlsRouterPtr_->addHttpRegex( + regExp, binder, validMethods, filters, handlerName); +} + HttpAppFramework &HttpAppFrameworkImpl::setThreadNum(size_t threadNum) { if (threadNum == 0) diff --git a/lib/src/HttpAppFrameworkImpl.h b/lib/src/HttpAppFrameworkImpl.h index d9a78883..567139e0 100644 --- a/lib/src/HttpAppFrameworkImpl.h +++ b/lib/src/HttpAppFrameworkImpl.h @@ -414,6 +414,12 @@ class HttpAppFrameworkImpl : public HttpAppFramework const std::vector &validMethods = std::vector(), const std::vector &filters = std::vector(), const std::string &handlerName = "") override; + virtual void registerHttpControllerViaRegex( + const std::string ®Exp, + const internal::HttpBinderBasePtr &binder, + const std::vector &validMethods, + const std::vector &filters, + const std::string &handlerName) override; void onAsyncRequest( const HttpRequestImplPtr &req, std::function &&callback); diff --git a/lib/src/HttpControllersRouter.cc b/lib/src/HttpControllersRouter.cc index 6686018a..533cd6e0 100644 --- a/lib/src/HttpControllersRouter.cc +++ b/lib/src/HttpControllersRouter.cc @@ -40,15 +40,10 @@ void HttpControllersRouter::doWhenNoHandlerFound( void HttpControllersRouter::init( const std::vector &ioLoops) { - std::string regString; for (auto &router : ctrlVector_) { - std::regex reg("\\(\\[\\^/\\]\\*\\)"); - std::string tmp = - std::regex_replace(router.pathParameterPattern_, reg, "[^/]*"); router.regex_ = std::regex(router.pathParameterPattern_, std::regex_constants::icase); - regString.append("(").append(tmp).append(")|"); for (auto &binder : router.binders_) { if (binder) @@ -58,10 +53,6 @@ void HttpControllersRouter::init( } } } - if (regString.length() > 0) - regString.resize(regString.length() - 1); // remove the last '|' - LOG_TRACE << "regex string:" << regString; - ctrlRegex_ = std::regex(regString, std::regex_constants::icase); } std::vector> @@ -88,6 +79,66 @@ HttpControllersRouter::getHandlersInfo() const } return ret; } + +void HttpControllersRouter::addHttpRegex( + const std::string ®Exp, + const internal::HttpBinderBasePtr &binder, + const std::vector &validMethods, + const std::vector &filters, + const std::string &handlerName) +{ + auto binderInfo = std::make_shared(); + binderInfo->filterNames_ = filters; + binderInfo->handlerName_ = handlerName; + binderInfo->binderPtr_ = binder; + drogon::app().getLoop()->queueInLoop([binderInfo]() { + // Recreate this with the correct number of threads. + binderInfo->responseCache_ = IOThreadStorage(); + }); + { + for (auto &router : ctrlVector_) + { + if (router.pathParameterPattern_ == regExp) + { + if (validMethods.size() > 0) + { + for (auto const &method : validMethods) + { + router.binders_[method] = binderInfo; + if (method == Options) + binderInfo->isCORS_ = true; + } + } + else + { + binderInfo->isCORS_ = true; + for (int i = 0; i < Invalid; ++i) + router.binders_[i] = binderInfo; + } + return; + } + } + } + struct HttpControllerRouterItem router; + router.pathParameterPattern_ = regExp; + router.pathPattern_ = regExp; + if (validMethods.size() > 0) + { + for (auto const &method : validMethods) + { + router.binders_[method] = binderInfo; + if (method == Options) + binderInfo->isCORS_ = true; + } + } + else + { + binderInfo->isCORS_ = true; + for (int i = 0; i < Invalid; ++i) + router.binders_[i] = binderInfo; + } + ctrlVector_.push_back(std::move(router)); +} void HttpControllersRouter::addHttpPath( const std::string &path, const internal::HttpBinderBasePtr &binder, @@ -312,7 +363,6 @@ void HttpControllersRouter::addHttpPath( binderInfo->responseCache_ = IOThreadStorage(); }); { - std::lock_guard guard(ctrlMutex_); for (auto &router : ctrlVector_) { if (router.pathParameterPattern_ == pathParameterPattern) @@ -354,10 +404,7 @@ void HttpControllersRouter::addHttpPath( for (int i = 0; i < Invalid; ++i) router.binders_[i] = binderInfo; } - { - std::lock_guard guard(ctrlMutex_); - ctrlVector_.push_back(std::move(router)); - } + ctrlVector_.push_back(std::move(router)); } void HttpControllersRouter::route( @@ -365,62 +412,100 @@ void HttpControllersRouter::route( std::function &&callback) { // Find http controller - if (ctrlRegex_.mark_count() > 0) + for (auto &routerItem : ctrlVector_) { std::smatch result; - if (std::regex_match(req->path(), result, ctrlRegex_)) + auto const &ctrlRegex = routerItem.regex_; + if (std::regex_match(req->path(), result, ctrlRegex)) { - for (size_t i = 1; i < result.size(); ++i) + assert(Invalid > req->method()); + req->setMatchedPathPattern(routerItem.pathPattern_); + auto &binder = routerItem.binders_[req->method()]; + if (!binder) { - // TODO: Is there any better way to find the sub-match index - // without using loop? - if (!result[i].matched) - continue; - if (i <= ctrlVector_.size()) + // Invalid Http Method + auto res = drogon::HttpResponse::newHttpResponse(); + if (req->method() != Options) { - size_t ctlIndex = i - 1; - auto &routerItem = ctrlVector_[ctlIndex]; - assert(Invalid > req->method()); - req->setMatchedPathPattern(routerItem.pathPattern_); - auto &binder = routerItem.binders_[req->method()]; - if (!binder) - { - // Invalid Http Method - auto res = drogon::HttpResponse::newHttpResponse(); - if (req->method() != Options) - { - res->setStatusCode(k405MethodNotAllowed); - } - else - { - res->setStatusCode(k403Forbidden); - } - callback(res); - return; - } - if (!postRoutingObservers_.empty()) - { - for (auto &observer : postRoutingObservers_) - { - observer(req); - } - } - if (postRoutingAdvices_.empty()) - { + res->setStatusCode(k405MethodNotAllowed); + } + else + { + res->setStatusCode(k403Forbidden); + } + callback(res); + return; + } + if (!postRoutingObservers_.empty()) + { + for (auto &observer : postRoutingObservers_) + { + observer(req); + } + } + if (postRoutingAdvices_.empty()) + { + if (!binder->filters_.empty()) + { + auto &filters = binder->filters_; + auto callbackPtr = std::make_shared< + std::function>( + std::move(callback)); + filters_function::doFilters( + filters, + req, + callbackPtr, + [=, + &binder, + &routerItem, + result = std::move(result)]() mutable { + doPreHandlingAdvices(binder, + routerItem, + req, + std::move(result), + std::move(*callbackPtr)); + }); + } + else + { + doPreHandlingAdvices(binder, + routerItem, + req, + std::move(result), + std::move(callback)); + } + } + else + { + auto callbackPtr = std::make_shared< + std::function>( + std::move(callback)); + doAdvicesChain( + postRoutingAdvices_, + 0, + req, + callbackPtr, + [&binder, + callbackPtr, + req, + this, + &routerItem, + result = std::move(result)]() mutable { if (!binder->filters_.empty()) { auto &filters = binder->filters_; - auto callbackPtr = std::make_shared< - std::function>( - std::move(callback)); filters_function::doFilters( filters, req, callbackPtr, - [=, &binder, &routerItem]() { + [=, + &binder, + &routerItem, + result = std::move(result)]() mutable { doPreHandlingAdvices(binder, routerItem, req, + std::move(result), std::move( *callbackPtr)); }); @@ -430,69 +515,23 @@ void HttpControllersRouter::route( doPreHandlingAdvices(binder, routerItem, req, - std::move(callback)); + std::move(result), + std::move(*callbackPtr)); } - } - else - { - auto callbackPtr = std::make_shared< - std::function>( - std::move(callback)); - doAdvicesChain(postRoutingAdvices_, - 0, - req, - callbackPtr, - [&binder, - callbackPtr, - req, - this, - &routerItem]() mutable { - if (!binder->filters_.empty()) - { - auto &filters = binder->filters_; - filters_function::doFilters( - filters, - req, - callbackPtr, - [=, &binder, &routerItem]() { - doPreHandlingAdvices( - binder, - routerItem, - req, - std::move( - *callbackPtr)); - }); - } - else - { - doPreHandlingAdvices( - binder, - routerItem, - req, - std::move(*callbackPtr)); - } - }); - } - } + }); } - } - else - { - // No handler found - doWhenNoHandlerFound(req, std::move(callback)); + return; } } - else - { - // No handler found - doWhenNoHandlerFound(req, std::move(callback)); - } + // No handler found + doWhenNoHandlerFound(req, std::move(callback)); } void HttpControllersRouter::doControllerHandler( const CtrlBinderPtr &ctrlBinderPtr, const HttpControllerRouterItem &routerItem, const HttpRequestImplPtr &req, + const std::smatch &matchResult, std::function &&callback) { auto &responsePtr = *(ctrlBinderPtr->responseCache_); @@ -514,18 +553,22 @@ void HttpControllersRouter::doControllerHandler( } std::vector params(ctrlBinderPtr->parameterPlaces_.size()); - std::smatch r; - if (std::regex_match(req->path(), r, routerItem.regex_)) + + for (size_t j = 1; j < matchResult.size(); ++j) { - for (size_t j = 1; j < r.size(); ++j) + if (!matchResult[j].matched) + continue; + size_t place = j; + if (j <= ctrlBinderPtr->parameterPlaces_.size()) { - size_t place = ctrlBinderPtr->parameterPlaces_[j - 1]; - if (place > params.size()) - params.resize(place); - params[place - 1] = r[j].str(); - LOG_TRACE << "place=" << place << " para:" << params[place - 1]; + place = ctrlBinderPtr->parameterPlaces_[j - 1]; } + if (place > params.size()) + params.resize(place); + params[place - 1] = matchResult[j].str(); + LOG_TRACE << "place=" << place << " para:" << params[place - 1]; } + if (ctrlBinderPtr->queryParametersPlaces_.size() > 0) { auto qureyPara = req->getParameters(); @@ -578,6 +621,7 @@ void HttpControllersRouter::doPreHandlingAdvices( const CtrlBinderPtr &ctrlBinderPtr, const HttpControllerRouterItem &routerItem, const HttpRequestImplPtr &req, + std::smatch &&matchResult, std::function &&callback) { if (req->method() == Options) @@ -627,10 +671,8 @@ void HttpControllersRouter::doPreHandlingAdvices( } if (preHandlingAdvices_.empty()) { - doControllerHandler(ctrlBinderPtr, - routerItem, - req, - std::move(callback)); + doControllerHandler( + ctrlBinderPtr, routerItem, req, matchResult, std::move(callback)); } else { @@ -647,10 +689,16 @@ void HttpControllersRouter::doPreHandlingAdvices( resp, *callbackPtr); }), - [this, ctrlBinderPtr, &routerItem, req, callbackPtr]() { + [this, + ctrlBinderPtr, + &routerItem, + req, + callbackPtr, + result = std::move(matchResult)]() { doControllerHandler(ctrlBinderPtr, routerItem, req, + result, std::move(*callbackPtr)); }); } diff --git a/lib/src/HttpControllersRouter.h b/lib/src/HttpControllersRouter.h index 0647a774..7286dbf2 100644 --- a/lib/src/HttpControllersRouter.h +++ b/lib/src/HttpControllersRouter.h @@ -64,6 +64,11 @@ class HttpControllersRouter : public trantor::NonCopyable const std::vector &validMethods, const std::vector &filters, const std::string &handlerName = ""); + void addHttpRegex(const std::string ®Exp, + const internal::HttpBinderBasePtr &binder, + const std::vector &validMethods, + const std::vector &filters, + const std::string &handlerName = ""); void route(const HttpRequestImplPtr &req, std::function &&callback); std::vector> @@ -92,8 +97,6 @@ class HttpControllersRouter : public trantor::NonCopyable nullptr}; // The enum value of Invalid is the http methods number }; std::vector ctrlVector_; - std::mutex ctrlMutex_; - std::regex ctrlRegex_; const std::vector &&callback); void doControllerHandler( const CtrlBinderPtr &ctrlBinderPtr, const HttpControllerRouterItem &routerItem, const HttpRequestImplPtr &req, + const std::smatch &matchResult, std::function &&callback); void invokeCallback( const std::function &callback,