Add support for regular expressions when routing (#329)

This commit is contained in:
An Tao 2020-01-09 17:03:30 +08:00 committed by GitHub
parent 668533fbbd
commit 62fc82cba1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 317 additions and 123 deletions

View File

@ -442,4 +442,16 @@ void ApiTest::attributesTest(
callback(HttpResponse::newHttpJsonResponse(ret));
return;
}
void ApiTest::regexTest(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&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);
}

View File

@ -34,6 +34,7 @@ class ApiTest : public drogon::HttpController<ApiTest>
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<ApiTest>
void attributesTest(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void regexTest(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
int p1,
std::string &&p2);
public:
ApiTest()

View File

@ -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");

View File

@ -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 <typename FUNCTION>
HttpAppFramework &registerHandlerViaRegex(
const std::string &regExp,
FUNCTION &&function,
const std::vector<internal::HttpConstraint> &filtersAndMethods =
std::vector<internal::HttpConstraint>{},
const std::string &handlerName = "")
{
LOG_TRACE << "regex:" << regExp;
internal::HttpBinderBasePtr binder;
binder = std::make_shared<internal::HttpBinder<FUNCTION>>(
std::forward<FUNCTION>(function));
std::vector<HttpMethod> validMethods;
std::vector<std::string> 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<HttpMethod> &validMethods = std::vector<HttpMethod>(),
const std::vector<std::string> &filters = std::vector<std::string>(),
const std::string &handlerName = "") = 0;
virtual void registerHttpControllerViaRegex(
const std::string &regExp,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &filters,
const std::string &handlerName) = 0;
};
/// A wrapper of the instance() method

View File

@ -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<T>, public HttpControllerBase
}
}
template <typename FUNCTION>
static void registerMethodViaRegex(
FUNCTION &&function,
const std::string &regExp,
const std::vector<internal::HttpConstraint> &filtersAndMethods =
std::vector<internal::HttpConstraint>{},
const std::string &handlerName = "")
{
app().registerHandlerViaRegex(regExp,
std::forward<FUNCTION>(function),
filtersAndMethods,
handlerName);
}
private:
class methodRegistrator
{

View File

@ -222,6 +222,21 @@ void HttpAppFrameworkImpl::registerHttpController(
httpCtrlsRouterPtr_->addHttpPath(
pathPattern, binder, validMethods, filters, handlerName);
}
void HttpAppFrameworkImpl::registerHttpControllerViaRegex(
const std::string &regExp,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &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)

View File

@ -414,6 +414,12 @@ class HttpAppFrameworkImpl : public HttpAppFramework
const std::vector<HttpMethod> &validMethods = std::vector<HttpMethod>(),
const std::vector<std::string> &filters = std::vector<std::string>(),
const std::string &handlerName = "") override;
virtual void registerHttpControllerViaRegex(
const std::string &regExp,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &filters,
const std::string &handlerName) override;
void onAsyncRequest(
const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);

View File

@ -40,15 +40,10 @@ void HttpControllersRouter::doWhenNoHandlerFound(
void HttpControllersRouter::init(
const std::vector<trantor::EventLoop *> &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<std::tuple<std::string, HttpMethod, std::string>>
@ -88,6 +79,66 @@ HttpControllersRouter::getHandlersInfo() const
}
return ret;
}
void HttpControllersRouter::addHttpRegex(
const std::string &regExp,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &filters,
const std::string &handlerName)
{
auto binderInfo = std::make_shared<CtrlBinder>();
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<HttpResponsePtr>();
});
{
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<HttpResponsePtr>();
});
{
std::lock_guard<std::mutex> 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<std::mutex> 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<void(const HttpResponsePtr &)> &&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<void(const HttpResponsePtr &)>>(
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<void(const HttpResponsePtr &)>>(
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<void(const HttpResponsePtr &)>>(
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<void(const HttpResponsePtr &)>>(
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<void(const HttpResponsePtr &)> &&callback)
{
auto &responsePtr = *(ctrlBinderPtr->responseCache_);
@ -514,18 +553,22 @@ void HttpControllersRouter::doControllerHandler(
}
std::vector<std::string> 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<void(const HttpResponsePtr &)> &&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));
});
}

View File

@ -64,6 +64,11 @@ class HttpControllersRouter : public trantor::NonCopyable
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &filters,
const std::string &handlerName = "");
void addHttpRegex(const std::string &regExp,
const internal::HttpBinderBasePtr &binder,
const std::vector<HttpMethod> &validMethods,
const std::vector<std::string> &filters,
const std::string &handlerName = "");
void route(const HttpRequestImplPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
std::vector<std::tuple<std::string, HttpMethod, std::string>>
@ -92,8 +97,6 @@ class HttpControllersRouter : public trantor::NonCopyable
nullptr}; // The enum value of Invalid is the http methods number
};
std::vector<HttpControllerRouterItem> ctrlVector_;
std::mutex ctrlMutex_;
std::regex ctrlRegex_;
const std::vector<std::function<void(const HttpRequestPtr &,
AdviceCallback &&,
@ -115,12 +118,14 @@ class HttpControllersRouter : public trantor::NonCopyable
const CtrlBinderPtr &ctrlBinderPtr,
const HttpControllerRouterItem &routerItem,
const HttpRequestImplPtr &req,
std::smatch &&matchResult,
std::function<void(const HttpResponsePtr &)> &&callback);
void doControllerHandler(
const CtrlBinderPtr &ctrlBinderPtr,
const HttpControllerRouterItem &routerItem,
const HttpRequestImplPtr &req,
const std::smatch &matchResult,
std::function<void(const HttpResponsePtr &)> &&callback);
void invokeCallback(
const std::function<void(const HttpResponsePtr &)> &callback,