Merge pull request #28 from an-tao/dev
Split the HttpAppFrameworkImpl class
This commit is contained in:
commit
d1ae4fcc2d
|
@ -3,14 +3,15 @@ using namespace example;
|
|||
void WebSocketTest::handleNewMessage(const WebSocketConnectionPtr &wsConnPtr, std::string &&message)
|
||||
{
|
||||
//write your application logic here
|
||||
LOG_TRACE << "new websocket message:" << message;
|
||||
LOG_DEBUG << "new websocket message:" << message;
|
||||
}
|
||||
void WebSocketTest::handleConnectionClosed(const WebSocketConnectionPtr &)
|
||||
{
|
||||
LOG_TRACE << "websocket closed!";
|
||||
LOG_DEBUG << "websocket closed!";
|
||||
}
|
||||
void WebSocketTest::handleNewConnection(const HttpRequestPtr &,
|
||||
const WebSocketConnectionPtr &)
|
||||
const WebSocketConnectionPtr &conn)
|
||||
{
|
||||
LOG_TRACE << "new websocket connection!";
|
||||
LOG_DEBUG << "new websocket connection!";
|
||||
conn->send("haha!!!");
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
GIT_VER=`git log|grep ^commit|wc -l|sed -e "s/^ *//"`
|
||||
MD5=`git log|head -1|awk '{printf $2}'`
|
||||
TMP_FILE=/tmp/version
|
||||
echo "#define VERSION \"0.9.14.$GIT_VER\"" > ${TMP_FILE}
|
||||
echo "#define VERSION \"0.9.15.$GIT_VER\"" > ${TMP_FILE}
|
||||
echo "#define VERSION_MD5 \"$MD5\"" >> ${TMP_FILE}
|
||||
if [ ! -f $1 ];then
|
||||
mv -f ${TMP_FILE} $1
|
||||
|
|
|
@ -107,6 +107,9 @@ class HttpResponse
|
|||
}
|
||||
|
||||
virtual HttpStatusCode statusCode() = 0;
|
||||
|
||||
virtual const trantor::Date &createDate() const = 0;
|
||||
|
||||
virtual void setStatusCode(HttpStatusCode code) = 0;
|
||||
|
||||
virtual void setStatusCode(HttpStatusCode code, const std::string &status_message) = 0;
|
||||
|
|
|
@ -22,11 +22,7 @@
|
|||
#include <drogon/CacheMap.h>
|
||||
#include <drogon/Session.h>
|
||||
#include <trantor/utils/AsyncFileLogger.h>
|
||||
#ifdef USE_OPENSSL
|
||||
#include <openssl/sha.h>
|
||||
#else
|
||||
#include "ssl_funcs/Sha1.h"
|
||||
#endif
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
@ -109,184 +105,20 @@ void HttpAppFrameworkImpl::setFileTypes(const std::vector<std::string> &types)
|
|||
_fileTypeSet.insert(type);
|
||||
}
|
||||
}
|
||||
void HttpAppFrameworkImpl::initRegex()
|
||||
{
|
||||
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(")|");
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
void HttpAppFrameworkImpl::registerWebSocketController(const std::string &pathName,
|
||||
const std::string &ctrlName,
|
||||
const std::vector<std::string> &filters)
|
||||
{
|
||||
assert(!pathName.empty());
|
||||
assert(!ctrlName.empty());
|
||||
std::string path(pathName);
|
||||
std::transform(pathName.begin(), pathName.end(), path.begin(), tolower);
|
||||
auto objPtr = std::shared_ptr<DrObjectBase>(DrClassMap::newObject(ctrlName));
|
||||
auto ctrlPtr = std::dynamic_pointer_cast<WebSocketControllerBase>(objPtr);
|
||||
assert(ctrlPtr);
|
||||
std::lock_guard<std::mutex> guard(_websockCtrlMutex);
|
||||
|
||||
_websockCtrlMap[path].controller = ctrlPtr;
|
||||
_websockCtrlMap[path].filtersName = filters;
|
||||
_websockCtrlsRouter.registerWebSocketController(pathName, ctrlName, filters);
|
||||
}
|
||||
void HttpAppFrameworkImpl::registerHttpSimpleController(const std::string &pathName,
|
||||
const std::string &ctrlName,
|
||||
const std::vector<any> &filtersAndMethods)
|
||||
{
|
||||
assert(!pathName.empty());
|
||||
assert(!ctrlName.empty());
|
||||
|
||||
std::string path(pathName);
|
||||
std::transform(pathName.begin(), pathName.end(), path.begin(), tolower);
|
||||
std::lock_guard<std::mutex> guard(_simpCtrlMutex);
|
||||
std::vector<HttpMethod> validMethods;
|
||||
std::vector<std::string> filters;
|
||||
for (auto &filterOrMethod : filtersAndMethods)
|
||||
{
|
||||
if (filterOrMethod.type() == typeid(std::string))
|
||||
{
|
||||
filters.push_back(*any_cast<std::string>(&filterOrMethod));
|
||||
}
|
||||
else if (filterOrMethod.type() == typeid(const char *))
|
||||
{
|
||||
filters.push_back(*any_cast<const char *>(&filterOrMethod));
|
||||
}
|
||||
else if (filterOrMethod.type() == typeid(HttpMethod))
|
||||
{
|
||||
validMethods.push_back(*any_cast<HttpMethod>(&filterOrMethod));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Invalid controller constraint type:" << filterOrMethod.type().name() << std::endl;
|
||||
LOG_ERROR << "Invalid controller constraint type";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
auto &iterm = _simpCtrlMap[path];
|
||||
iterm.controllerName = ctrlName;
|
||||
iterm.filtersName = filters;
|
||||
iterm._validMethodsFlags.clear(); //There may be old data, first clear
|
||||
if (validMethods.size() > 0)
|
||||
{
|
||||
iterm._validMethodsFlags.resize(Invalid, 0);
|
||||
for (auto method : validMethods)
|
||||
{
|
||||
iterm._validMethodsFlags[method] = 1;
|
||||
}
|
||||
}
|
||||
_httpSimpleCtrlsRouter.registerHttpSimpleController(pathName, ctrlName, filtersAndMethods);
|
||||
}
|
||||
void HttpAppFrameworkImpl::addHttpPath(const std::string &path,
|
||||
const internal::HttpBinderBasePtr &binder,
|
||||
const std::vector<HttpMethod> &validMethods,
|
||||
const std::vector<std::string> &filters)
|
||||
{
|
||||
//path will be 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("\\{([0-9]+)\\}");
|
||||
std::smatch results;
|
||||
auto pos = tmpPath.find("?");
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
paras = tmpPath.substr(pos + 1);
|
||||
tmpPath = tmpPath.substr(0, pos);
|
||||
}
|
||||
std::string originPath = tmpPath;
|
||||
while (std::regex_search(tmpPath, results, regex))
|
||||
{
|
||||
if (results.size() > 1)
|
||||
{
|
||||
size_t place = (size_t)std::stoi(results[1].str());
|
||||
if (place > binder->paramCount() || place == 0)
|
||||
{
|
||||
LOG_ERROR << "parameter placeholder(value=" << place << ") out of range (1 to "
|
||||
<< binder->paramCount() << ")";
|
||||
exit(0);
|
||||
}
|
||||
places.push_back(place);
|
||||
}
|
||||
tmpPath = results.suffix();
|
||||
}
|
||||
std::map<std::string, size_t> parametersPlaces;
|
||||
if (!paras.empty())
|
||||
{
|
||||
std::regex pregex("([^&]*)=\\{([0-9]+)\\}&*");
|
||||
while (std::regex_search(paras, results, pregex))
|
||||
{
|
||||
if (results.size() > 2)
|
||||
{
|
||||
size_t place = (size_t)std::stoi(results[2].str());
|
||||
if (place > binder->paramCount() || place == 0)
|
||||
{
|
||||
LOG_ERROR << "parameter placeholder(value=" << place << ") out of range (1 to "
|
||||
<< binder->paramCount() << ")";
|
||||
exit(0);
|
||||
}
|
||||
parametersPlaces[results[1].str()] = place;
|
||||
}
|
||||
paras = results.suffix();
|
||||
}
|
||||
}
|
||||
auto pathParameterPattern = std::regex_replace(originPath, regex, "([^/]*)");
|
||||
auto binderInfo = CtrlBinderPtr(new CtrlBinder);
|
||||
binderInfo->filtersName = filters;
|
||||
binderInfo->binderPtr = binder;
|
||||
binderInfo->parameterPlaces = std::move(places);
|
||||
binderInfo->queryParametersPlaces = std::move(parametersPlaces);
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(_ctrlMutex);
|
||||
for (auto &router : _ctrlVector)
|
||||
{
|
||||
if (router.pathParameterPattern == pathParameterPattern)
|
||||
{
|
||||
if (validMethods.size() > 0)
|
||||
{
|
||||
for (auto method : validMethods)
|
||||
{
|
||||
router._binders[method] = binderInfo;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Invalid; i++)
|
||||
router._binders[i] = binderInfo;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct HttpControllerRouterItem router;
|
||||
router.pathParameterPattern = pathParameterPattern;
|
||||
if (validMethods.size() > 0)
|
||||
{
|
||||
for (auto method : validMethods)
|
||||
{
|
||||
router._binders[method] = binderInfo;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Invalid; i++)
|
||||
router._binders[i] = binderInfo;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(_ctrlMutex);
|
||||
_ctrlVector.push_back(std::move(router));
|
||||
}
|
||||
}
|
||||
void HttpAppFrameworkImpl::registerHttpController(const std::string &pathPattern,
|
||||
const internal::HttpBinderBasePtr &binder,
|
||||
const std::vector<HttpMethod> &validMethods,
|
||||
|
@ -295,9 +127,7 @@ void HttpAppFrameworkImpl::registerHttpController(const std::string &pathPattern
|
|||
assert(!pathPattern.empty());
|
||||
assert(binder);
|
||||
std::string path(pathPattern);
|
||||
|
||||
//std::transform(path.begin(), path.end(), path.begin(), tolower);
|
||||
addHttpPath(path, binder, validMethods, filters);
|
||||
_httpCtrlsRouter.addHttpPath(path, binder, validMethods, filters);
|
||||
}
|
||||
void HttpAppFrameworkImpl::setThreadNum(size_t threadNum)
|
||||
{
|
||||
|
@ -445,7 +275,7 @@ void HttpAppFrameworkImpl::run()
|
|||
}
|
||||
std::vector<std::shared_ptr<HttpServer>> servers;
|
||||
std::vector<std::shared_ptr<EventLoopThread>> loopThreads;
|
||||
initRegex();
|
||||
_httpCtrlsRouter.init();
|
||||
for (auto listener : _listeners)
|
||||
{
|
||||
LOG_TRACE << "thread num=" << _threadNum;
|
||||
|
@ -543,7 +373,7 @@ void HttpAppFrameworkImpl::run()
|
|||
_sessionMapPtr = std::unique_ptr<CacheMap<std::string, SessionPtr>>(new CacheMap<std::string, SessionPtr>(&_loop, 0, 0, 0));
|
||||
}
|
||||
}
|
||||
_responseCacheMap = std::unique_ptr<CacheMap<std::string, HttpResponsePtr>>(new CacheMap<std::string, HttpResponsePtr>(&_loop, 1.0, 4, 50)); //Max timeout up to about 70 days;
|
||||
_responseCachingMap = std::unique_ptr<CacheMap<std::string, HttpResponsePtr>>(new CacheMap<std::string, HttpResponsePtr>(&_loop, 1.0, 4, 50)); //Max timeout up to about 70 days;
|
||||
_loop.loop();
|
||||
}
|
||||
|
||||
|
@ -748,47 +578,7 @@ void HttpAppFrameworkImpl::onNewWebsockRequest(const HttpRequestImplPtr &req,
|
|||
const std::function<void(const HttpResponsePtr &)> &callback,
|
||||
const WebSocketConnectionPtr &wsConnPtr)
|
||||
{
|
||||
std::string wsKey = req->getHeaderBy("sec-websocket-key");
|
||||
if (!wsKey.empty())
|
||||
{
|
||||
// magic="258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
WebSocketControllerBasePtr ctrlPtr;
|
||||
std::vector<std::string> filtersName;
|
||||
{
|
||||
std::string pathLower(req->path());
|
||||
std::transform(pathLower.begin(), pathLower.end(), pathLower.begin(), tolower);
|
||||
std::lock_guard<std::mutex> guard(_websockCtrlMutex);
|
||||
if (_websockCtrlMap.find(pathLower) != _websockCtrlMap.end())
|
||||
{
|
||||
ctrlPtr = _websockCtrlMap[pathLower].controller;
|
||||
filtersName = _websockCtrlMap[pathLower].filtersName;
|
||||
}
|
||||
}
|
||||
if (ctrlPtr)
|
||||
{
|
||||
doFilters(filtersName, req, callback, false, "", [=]() mutable {
|
||||
wsKey.append("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
|
||||
unsigned char accKey[SHA_DIGEST_LENGTH];
|
||||
SHA1(reinterpret_cast<const unsigned char *>(wsKey.c_str()), wsKey.length(), accKey);
|
||||
auto base64Key = base64Encode(accKey, SHA_DIGEST_LENGTH);
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
resp->setStatusCode(HttpResponse::k101SwitchingProtocols);
|
||||
resp->addHeader("Upgrade", "websocket");
|
||||
resp->addHeader("Connection", "Upgrade");
|
||||
resp->addHeader("Sec-WebSocket-Accept", base64Key);
|
||||
callback(resp);
|
||||
auto wsConnImplPtr = std::dynamic_pointer_cast<WebSocketConnectionImpl>(wsConnPtr);
|
||||
assert(wsConnImplPtr);
|
||||
wsConnImplPtr->setController(ctrlPtr);
|
||||
ctrlPtr->handleNewConnection(req, wsConnPtr);
|
||||
return;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto resp = drogon::HttpResponse::newNotFoundResponse();
|
||||
resp->setCloseConnection(true);
|
||||
callback(resp);
|
||||
_websockCtrlsRouter.route(req, callback, wsConnPtr);
|
||||
}
|
||||
void HttpAppFrameworkImpl::onAsyncRequest(const HttpRequestImplPtr &req, const std::function<void(const HttpResponsePtr &)> &callback)
|
||||
{
|
||||
|
@ -985,233 +775,11 @@ void HttpAppFrameworkImpl::onAsyncRequest(const HttpRequestImplPtr &req, const s
|
|||
}
|
||||
}
|
||||
|
||||
/*find simple controller*/
|
||||
std::string pathLower(req->path());
|
||||
std::transform(pathLower.begin(), pathLower.end(), pathLower.begin(), tolower);
|
||||
|
||||
if (_simpCtrlMap.find(pathLower) != _simpCtrlMap.end())
|
||||
{
|
||||
auto &ctrlInfo = _simpCtrlMap[pathLower];
|
||||
if (ctrlInfo._validMethodsFlags.size() > 0)
|
||||
{
|
||||
assert(ctrlInfo._validMethodsFlags.size() > req->method());
|
||||
if (ctrlInfo._validMethodsFlags[req->method()] == 0)
|
||||
{
|
||||
//Invalid Http Method
|
||||
auto res = drogon::HttpResponse::newHttpResponse();
|
||||
res->setStatusCode(HttpResponse::k405MethodNotAllowed);
|
||||
callback(res);
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto &filters = ctrlInfo.filtersName;
|
||||
doFilters(filters, req, callback, needSetJsessionid, session_id, [=]() {
|
||||
auto &ctrlItem = _simpCtrlMap[pathLower];
|
||||
const std::string &ctrlName = ctrlItem.controllerName;
|
||||
std::shared_ptr<HttpSimpleControllerBase> controller;
|
||||
HttpResponsePtr responsePtr;
|
||||
{
|
||||
//maybe update controller,so we use lock_guard to protect;
|
||||
std::lock_guard<std::mutex> guard(ctrlItem._mutex);
|
||||
controller = ctrlItem.controller;
|
||||
responsePtr = ctrlItem.responsePtr.lock();
|
||||
if (!controller)
|
||||
{
|
||||
auto _object = std::shared_ptr<DrObjectBase>(DrClassMap::newObject(ctrlName));
|
||||
controller = std::dynamic_pointer_cast<HttpSimpleControllerBase>(_object);
|
||||
ctrlItem.controller = controller;
|
||||
}
|
||||
}
|
||||
|
||||
if (controller)
|
||||
{
|
||||
if (responsePtr)
|
||||
{
|
||||
//use cached response!
|
||||
LOG_TRACE << "Use cached response";
|
||||
if (!needSetJsessionid)
|
||||
callback(responsePtr);
|
||||
else
|
||||
{
|
||||
//make a copy response;
|
||||
auto newResp = std::make_shared<HttpResponseImpl>(*std::dynamic_pointer_cast<HttpResponseImpl>(responsePtr));
|
||||
newResp->setExpiredTime(-1); //make it temporary
|
||||
newResp->addCookie("JSESSIONID", session_id);
|
||||
callback(newResp);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
controller->asyncHandleHttpRequest(req, [=](const HttpResponsePtr &resp) {
|
||||
auto newResp = resp;
|
||||
if (resp->expiredTime() >= 0)
|
||||
{
|
||||
//cache the response;
|
||||
std::dynamic_pointer_cast<HttpResponseImpl>(resp)->makeHeaderString();
|
||||
{
|
||||
auto &item = _simpCtrlMap[pathLower];
|
||||
std::lock_guard<std::mutex> guard(item._mutex);
|
||||
_responseCacheMap->insert(pathLower, resp, resp->expiredTime());
|
||||
item.responsePtr = resp;
|
||||
}
|
||||
}
|
||||
if (needSetJsessionid)
|
||||
{
|
||||
//make a copy
|
||||
newResp = std::make_shared<HttpResponseImpl>(*std::dynamic_pointer_cast<HttpResponseImpl>(resp));
|
||||
newResp->setExpiredTime(-1); //make it temporary
|
||||
newResp->addCookie("JSESSIONID", session_id);
|
||||
}
|
||||
|
||||
callback(newResp);
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "can't find controller " << ctrlName;
|
||||
}
|
||||
});
|
||||
//find simple controller
|
||||
if (_httpSimpleCtrlsRouter.route(req, callback, needSetJsessionid, session_id))
|
||||
return;
|
||||
}
|
||||
//find http controller
|
||||
if (_ctrlRegex.mark_count() > 0)
|
||||
{
|
||||
std::smatch result;
|
||||
if (std::regex_match(req->path(), result, _ctrlRegex))
|
||||
{
|
||||
for (size_t i = 1; i < result.size(); i++)
|
||||
{
|
||||
//FIXME:Is there any better way to find the sub-match index without using loop?
|
||||
if (!result[i].matched)
|
||||
continue;
|
||||
if (result[i].str() == req->path() && i <= _ctrlVector.size())
|
||||
{
|
||||
size_t ctlIndex = i - 1;
|
||||
auto &router = _ctrlVector[ctlIndex];
|
||||
//LOG_TRACE << "got http access,regex=" << binder.pathParameterPattern;
|
||||
assert(Invalid > req->method());
|
||||
auto &binder = router._binders[req->method()];
|
||||
if (!binder)
|
||||
{
|
||||
//Invalid Http Method
|
||||
auto res = drogon::HttpResponse::newHttpResponse();
|
||||
res->setStatusCode(HttpResponse::k405MethodNotAllowed);
|
||||
callback(res);
|
||||
return;
|
||||
}
|
||||
|
||||
auto &filters = binder->filtersName;
|
||||
doFilters(filters, req, callback, needSetJsessionid, session_id, [=]() {
|
||||
HttpResponsePtr responsePtr;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(*(binder->binderMtx));
|
||||
responsePtr = binder->responsePtr.lock();
|
||||
}
|
||||
if (responsePtr)
|
||||
{
|
||||
//use cached response!
|
||||
LOG_TRACE << "Use cached response";
|
||||
|
||||
if (!needSetJsessionid)
|
||||
callback(responsePtr);
|
||||
else
|
||||
{
|
||||
//make a copy response;
|
||||
auto newResp = std::make_shared<HttpResponseImpl>(*std::dynamic_pointer_cast<HttpResponseImpl>(responsePtr));
|
||||
newResp->setExpiredTime(-1); //make it temporary
|
||||
newResp->addCookie("JSESSIONID", session_id);
|
||||
callback(newResp);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> params(binder->parameterPlaces.size());
|
||||
std::smatch r;
|
||||
if (std::regex_match(req->path(), r, router._regex))
|
||||
{
|
||||
for (size_t j = 1; j < r.size(); j++)
|
||||
{
|
||||
size_t place = binder->parameterPlaces[j - 1];
|
||||
if (place > params.size())
|
||||
params.resize(place);
|
||||
params[place - 1] = r[j].str();
|
||||
LOG_TRACE << "place=" << place << " para:" << params[place - 1];
|
||||
}
|
||||
}
|
||||
if (binder->queryParametersPlaces.size() > 0)
|
||||
{
|
||||
auto qureyPara = req->getParameters();
|
||||
for (auto parameter : qureyPara)
|
||||
{
|
||||
if (binder->queryParametersPlaces.find(parameter.first) !=
|
||||
binder->queryParametersPlaces.end())
|
||||
{
|
||||
auto place = binder->queryParametersPlaces.find(parameter.first)->second;
|
||||
if (place > params.size())
|
||||
params.resize(place);
|
||||
params[place - 1] = parameter.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::list<std::string> paraList;
|
||||
for (auto p : params)
|
||||
{
|
||||
LOG_TRACE << p;
|
||||
paraList.push_back(std::move(p));
|
||||
}
|
||||
|
||||
binder->binderPtr->handleHttpRequest(paraList, req, [=](const HttpResponsePtr &resp) {
|
||||
LOG_TRACE << "http resp:needSetJsessionid=" << needSetJsessionid << ";JSESSIONID=" << session_id;
|
||||
auto newResp = resp;
|
||||
if (resp->expiredTime() >= 0)
|
||||
{
|
||||
//cache the response;
|
||||
std::dynamic_pointer_cast<HttpResponseImpl>(resp)->makeHeaderString();
|
||||
{
|
||||
auto &binderIterm = _ctrlVector[ctlIndex];
|
||||
std::lock_guard<std::mutex> guard(*(binder->binderMtx));
|
||||
_responseCacheMap->insert(binderIterm.pathParameterPattern, resp, resp->expiredTime());
|
||||
binder->responsePtr = resp;
|
||||
}
|
||||
}
|
||||
if (needSetJsessionid)
|
||||
{
|
||||
//make a copy
|
||||
newResp = std::make_shared<HttpResponseImpl>(*std::dynamic_pointer_cast<HttpResponseImpl>(resp));
|
||||
newResp->setExpiredTime(-1); //make it temporary
|
||||
newResp->addCookie("JSESSIONID", session_id);
|
||||
}
|
||||
callback(newResp);
|
||||
});
|
||||
return;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//No controller found
|
||||
auto res = drogon::HttpResponse::newNotFoundResponse();
|
||||
if (needSetJsessionid)
|
||||
res->addCookie("JSESSIONID", session_id);
|
||||
|
||||
callback(res);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//No controller found
|
||||
auto res = drogon::HttpResponse::newNotFoundResponse();
|
||||
|
||||
if (needSetJsessionid)
|
||||
res->addCookie("JSESSIONID", session_id);
|
||||
|
||||
callback(res);
|
||||
}
|
||||
//Find http controller
|
||||
_httpCtrlsRouter.route(req, callback, needSetJsessionid, session_id);
|
||||
}
|
||||
|
||||
void HttpAppFrameworkImpl::readSendFile(const std::string &filePath, const HttpRequestImplPtr &req, const HttpResponsePtr &resp)
|
||||
|
@ -1252,7 +820,7 @@ void HttpAppFrameworkImpl::readSendFile(const std::string &filePath, const HttpR
|
|||
if (_staticFilesCacheTime >= 0)
|
||||
{
|
||||
resp->setExpiredTime(_staticFilesCacheTime);
|
||||
_responseCacheMap->insert(filePath, resp, resp->expiredTime(), [=]() {
|
||||
_responseCachingMap->insert(filePath, resp, resp->expiredTime(), [=]() {
|
||||
std::lock_guard<std::mutex> guard(_staticFilesCacheMutex);
|
||||
_staticFilesCache.erase(filePath);
|
||||
});
|
||||
|
|
|
@ -19,6 +19,10 @@
|
|||
#include "HttpClientImpl.h"
|
||||
#include "SharedLibManager.h"
|
||||
#include "WebSockectConnectionImpl.h"
|
||||
#include "HttpControllersRouter.h"
|
||||
#include "HttpSimpleControllersRouter.h"
|
||||
#include "WebsocketControllersRouter.h"
|
||||
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
#include <drogon/HttpSimpleController.h>
|
||||
#include <drogon/version.h>
|
||||
|
@ -35,7 +39,10 @@ class HttpAppFrameworkImpl : public HttpAppFramework
|
|||
{
|
||||
public:
|
||||
HttpAppFrameworkImpl()
|
||||
: _uploadPath(_rootPath + "uploads"),
|
||||
: _httpSimpleCtrlsRouter(*this),
|
||||
_httpCtrlsRouter(*this),
|
||||
_websockCtrlsRouter(*this),
|
||||
_uploadPath(_rootPath + "uploads"),
|
||||
_connectionNum(0)
|
||||
{
|
||||
}
|
||||
|
@ -109,6 +116,13 @@ class HttpAppFrameworkImpl : public HttpAppFramework
|
|||
const std::string &filename = "",
|
||||
const std::string &name = "default") override;
|
||||
#endif
|
||||
void doFilters(const std::vector<std::string> &filters,
|
||||
const HttpRequestImplPtr &req,
|
||||
const std::function<void(const HttpResponsePtr &)> &callback,
|
||||
bool needSetJsessionid,
|
||||
const std::string &session_id,
|
||||
const std::function<void()> &missCallback);
|
||||
|
||||
private:
|
||||
virtual void registerHttpController(const std::string &pathPattern,
|
||||
const internal::HttpBinderBasePtr &binder,
|
||||
|
@ -128,7 +142,6 @@ class HttpAppFrameworkImpl : public HttpAppFramework
|
|||
const internal::HttpBinderBasePtr &binder,
|
||||
const std::vector<HttpMethod> &validMethods,
|
||||
const std::vector<std::string> &filters);
|
||||
void initRegex();
|
||||
//if uuid package found,we can use a uuid string as session id;
|
||||
//set _sessionTimeout=0 to make location session valid forever based on cookies;
|
||||
size_t _sessionTimeout = 0;
|
||||
|
@ -136,61 +149,19 @@ class HttpAppFrameworkImpl : public HttpAppFramework
|
|||
bool _useSession = false;
|
||||
typedef std::shared_ptr<Session> SessionPtr;
|
||||
std::unique_ptr<CacheMap<std::string, SessionPtr>> _sessionMapPtr;
|
||||
std::unique_ptr<CacheMap<std::string, HttpResponsePtr>> _responseCachingMap;
|
||||
|
||||
std::unique_ptr<CacheMap<std::string, HttpResponsePtr>> _responseCacheMap;
|
||||
|
||||
void doFilters(const std::vector<std::string> &filters,
|
||||
const HttpRequestImplPtr &req,
|
||||
const std::function<void(const HttpResponsePtr &)> &callback,
|
||||
bool needSetJsessionid,
|
||||
const std::string &session_id,
|
||||
const std::function<void()> &missCallback);
|
||||
void doFilterChain(const std::shared_ptr<std::queue<std::shared_ptr<HttpFilterBase>>> &chain,
|
||||
const HttpRequestImplPtr &req,
|
||||
const std::function<void(const HttpResponsePtr &)> &callback,
|
||||
bool needSetJsessionid,
|
||||
const std::string &session_id,
|
||||
const std::function<void()> &missCallback);
|
||||
//
|
||||
struct SimpleControllerRouterItem
|
||||
{
|
||||
std::string controllerName;
|
||||
std::vector<std::string> filtersName;
|
||||
std::vector<int> _validMethodsFlags;
|
||||
std::shared_ptr<HttpSimpleControllerBase> controller;
|
||||
std::weak_ptr<HttpResponse> responsePtr;
|
||||
std::mutex _mutex;
|
||||
};
|
||||
std::unordered_map<std::string, SimpleControllerRouterItem> _simpCtrlMap;
|
||||
std::mutex _simpCtrlMutex;
|
||||
struct WebSocketControllerRouterItem
|
||||
{
|
||||
WebSocketControllerBasePtr controller;
|
||||
std::vector<std::string> filtersName;
|
||||
};
|
||||
std::unordered_map<std::string, WebSocketControllerRouterItem> _websockCtrlMap;
|
||||
std::mutex _websockCtrlMutex;
|
||||
|
||||
struct CtrlBinder
|
||||
{
|
||||
internal::HttpBinderBasePtr binderPtr;
|
||||
std::vector<std::string> filtersName;
|
||||
std::vector<size_t> parameterPlaces;
|
||||
std::map<std::string, size_t> queryParametersPlaces;
|
||||
std::unique_ptr<std::mutex> binderMtx = std::unique_ptr<std::mutex>(new std::mutex);
|
||||
std::weak_ptr<HttpResponse> responsePtr;
|
||||
};
|
||||
typedef std::shared_ptr<CtrlBinder> CtrlBinderPtr;
|
||||
struct HttpControllerRouterItem
|
||||
{
|
||||
std::string pathParameterPattern;
|
||||
std::regex _regex;
|
||||
CtrlBinderPtr _binders[Invalid]; //The enum value Invalid is the http methods number
|
||||
};
|
||||
std::vector<HttpControllerRouterItem> _ctrlVector;
|
||||
std::mutex _ctrlMutex;
|
||||
HttpSimpleControllersRouter _httpSimpleCtrlsRouter;
|
||||
HttpControllersRouter _httpCtrlsRouter;
|
||||
WebsocketControllersRouter _websockCtrlsRouter;
|
||||
|
||||
std::regex _ctrlRegex;
|
||||
bool _enableLastModify = true;
|
||||
std::set<std::string> _fileTypeSet = {"html", "js", "css", "xml", "xsl", "txt", "svg", "ttf",
|
||||
"otf", "woff2", "woff", "eot", "png", "jpg", "jpeg",
|
||||
|
@ -199,8 +170,6 @@ class HttpAppFrameworkImpl : public HttpAppFramework
|
|||
std::string _uploadPath;
|
||||
std::atomic_bool _running;
|
||||
|
||||
//tool funcs
|
||||
|
||||
size_t _threadNum = 1;
|
||||
std::vector<std::string> _libFilePaths;
|
||||
|
||||
|
|
|
@ -0,0 +1,279 @@
|
|||
/**
|
||||
*
|
||||
* HttpControllersRouter.cc
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HttpControllersRouter.h"
|
||||
#include "HttpRequestImpl.h"
|
||||
#include "HttpResponseImpl.h"
|
||||
#include "HttpAppFrameworkImpl.h"
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
void HttpControllersRouter::init()
|
||||
{
|
||||
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(")|");
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
void HttpControllersRouter::addHttpPath(const std::string &path,
|
||||
const internal::HttpBinderBasePtr &binder,
|
||||
const std::vector<HttpMethod> &validMethods,
|
||||
const std::vector<std::string> &filters)
|
||||
{
|
||||
//path will be 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("\\{([0-9]+)\\}");
|
||||
std::smatch results;
|
||||
auto pos = tmpPath.find("?");
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
paras = tmpPath.substr(pos + 1);
|
||||
tmpPath = tmpPath.substr(0, pos);
|
||||
}
|
||||
std::string originPath = tmpPath;
|
||||
while (std::regex_search(tmpPath, results, regex))
|
||||
{
|
||||
if (results.size() > 1)
|
||||
{
|
||||
size_t place = (size_t)std::stoi(results[1].str());
|
||||
if (place > binder->paramCount() || place == 0)
|
||||
{
|
||||
LOG_ERROR << "parameter placeholder(value=" << place << ") out of range (1 to "
|
||||
<< binder->paramCount() << ")";
|
||||
exit(0);
|
||||
}
|
||||
places.push_back(place);
|
||||
}
|
||||
tmpPath = results.suffix();
|
||||
}
|
||||
std::map<std::string, size_t> parametersPlaces;
|
||||
if (!paras.empty())
|
||||
{
|
||||
std::regex pregex("([^&]*)=\\{([0-9]+)\\}&*");
|
||||
while (std::regex_search(paras, results, pregex))
|
||||
{
|
||||
if (results.size() > 2)
|
||||
{
|
||||
size_t place = (size_t)std::stoi(results[2].str());
|
||||
if (place > binder->paramCount() || place == 0)
|
||||
{
|
||||
LOG_ERROR << "parameter placeholder(value=" << place << ") out of range (1 to "
|
||||
<< binder->paramCount() << ")";
|
||||
exit(0);
|
||||
}
|
||||
parametersPlaces[results[1].str()] = place;
|
||||
}
|
||||
paras = results.suffix();
|
||||
}
|
||||
}
|
||||
auto pathParameterPattern = std::regex_replace(originPath, regex, "([^/]*)");
|
||||
auto binderInfo = CtrlBinderPtr(new CtrlBinder);
|
||||
binderInfo->filtersName = filters;
|
||||
binderInfo->binderPtr = binder;
|
||||
binderInfo->parameterPlaces = std::move(places);
|
||||
binderInfo->queryParametersPlaces = std::move(parametersPlaces);
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(_ctrlMutex);
|
||||
for (auto &router : _ctrlVector)
|
||||
{
|
||||
if (router.pathParameterPattern == pathParameterPattern)
|
||||
{
|
||||
if (validMethods.size() > 0)
|
||||
{
|
||||
for (auto method : validMethods)
|
||||
{
|
||||
router._binders[method] = binderInfo;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Invalid; i++)
|
||||
router._binders[i] = binderInfo;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct HttpControllerRouterItem router;
|
||||
router.pathParameterPattern = pathParameterPattern;
|
||||
if (validMethods.size() > 0)
|
||||
{
|
||||
for (auto method : validMethods)
|
||||
{
|
||||
router._binders[method] = binderInfo;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < Invalid; i++)
|
||||
router._binders[i] = binderInfo;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(_ctrlMutex);
|
||||
_ctrlVector.push_back(std::move(router));
|
||||
}
|
||||
}
|
||||
|
||||
void HttpControllersRouter::route(const HttpRequestImplPtr &req,
|
||||
const std::function<void(const HttpResponsePtr &)> &callback,
|
||||
bool needSetJsessionid,
|
||||
const std::string &session_id)
|
||||
{
|
||||
//find http controller
|
||||
if (_ctrlRegex.mark_count() > 0)
|
||||
{
|
||||
std::smatch result;
|
||||
if (std::regex_match(req->path(), result, _ctrlRegex))
|
||||
{
|
||||
for (size_t i = 1; i < result.size(); i++)
|
||||
{
|
||||
//FIXME:Is there any better way to find the sub-match index without using loop?
|
||||
if (!result[i].matched)
|
||||
continue;
|
||||
if (result[i].str() == req->path() && i <= _ctrlVector.size())
|
||||
{
|
||||
size_t ctlIndex = i - 1;
|
||||
auto &router = _ctrlVector[ctlIndex];
|
||||
//LOG_TRACE << "got http access,regex=" << binder.pathParameterPattern;
|
||||
assert(Invalid > req->method());
|
||||
auto &binder = router._binders[req->method()];
|
||||
if (!binder)
|
||||
{
|
||||
//Invalid Http Method
|
||||
auto res = drogon::HttpResponse::newHttpResponse();
|
||||
res->setStatusCode(HttpResponse::k405MethodNotAllowed);
|
||||
callback(res);
|
||||
return;
|
||||
}
|
||||
|
||||
auto &filters = binder->filtersName;
|
||||
_appImpl.doFilters(filters, req, callback, needSetJsessionid, session_id, [=]() {
|
||||
HttpResponsePtr responsePtr;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(*(binder->binderMtx));
|
||||
responsePtr = binder->responsePtr;
|
||||
}
|
||||
|
||||
if (responsePtr && (responsePtr->expiredTime() == 0 || (trantor::Date::now() < responsePtr->createDate().after(responsePtr->expiredTime()))))
|
||||
{
|
||||
//use cached response!
|
||||
LOG_TRACE << "Use cached response";
|
||||
|
||||
if (!needSetJsessionid)
|
||||
callback(responsePtr);
|
||||
else
|
||||
{
|
||||
//make a copy response;
|
||||
auto newResp = std::make_shared<HttpResponseImpl>(*std::dynamic_pointer_cast<HttpResponseImpl>(responsePtr));
|
||||
newResp->setExpiredTime(-1); //make it temporary
|
||||
newResp->addCookie("JSESSIONID", session_id);
|
||||
callback(newResp);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> params(binder->parameterPlaces.size());
|
||||
std::smatch r;
|
||||
if (std::regex_match(req->path(), r, router._regex))
|
||||
{
|
||||
for (size_t j = 1; j < r.size(); j++)
|
||||
{
|
||||
size_t place = binder->parameterPlaces[j - 1];
|
||||
if (place > params.size())
|
||||
params.resize(place);
|
||||
params[place - 1] = r[j].str();
|
||||
LOG_TRACE << "place=" << place << " para:" << params[place - 1];
|
||||
}
|
||||
}
|
||||
if (binder->queryParametersPlaces.size() > 0)
|
||||
{
|
||||
auto qureyPara = req->getParameters();
|
||||
for (auto parameter : qureyPara)
|
||||
{
|
||||
if (binder->queryParametersPlaces.find(parameter.first) !=
|
||||
binder->queryParametersPlaces.end())
|
||||
{
|
||||
auto place = binder->queryParametersPlaces.find(parameter.first)->second;
|
||||
if (place > params.size())
|
||||
params.resize(place);
|
||||
params[place - 1] = parameter.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::list<std::string> paraList;
|
||||
for (auto p : params)
|
||||
{
|
||||
LOG_TRACE << p;
|
||||
paraList.push_back(std::move(p));
|
||||
}
|
||||
|
||||
binder->binderPtr->handleHttpRequest(paraList, req, [=](const HttpResponsePtr &resp) {
|
||||
LOG_TRACE << "http resp:needSetJsessionid=" << needSetJsessionid << ";JSESSIONID=" << session_id;
|
||||
auto newResp = resp;
|
||||
if (resp->expiredTime() >= 0)
|
||||
{
|
||||
//cache the response;
|
||||
std::dynamic_pointer_cast<HttpResponseImpl>(resp)->makeHeaderString();
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(*(binder->binderMtx));
|
||||
binder->responsePtr = resp;
|
||||
}
|
||||
}
|
||||
if (needSetJsessionid)
|
||||
{
|
||||
//make a copy
|
||||
newResp = std::make_shared<HttpResponseImpl>(*std::dynamic_pointer_cast<HttpResponseImpl>(resp));
|
||||
newResp->setExpiredTime(-1); //make it temporary
|
||||
newResp->addCookie("JSESSIONID", session_id);
|
||||
}
|
||||
callback(newResp);
|
||||
});
|
||||
return;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//No controller found
|
||||
auto res = drogon::HttpResponse::newNotFoundResponse();
|
||||
if (needSetJsessionid)
|
||||
res->addCookie("JSESSIONID", session_id);
|
||||
|
||||
callback(res);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//No controller found
|
||||
auto res = drogon::HttpResponse::newNotFoundResponse();
|
||||
|
||||
if (needSetJsessionid)
|
||||
res->addCookie("JSESSIONID", session_id);
|
||||
|
||||
callback(res);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
*
|
||||
* HttpControllersRouter.h
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "HttpRequestImpl.h"
|
||||
#include "HttpResponseImpl.h"
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <drogon/HttpBinder.h>
|
||||
#include <vector>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class HttpAppFrameworkImpl;
|
||||
class HttpControllersRouter : public trantor::NonCopyable
|
||||
{
|
||||
public:
|
||||
HttpControllersRouter(HttpAppFrameworkImpl &app) : _appImpl(app) {}
|
||||
void init();
|
||||
void addHttpPath(const std::string &path,
|
||||
const internal::HttpBinderBasePtr &binder,
|
||||
const std::vector<HttpMethod> &validMethods,
|
||||
const std::vector<std::string> &filters);
|
||||
void route(const HttpRequestImplPtr &req,
|
||||
const std::function<void(const HttpResponsePtr &)> &callback,
|
||||
bool needSetJsessionid,
|
||||
const std::string &session_id);
|
||||
|
||||
private:
|
||||
struct CtrlBinder
|
||||
{
|
||||
internal::HttpBinderBasePtr binderPtr;
|
||||
std::vector<std::string> filtersName;
|
||||
std::vector<size_t> parameterPlaces;
|
||||
std::map<std::string, size_t> queryParametersPlaces;
|
||||
std::unique_ptr<std::mutex> binderMtx = std::unique_ptr<std::mutex>(new std::mutex);
|
||||
std::shared_ptr<HttpResponse> responsePtr;
|
||||
};
|
||||
typedef std::shared_ptr<CtrlBinder> CtrlBinderPtr;
|
||||
struct HttpControllerRouterItem
|
||||
{
|
||||
std::string pathParameterPattern;
|
||||
std::regex _regex;
|
||||
CtrlBinderPtr _binders[Invalid]; //The enum value Invalid is the http methods number
|
||||
};
|
||||
std::vector<HttpControllerRouterItem> _ctrlVector;
|
||||
std::mutex _ctrlMutex;
|
||||
std::regex _ctrlRegex;
|
||||
HttpAppFrameworkImpl &_appImpl;
|
||||
};
|
||||
} // namespace drogon
|
|
@ -34,6 +34,7 @@ class HttpResponseImpl : public HttpResponse
|
|||
public:
|
||||
explicit HttpResponseImpl()
|
||||
: _statusCode(kUnknown),
|
||||
_createDate(trantor::Date::now()),
|
||||
_closeConnection(false),
|
||||
_left_body_length(0),
|
||||
_current_chunk_length(0),
|
||||
|
@ -45,6 +46,12 @@ class HttpResponseImpl : public HttpResponse
|
|||
{
|
||||
return _statusCode;
|
||||
}
|
||||
|
||||
virtual const trantor::Date &createDate() const override
|
||||
{
|
||||
return _createDate;
|
||||
}
|
||||
|
||||
virtual void setStatusCode(HttpStatusCode code) override
|
||||
{
|
||||
_statusCode = code;
|
||||
|
@ -358,6 +365,7 @@ class HttpResponseImpl : public HttpResponse
|
|||
std::unordered_map<std::string, std::string> _headers;
|
||||
std::unordered_map<std::string, Cookie> _cookies;
|
||||
HttpStatusCode _statusCode;
|
||||
trantor::Date _createDate;
|
||||
// FIXME: add http version
|
||||
Version _v;
|
||||
std::string _statusMessage;
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
/**
|
||||
*
|
||||
* HttpSimpleControllersRouter.cc
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HttpSimpleControllersRouter.h"
|
||||
#include "HttpAppFrameworkImpl.h"
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
void HttpSimpleControllersRouter::registerHttpSimpleController(const std::string &pathName,
|
||||
const std::string &ctrlName,
|
||||
const std::vector<any> &filtersAndMethods)
|
||||
{
|
||||
assert(!pathName.empty());
|
||||
assert(!ctrlName.empty());
|
||||
|
||||
std::string path(pathName);
|
||||
std::transform(pathName.begin(), pathName.end(), path.begin(), tolower);
|
||||
std::lock_guard<std::mutex> guard(_simpCtrlMutex);
|
||||
std::vector<HttpMethod> validMethods;
|
||||
std::vector<std::string> filters;
|
||||
for (auto &filterOrMethod : filtersAndMethods)
|
||||
{
|
||||
if (filterOrMethod.type() == typeid(std::string))
|
||||
{
|
||||
filters.push_back(*any_cast<std::string>(&filterOrMethod));
|
||||
}
|
||||
else if (filterOrMethod.type() == typeid(const char *))
|
||||
{
|
||||
filters.push_back(*any_cast<const char *>(&filterOrMethod));
|
||||
}
|
||||
else if (filterOrMethod.type() == typeid(HttpMethod))
|
||||
{
|
||||
validMethods.push_back(*any_cast<HttpMethod>(&filterOrMethod));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Invalid controller constraint type:" << filterOrMethod.type().name() << std::endl;
|
||||
LOG_ERROR << "Invalid controller constraint type";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
auto &iterm = _simpCtrlMap[path];
|
||||
iterm.controllerName = ctrlName;
|
||||
iterm.filtersName = filters;
|
||||
iterm._validMethodsFlags.clear(); //There may be old data, first clear
|
||||
if (validMethods.size() > 0)
|
||||
{
|
||||
iterm._validMethodsFlags.resize(Invalid, 0);
|
||||
for (auto method : validMethods)
|
||||
{
|
||||
iterm._validMethodsFlags[method] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpSimpleControllersRouter::route(const HttpRequestImplPtr &req,
|
||||
const std::function<void(const HttpResponsePtr &)> &callback,
|
||||
bool needSetJsessionid,
|
||||
const std::string &session_id)
|
||||
{
|
||||
std::string pathLower(req->path());
|
||||
std::transform(pathLower.begin(), pathLower.end(), pathLower.begin(), tolower);
|
||||
|
||||
if (_simpCtrlMap.find(pathLower) != _simpCtrlMap.end())
|
||||
{
|
||||
auto &ctrlInfo = _simpCtrlMap[pathLower];
|
||||
if (ctrlInfo._validMethodsFlags.size() > 0)
|
||||
{
|
||||
assert(ctrlInfo._validMethodsFlags.size() > req->method());
|
||||
if (ctrlInfo._validMethodsFlags[req->method()] == 0)
|
||||
{
|
||||
//Invalid Http Method
|
||||
auto res = drogon::HttpResponse::newHttpResponse();
|
||||
res->setStatusCode(HttpResponse::k405MethodNotAllowed);
|
||||
callback(res);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
auto &filters = ctrlInfo.filtersName;
|
||||
_appImpl.doFilters(filters, req, callback, needSetJsessionid, session_id, [=]() {
|
||||
auto &ctrlItem = _simpCtrlMap[pathLower];
|
||||
const std::string &ctrlName = ctrlItem.controllerName;
|
||||
std::shared_ptr<HttpSimpleControllerBase> controller;
|
||||
HttpResponsePtr responsePtr;
|
||||
{
|
||||
//maybe update controller,so we use lock_guard to protect;
|
||||
std::lock_guard<std::mutex> guard(ctrlItem._mutex);
|
||||
controller = ctrlItem.controller;
|
||||
responsePtr = ctrlItem.responsePtr;
|
||||
if (!controller)
|
||||
{
|
||||
auto _object = std::shared_ptr<DrObjectBase>(DrClassMap::newObject(ctrlName));
|
||||
controller = std::dynamic_pointer_cast<HttpSimpleControllerBase>(_object);
|
||||
ctrlItem.controller = controller;
|
||||
}
|
||||
}
|
||||
|
||||
if (controller)
|
||||
{
|
||||
if (responsePtr && (responsePtr->expiredTime() == 0 || (trantor::Date::now() < responsePtr->createDate().after(responsePtr->expiredTime()))))
|
||||
{
|
||||
//use cached response!
|
||||
LOG_TRACE << "Use cached response";
|
||||
if (!needSetJsessionid)
|
||||
callback(responsePtr);
|
||||
else
|
||||
{
|
||||
//make a copy response;
|
||||
auto newResp = std::make_shared<HttpResponseImpl>(*std::dynamic_pointer_cast<HttpResponseImpl>(responsePtr));
|
||||
newResp->setExpiredTime(-1); //make it temporary
|
||||
newResp->addCookie("JSESSIONID", session_id);
|
||||
callback(newResp);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
controller->asyncHandleHttpRequest(req, [=](const HttpResponsePtr &resp) {
|
||||
auto newResp = resp;
|
||||
if (resp->expiredTime() >= 0)
|
||||
{
|
||||
//cache the response;
|
||||
std::dynamic_pointer_cast<HttpResponseImpl>(resp)->makeHeaderString();
|
||||
{
|
||||
auto &item = _simpCtrlMap[pathLower];
|
||||
std::lock_guard<std::mutex> guard(item._mutex);
|
||||
item.responsePtr = resp;
|
||||
}
|
||||
}
|
||||
if (needSetJsessionid)
|
||||
{
|
||||
//make a copy
|
||||
newResp = std::make_shared<HttpResponseImpl>(*std::dynamic_pointer_cast<HttpResponseImpl>(resp));
|
||||
newResp->setExpiredTime(-1); //make it temporary
|
||||
newResp->addCookie("JSESSIONID", session_id);
|
||||
}
|
||||
|
||||
callback(newResp);
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "can't find controller " << ctrlName;
|
||||
auto res = drogon::HttpResponse::newNotFoundResponse();
|
||||
if (needSetJsessionid)
|
||||
res->addCookie("JSESSIONID", session_id);
|
||||
|
||||
callback(res);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
*
|
||||
* HttpSimpleControllersRouter.h
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "HttpRequestImpl.h"
|
||||
#include "HttpResponseImpl.h"
|
||||
#include <drogon/HttpSimpleController.h>
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <drogon/HttpBinder.h>
|
||||
#include <vector>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class HttpAppFrameworkImpl;
|
||||
class HttpSimpleControllersRouter : public trantor::NonCopyable
|
||||
{
|
||||
public:
|
||||
HttpSimpleControllersRouter(HttpAppFrameworkImpl &app) : _appImpl(app) {}
|
||||
void registerHttpSimpleController(const std::string &pathName,
|
||||
const std::string &ctrlName,
|
||||
const std::vector<any> &filtersAndMethods);
|
||||
bool route(const HttpRequestImplPtr &req,
|
||||
const std::function<void(const HttpResponsePtr &)> &callback,
|
||||
bool needSetJsessionid,
|
||||
const std::string &session_id);
|
||||
|
||||
private:
|
||||
HttpAppFrameworkImpl &_appImpl;
|
||||
struct SimpleControllerRouterItem
|
||||
{
|
||||
std::string controllerName;
|
||||
std::vector<std::string> filtersName;
|
||||
std::vector<int> _validMethodsFlags;
|
||||
std::shared_ptr<HttpSimpleControllerBase> controller;
|
||||
std::shared_ptr<HttpResponse> responsePtr;
|
||||
std::mutex _mutex;
|
||||
};
|
||||
std::unordered_map<std::string, SimpleControllerRouterItem> _simpCtrlMap;
|
||||
std::mutex _simpCtrlMutex;
|
||||
};
|
||||
} // namespace drogon
|
|
@ -0,0 +1,72 @@
|
|||
#include "WebsocketControllersRouter.h"
|
||||
#include "HttpAppFrameworkImpl.h"
|
||||
#ifdef USE_OPENSSL
|
||||
#include <openssl/sha.h>
|
||||
#else
|
||||
#include "ssl_funcs/Sha1.h"
|
||||
#endif
|
||||
using namespace drogon;
|
||||
|
||||
void WebsocketControllersRouter::registerWebSocketController(const std::string &pathName,
|
||||
const std::string &ctrlName,
|
||||
const std::vector<std::string> &filters)
|
||||
{
|
||||
assert(!pathName.empty());
|
||||
assert(!ctrlName.empty());
|
||||
std::string path(pathName);
|
||||
std::transform(pathName.begin(), pathName.end(), path.begin(), tolower);
|
||||
auto objPtr = std::shared_ptr<DrObjectBase>(DrClassMap::newObject(ctrlName));
|
||||
auto ctrlPtr = std::dynamic_pointer_cast<WebSocketControllerBase>(objPtr);
|
||||
assert(ctrlPtr);
|
||||
std::lock_guard<std::mutex> guard(_websockCtrlMutex);
|
||||
|
||||
_websockCtrlMap[path].controller = ctrlPtr;
|
||||
_websockCtrlMap[path].filtersName = filters;
|
||||
}
|
||||
|
||||
void WebsocketControllersRouter::route(const HttpRequestImplPtr &req,
|
||||
const std::function<void(const HttpResponsePtr &)> &callback,
|
||||
const WebSocketConnectionPtr &wsConnPtr)
|
||||
{
|
||||
std::string wsKey = req->getHeaderBy("sec-websocket-key");
|
||||
if (!wsKey.empty())
|
||||
{
|
||||
// magic="258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
WebSocketControllerBasePtr ctrlPtr;
|
||||
std::vector<std::string> filtersName;
|
||||
{
|
||||
std::string pathLower(req->path());
|
||||
std::transform(pathLower.begin(), pathLower.end(), pathLower.begin(), tolower);
|
||||
std::lock_guard<std::mutex> guard(_websockCtrlMutex);
|
||||
if (_websockCtrlMap.find(pathLower) != _websockCtrlMap.end())
|
||||
{
|
||||
ctrlPtr = _websockCtrlMap[pathLower].controller;
|
||||
filtersName = _websockCtrlMap[pathLower].filtersName;
|
||||
}
|
||||
}
|
||||
if (ctrlPtr)
|
||||
{
|
||||
_appImpl.doFilters(filtersName, req, callback, false, "", [=]() mutable {
|
||||
wsKey.append("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
|
||||
unsigned char accKey[SHA_DIGEST_LENGTH];
|
||||
SHA1(reinterpret_cast<const unsigned char *>(wsKey.c_str()), wsKey.length(), accKey);
|
||||
auto base64Key = base64Encode(accKey, SHA_DIGEST_LENGTH);
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
resp->setStatusCode(HttpResponse::k101SwitchingProtocols);
|
||||
resp->addHeader("Upgrade", "websocket");
|
||||
resp->addHeader("Connection", "Upgrade");
|
||||
resp->addHeader("Sec-WebSocket-Accept", base64Key);
|
||||
callback(resp);
|
||||
auto wsConnImplPtr = std::dynamic_pointer_cast<WebSocketConnectionImpl>(wsConnPtr);
|
||||
assert(wsConnImplPtr);
|
||||
wsConnImplPtr->setController(ctrlPtr);
|
||||
ctrlPtr->handleNewConnection(req, wsConnPtr);
|
||||
return;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto resp = drogon::HttpResponse::newNotFoundResponse();
|
||||
resp->setCloseConnection(true);
|
||||
callback(resp);
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
*
|
||||
* WebsocketControllersRouter.h
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "HttpRequestImpl.h"
|
||||
#include "HttpResponseImpl.h"
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <drogon/WebSocketController.h>
|
||||
#include <vector>
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class HttpAppFrameworkImpl;
|
||||
class WebsocketControllersRouter : public trantor::NonCopyable
|
||||
{
|
||||
public:
|
||||
WebsocketControllersRouter(HttpAppFrameworkImpl &app) : _appImpl(app) {}
|
||||
void registerWebSocketController(const std::string &pathName,
|
||||
const std::string &ctrlName,
|
||||
const std::vector<std::string> &filters);
|
||||
void route(const HttpRequestImplPtr &req,
|
||||
const std::function<void(const HttpResponsePtr &)> &callback,
|
||||
const WebSocketConnectionPtr &wsConnPtr);
|
||||
|
||||
private:
|
||||
HttpAppFrameworkImpl &_appImpl;
|
||||
struct WebSocketControllerRouterItem
|
||||
{
|
||||
WebSocketControllerBasePtr controller;
|
||||
std::vector<std::string> filtersName;
|
||||
};
|
||||
std::unordered_map<std::string, WebSocketControllerRouterItem> _websockCtrlMap;
|
||||
std::mutex _websockCtrlMutex;
|
||||
};
|
||||
} // namespace drogon
|
Loading…
Reference in New Issue