mirror of
https://gitee.com/an-tao/drogon.git
synced 2024-12-03 12:18:11 +08:00
Optimize HttpControllersRouter for cases where regex is not needed (#883)
This commit is contained in:
parent
6a3f72f2e5
commit
4abbf76214
@ -44,11 +44,8 @@ void HttpControllersRouter::doWhenNoHandlerFound(
|
||||
void HttpControllersRouter::init(
|
||||
const std::vector<trantor::EventLoop *> & /*ioLoops*/)
|
||||
{
|
||||
for (auto &router : ctrlVector_)
|
||||
{
|
||||
router.regex_ = std::regex(router.pathParameterPattern_,
|
||||
std::regex_constants::icase);
|
||||
for (auto &binder : router.binders_)
|
||||
auto initFilters = [](auto &binders) {
|
||||
for (auto &binder : binders)
|
||||
{
|
||||
if (binder)
|
||||
{
|
||||
@ -56,6 +53,21 @@ void HttpControllersRouter::init(
|
||||
filters_function::createFilters(binder->filterNames_);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (auto &router : ctrlVector_)
|
||||
{
|
||||
router.regex_ = std::regex(router.pathParameterPattern_,
|
||||
std::regex_constants::icase);
|
||||
initFilters(router.binders_);
|
||||
}
|
||||
|
||||
for (auto &p : ctrlMap_)
|
||||
{
|
||||
auto &router = p.second;
|
||||
router.regex_ = std::regex(router.pathParameterPattern_,
|
||||
std::regex_constants::icase);
|
||||
initFilters(router.binders_);
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,8 +75,7 @@ std::vector<std::tuple<std::string, HttpMethod, std::string>>
|
||||
HttpControllersRouter::getHandlersInfo() const
|
||||
{
|
||||
std::vector<std::tuple<std::string, HttpMethod, std::string>> ret;
|
||||
for (auto &item : ctrlVector_)
|
||||
{
|
||||
auto gatherInfo = [&](const auto &item) {
|
||||
for (size_t i = 0; i < Invalid; ++i)
|
||||
{
|
||||
if (item.binders_[i])
|
||||
@ -80,6 +91,14 @@ HttpControllersRouter::getHandlersInfo() const
|
||||
ret.emplace_back(std::move(info));
|
||||
}
|
||||
}
|
||||
};
|
||||
for (auto &item : ctrlVector_)
|
||||
{
|
||||
gatherInfo(item);
|
||||
}
|
||||
for (auto &data : ctrlMap_)
|
||||
{
|
||||
gatherInfo(data.second);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@ -104,7 +123,7 @@ void HttpControllersRouter::addHttpRegex(
|
||||
{
|
||||
if (router.pathParameterPattern_ == regExp)
|
||||
{
|
||||
if (validMethods.size() > 0)
|
||||
if (!validMethods.empty())
|
||||
{
|
||||
for (auto const &method : validMethods)
|
||||
{
|
||||
@ -126,7 +145,7 @@ void HttpControllersRouter::addHttpRegex(
|
||||
struct HttpControllerRouterItem router;
|
||||
router.pathParameterPattern_ = regExp;
|
||||
router.pathPattern_ = regExp;
|
||||
if (validMethods.size() > 0)
|
||||
if (!validMethods.empty())
|
||||
{
|
||||
for (auto const &method : validMethods)
|
||||
{
|
||||
@ -153,8 +172,8 @@ void HttpControllersRouter::addHttpPath(
|
||||
// Path is like /api/v1/service/method/{1}/{2}/xxx...
|
||||
std::vector<size_t> places;
|
||||
std::string tmpPath = path;
|
||||
std::string paras = "";
|
||||
std::regex regex = std::regex("\\{([^/]*)\\}");
|
||||
std::string paras;
|
||||
static const std::regex regex("\\{([^/]*)\\}");
|
||||
std::smatch results;
|
||||
auto pos = tmpPath.find('?');
|
||||
if (pos != std::string::npos)
|
||||
@ -174,7 +193,7 @@ void HttpControllersRouter::addHttpPath(
|
||||
return std::isdigit(c);
|
||||
}))
|
||||
{
|
||||
size_t place = (size_t)std::stoi(result);
|
||||
auto place = (size_t)std::stoi(result);
|
||||
if (place > binder->paramCount() || place == 0)
|
||||
{
|
||||
LOG_ERROR << "Parameter placeholder(value=" << place
|
||||
@ -196,13 +215,13 @@ void HttpControllersRouter::addHttpPath(
|
||||
}
|
||||
else
|
||||
{
|
||||
std::regex regNumberAndName("([0-9]+):.*");
|
||||
static const std::regex regNumberAndName("([0-9]+):.*");
|
||||
std::smatch regexResult;
|
||||
if (std::regex_match(result, regexResult, regNumberAndName))
|
||||
{
|
||||
assert(regexResult.size() == 2 && regexResult[1].matched);
|
||||
auto num = regexResult[1].str();
|
||||
size_t place = (size_t)std::stoi(num);
|
||||
auto place = (size_t)std::stoi(num);
|
||||
if (place > binder->paramCount() || place == 0)
|
||||
{
|
||||
LOG_ERROR << "Parameter placeholder(value=" << place
|
||||
@ -247,7 +266,7 @@ void HttpControllersRouter::addHttpPath(
|
||||
std::vector<std::pair<std::string, size_t>> parametersPlaces;
|
||||
if (!paras.empty())
|
||||
{
|
||||
std::regex pregex("([^&]*)=\\{([^&]*)\\}&*");
|
||||
static const std::regex pregex("([^&]*)=\\{([^&]*)\\}&*");
|
||||
while (std::regex_search(paras, results, pregex))
|
||||
{
|
||||
if (results.size() > 2)
|
||||
@ -258,7 +277,7 @@ void HttpControllersRouter::addHttpPath(
|
||||
return std::isdigit(c);
|
||||
}))
|
||||
{
|
||||
size_t place = (size_t)std::stoi(result);
|
||||
auto place = (size_t)std::stoi(result);
|
||||
if (place > binder->paramCount() || place == 0)
|
||||
{
|
||||
LOG_ERROR << "Parameter placeholder(value=" << place
|
||||
@ -296,7 +315,7 @@ void HttpControllersRouter::addHttpPath(
|
||||
assert(regexResult.size() == 2 &&
|
||||
regexResult[1].matched);
|
||||
auto num = regexResult[1].str();
|
||||
size_t place = (size_t)std::stoi(num);
|
||||
auto place = (size_t)std::stoi(num);
|
||||
if (place > binder->paramCount() || place == 0)
|
||||
{
|
||||
LOG_ERROR << "Parameter placeholder(value=" << place
|
||||
@ -367,34 +386,53 @@ void HttpControllersRouter::addHttpPath(
|
||||
// Recreate this with the correct number of threads.
|
||||
binderInfo->responseCache_ = IOThreadStorage<HttpResponsePtr>();
|
||||
});
|
||||
bool routingRequiresRegex = (path != pathParameterPattern);
|
||||
HttpControllerRouterItem *existingRouterItemPtr = nullptr;
|
||||
|
||||
// If exists another controllers on the same route. Updathe them then exit
|
||||
if (routingRequiresRegex)
|
||||
{
|
||||
for (auto &router : ctrlVector_)
|
||||
{
|
||||
if (router.pathParameterPattern_ == pathParameterPattern)
|
||||
{
|
||||
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;
|
||||
}
|
||||
existingRouterItemPtr = &router;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string loweredPath;
|
||||
loweredPath.resize(path.size());
|
||||
std::transform(path.begin(), path.end(), loweredPath.begin(), tolower);
|
||||
auto it = ctrlMap_.find(loweredPath);
|
||||
if (it != ctrlMap_.end())
|
||||
existingRouterItemPtr = &it->second;
|
||||
}
|
||||
|
||||
if (existingRouterItemPtr != nullptr)
|
||||
{
|
||||
auto &router = *existingRouterItemPtr;
|
||||
if (!validMethods.empty())
|
||||
{
|
||||
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_ = pathParameterPattern;
|
||||
router.pathPattern_ = path;
|
||||
if (validMethods.size() > 0)
|
||||
if (!validMethods.empty())
|
||||
{
|
||||
for (auto const &method : validMethods)
|
||||
{
|
||||
@ -409,7 +447,16 @@ void HttpControllersRouter::addHttpPath(
|
||||
for (int i = 0; i < Invalid; ++i)
|
||||
router.binders_[i] = binderInfo;
|
||||
}
|
||||
ctrlVector_.push_back(std::move(router));
|
||||
|
||||
if (routingRequiresRegex)
|
||||
ctrlVector_.push_back(std::move(router));
|
||||
else
|
||||
{
|
||||
std::string loweredPath;
|
||||
loweredPath.resize(path.size());
|
||||
std::transform(path.begin(), path.end(), loweredPath.begin(), tolower);
|
||||
ctrlMap_[loweredPath] = std::move(router);
|
||||
}
|
||||
}
|
||||
|
||||
void HttpControllersRouter::route(
|
||||
@ -417,122 +464,145 @@ void HttpControllersRouter::route(
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
{
|
||||
// Find http controller
|
||||
for (auto &routerItem : ctrlVector_)
|
||||
HttpControllerRouterItem *routerItemPtr = nullptr;
|
||||
std::smatch result;
|
||||
std::string loweredPath = req->path();
|
||||
std::transform(loweredPath.begin(),
|
||||
loweredPath.end(),
|
||||
loweredPath.begin(),
|
||||
tolower);
|
||||
|
||||
auto it = ctrlMap_.find(loweredPath);
|
||||
// Try to find a controller in the hash map. If can't linear search
|
||||
// with regex.
|
||||
if (it != ctrlMap_.end())
|
||||
{
|
||||
std::smatch result;
|
||||
auto const &ctrlRegex = routerItem.regex_;
|
||||
if (std::regex_match(req->path(), result, ctrlRegex))
|
||||
routerItemPtr = &it->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto &item : ctrlVector_)
|
||||
{
|
||||
assert(Invalid > req->method());
|
||||
req->setMatchedPathPattern(routerItem.pathPattern_);
|
||||
auto &binder = routerItem.binders_[req->method()];
|
||||
if (!binder)
|
||||
auto const &ctrlRegex = item.regex_;
|
||||
if (std::regex_match(req->path(), result, ctrlRegex))
|
||||
{
|
||||
// Invalid Http Method
|
||||
if (req->method() != Options)
|
||||
{
|
||||
callback(
|
||||
app().getCustomErrorHandler()(k405MethodNotAllowed));
|
||||
}
|
||||
else
|
||||
{
|
||||
callback(app().getCustomErrorHandler()(k403Forbidden));
|
||||
}
|
||||
return;
|
||||
routerItemPtr = &item;
|
||||
break;
|
||||
}
|
||||
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,
|
||||
[req,
|
||||
callbackPtr,
|
||||
this,
|
||||
&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_;
|
||||
filters_function::doFilters(
|
||||
filters,
|
||||
req,
|
||||
callbackPtr,
|
||||
[this,
|
||||
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(*callbackPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// No handler found
|
||||
doWhenNoHandlerFound(req, std::move(callback));
|
||||
if (routerItemPtr == nullptr)
|
||||
{
|
||||
doWhenNoHandlerFound(req, std::move(callback));
|
||||
return;
|
||||
}
|
||||
|
||||
HttpControllerRouterItem &routerItem = *routerItemPtr;
|
||||
assert(Invalid > req->method());
|
||||
req->setMatchedPathPattern(routerItem.pathPattern_);
|
||||
auto &binder = routerItem.binders_[req->method()];
|
||||
if (!binder)
|
||||
{
|
||||
// Invalid Http Method
|
||||
if (req->method() != Options)
|
||||
{
|
||||
callback(app().getCustomErrorHandler()(k405MethodNotAllowed));
|
||||
}
|
||||
else
|
||||
{
|
||||
callback(app().getCustomErrorHandler()(k403Forbidden));
|
||||
}
|
||||
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,
|
||||
[req,
|
||||
callbackPtr,
|
||||
this,
|
||||
&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_;
|
||||
filters_function::doFilters(
|
||||
filters,
|
||||
req,
|
||||
callbackPtr,
|
||||
[this,
|
||||
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(*callbackPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void HttpControllersRouter::doControllerHandler(
|
||||
@ -620,7 +690,6 @@ void HttpControllersRouter::doControllerHandler(
|
||||
}
|
||||
invokeCallback(callback, req, resp);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
void HttpControllersRouter::doPreHandlingAdvices(
|
||||
|
@ -96,6 +96,7 @@ class HttpControllersRouter : public trantor::NonCopyable
|
||||
CtrlBinderPtr binders_[Invalid]{
|
||||
nullptr}; // The enum value of Invalid is the http methods number
|
||||
};
|
||||
std::unordered_map<std::string, HttpControllerRouterItem> ctrlMap_;
|
||||
std::vector<HttpControllerRouterItem> ctrlVector_;
|
||||
|
||||
const std::vector<std::function<void(const HttpRequestPtr &,
|
||||
|
@ -128,7 +128,7 @@ void doTest(const HttpClientPtr &client, std::shared_ptr<test::Case> TEST_CTX)
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
|
||||
std::shared_ptr<Json::Value> ret = *resp;
|
||||
REQUIRE(resp != nullptr);
|
||||
REQUIRE(ret != nullptr);
|
||||
CHECK((*ret)["result"].asString() == "ok");
|
||||
});
|
||||
// Post json again
|
||||
@ -142,7 +142,7 @@ void doTest(const HttpClientPtr &client, std::shared_ptr<test::Case> TEST_CTX)
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
|
||||
std::shared_ptr<Json::Value> ret = *resp;
|
||||
REQUIRE(resp != nullptr);
|
||||
REQUIRE(ret != nullptr);
|
||||
CHECK((*ret)["result"].asString() == "ok");
|
||||
});
|
||||
|
||||
@ -156,7 +156,7 @@ void doTest(const HttpClientPtr &client, std::shared_ptr<test::Case> TEST_CTX)
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
|
||||
std::shared_ptr<Json::Value> ret = *resp;
|
||||
REQUIRE(resp != nullptr);
|
||||
REQUIRE(ret != nullptr);
|
||||
CHECK((*ret)["result"].asString() == "ok");
|
||||
});
|
||||
|
||||
@ -527,7 +527,7 @@ void doTest(const HttpClientPtr &client, std::shared_ptr<test::Case> TEST_CTX)
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
auto ret = resp->getJsonObject();
|
||||
CHECK(ret != nullptr);
|
||||
REQUIRE(ret != nullptr);
|
||||
CHECK((*ret)["result"].asString() == "ok");
|
||||
});
|
||||
|
||||
@ -540,7 +540,7 @@ void doTest(const HttpClientPtr &client, std::shared_ptr<test::Case> TEST_CTX)
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
auto ret = resp->getJsonObject();
|
||||
CHECK(ret != nullptr);
|
||||
REQUIRE(ret != nullptr);
|
||||
CHECK((*ret)["result"].asString() == "ok");
|
||||
});
|
||||
|
||||
@ -579,7 +579,6 @@ void doTest(const HttpClientPtr &client, std::shared_ptr<test::Case> TEST_CTX)
|
||||
body->end(),
|
||||
resp->getBody().begin()));
|
||||
});
|
||||
// return;
|
||||
// Test file upload
|
||||
UploadFile file1("./drogon.jpg");
|
||||
UploadFile file2("./drogon.jpg", "drogon1.jpg");
|
||||
@ -593,13 +592,12 @@ void doTest(const HttpClientPtr &client, std::shared_ptr<test::Case> TEST_CTX)
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
auto json = resp->getJsonObject();
|
||||
CHECK(json != nullptr);
|
||||
REQUIRE(json != nullptr);
|
||||
CHECK((*json)["result"].asString() == "ok");
|
||||
CHECK((*json)["P1"] == "upload");
|
||||
CHECK((*json)["P2"] == "test");
|
||||
});
|
||||
|
||||
// return;
|
||||
// Test file upload, file type and extension interface.
|
||||
UploadFile image("./drogon.jpg");
|
||||
req = HttpRequest::newFileUploadRequest({image});
|
||||
@ -611,11 +609,12 @@ void doTest(const HttpClientPtr &client, std::shared_ptr<test::Case> TEST_CTX)
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
auto json = resp->getJsonObject();
|
||||
CHECK(json != nullptr);
|
||||
REQUIRE(json != nullptr);
|
||||
CHECK((*json)["P1"] == "upload");
|
||||
CHECK((*json)["P2"] == "test");
|
||||
});
|
||||
|
||||
// Test exception handling
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/this_will_fail");
|
||||
@ -625,6 +624,129 @@ void doTest(const HttpClientPtr &client, std::shared_ptr<test::Case> TEST_CTX)
|
||||
CHECK(resp->getStatusCode() == k500InternalServerError);
|
||||
});
|
||||
|
||||
// The result of this API is cached for (almost) forever. And the endpoint
|
||||
// increments a internal counter on each invoke. This tests if the respond
|
||||
// is taken from the cache after the first invoke.
|
||||
// Try poking the cache test endpoint 3 times. They should all respond 0
|
||||
// since the first respond is cached by the server.
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/ApiTest/cacheTest");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getStatusCode() == k200OK);
|
||||
CHECK(resp->body() == "0");
|
||||
});
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/ApiTest/cacheTest");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getStatusCode() == k200OK);
|
||||
CHECK(resp->body() == "0");
|
||||
});
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/ApiTest/cacheTest");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getStatusCode() == k200OK);
|
||||
CHECK(resp->body() == "0");
|
||||
});
|
||||
|
||||
// This API caches it's result on the third (counting from 1) calls. Thus
|
||||
// we expect to always see 2 upon the third call. And all previous calls
|
||||
// should be less than or equal to 2, as another test is also poking the API
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/ApiTest/cacheTest2");
|
||||
client->sendRequest(
|
||||
req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getStatusCode() == k200OK);
|
||||
int n;
|
||||
CHECK_NOTHROW(n = std::stoi(std::string(resp->body())));
|
||||
CHECK(n <= 2);
|
||||
});
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/ApiTest/cacheTest2");
|
||||
client->sendRequest(
|
||||
req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getStatusCode() == k200OK);
|
||||
int n;
|
||||
CHECK_NOTHROW(n = std::stoi(std::string(resp->body())));
|
||||
CHECK(n <= 2);
|
||||
});
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/ApiTest/cacheTest2");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getStatusCode() == k200OK);
|
||||
CHECK(resp->body() == "2");
|
||||
});
|
||||
|
||||
// Same as cacheTest2. But the server has to handle this API through regex.
|
||||
// it is intentionally made that the final part of the path can't conatin
|
||||
// a "z" character
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/cacheTestRegex/foobar");
|
||||
client->sendRequest(
|
||||
req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getStatusCode() == k200OK);
|
||||
int n;
|
||||
CHECK_NOTHROW(n = std::stoi(std::string(resp->body())));
|
||||
CHECK(n <= 2);
|
||||
});
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/cacheTestRegex/deadbeef");
|
||||
client->sendRequest(
|
||||
req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getStatusCode() == k200OK);
|
||||
int n;
|
||||
CHECK_NOTHROW(n = std::stoi(std::string(resp->body())));
|
||||
CHECK(n <= 2);
|
||||
});
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/cacheTestRegex/leet");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getStatusCode() == k200OK);
|
||||
CHECK(resp->body() == "2");
|
||||
});
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/cacheTestRegex/zebra");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getStatusCode() == k404NotFound);
|
||||
});
|
||||
|
||||
#if defined(__cpp_impl_coroutine)
|
||||
sync_wait([client, TEST_CTX]() -> Task<> {
|
||||
// Test coroutine requests
|
||||
|
@ -453,4 +453,58 @@ void ApiTest::regexTest(const HttpRequestPtr &req,
|
||||
ret["p2"] = std::move(p2);
|
||||
auto resp = HttpResponse::newHttpJsonResponse(std::move(ret));
|
||||
callback(resp);
|
||||
}
|
||||
}
|
||||
|
||||
static std::mutex cacheTestMtx;
|
||||
void ApiTest::cacheTest(const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(cacheTestMtx);
|
||||
static size_t callCount = 0;
|
||||
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
resp->setBody(std::to_string(callCount));
|
||||
resp->setContentTypeCode(CT_TEXT_PLAIN);
|
||||
// Expire after a millennia
|
||||
resp->setExpiredTime(31536000000);
|
||||
callback(resp);
|
||||
callCount++;
|
||||
}
|
||||
|
||||
static std::mutex cacheTest2Mtx;
|
||||
void ApiTest::cacheTest2(
|
||||
const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(cacheTest2Mtx);
|
||||
static size_t callCount = 0;
|
||||
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
LOG_ERROR << callCount;
|
||||
resp->setBody(std::to_string(callCount));
|
||||
resp->setContentTypeCode(CT_TEXT_PLAIN);
|
||||
// Expire after a millennia
|
||||
if (callCount >= 2)
|
||||
resp->setExpiredTime(31536000000);
|
||||
callback(resp);
|
||||
callCount++;
|
||||
}
|
||||
|
||||
static std::mutex regexCacheApiMtx;
|
||||
void ApiTest::cacheTestRegex(
|
||||
const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(regexCacheApiMtx);
|
||||
static size_t callCount = 0;
|
||||
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
LOG_ERROR << callCount;
|
||||
resp->setBody(std::to_string(callCount));
|
||||
resp->setContentTypeCode(CT_TEXT_PLAIN);
|
||||
// Expire after a millennia
|
||||
if (callCount >= 2)
|
||||
resp->setExpiredTime(31536000000);
|
||||
callback(resp);
|
||||
callCount++;
|
||||
}
|
||||
|
@ -37,6 +37,11 @@ class ApiTest : public drogon::HttpController<ApiTest>
|
||||
METHOD_ADD(ApiTest::formTest, "/form", Post);
|
||||
METHOD_ADD(ApiTest::attributesTest, "/attrs", Get);
|
||||
ADD_METHOD_VIA_REGEX(ApiTest::regexTest, "/reg/([0-9]*)/(.*)", Get);
|
||||
METHOD_ADD(ApiTest::cacheTest, "/cacheTest", Get);
|
||||
METHOD_ADD(ApiTest::cacheTest2, "/cacheTest2", Get);
|
||||
ADD_METHOD_VIA_REGEX(ApiTest::cacheTestRegex,
|
||||
"/cacheTestRegex/[a-y]+",
|
||||
Get);
|
||||
METHOD_LIST_END
|
||||
|
||||
void get(const HttpRequestPtr &req,
|
||||
@ -73,6 +78,13 @@ class ApiTest : public drogon::HttpController<ApiTest>
|
||||
{
|
||||
app().quit();
|
||||
}
|
||||
void cacheTest(const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback);
|
||||
void cacheTest2(const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback);
|
||||
void cacheTestRegex(
|
||||
const HttpRequestPtr &req,
|
||||
std::function<void(const HttpResponsePtr &)> &&callback);
|
||||
|
||||
public:
|
||||
ApiTest()
|
||||
|
Loading…
Reference in New Issue
Block a user