From 38966e09d47a6a8bea0afcea06227ba657dfc046 Mon Sep 17 00:00:00 2001 From: antao Date: Thu, 17 Jan 2019 13:51:25 +0800 Subject: [PATCH 1/2] Split the HttpAppFrameworkImpl class --- examples/simple_example/WebSocketTest.cc | 9 +- lib/inc/drogon/HttpResponse.h | 3 + lib/src/HttpAppFrameworkImpl.cc | 458 +---------------------- lib/src/HttpAppFrameworkImpl.h | 69 +--- lib/src/HttpControllersRouter.cc | 279 ++++++++++++++ lib/src/HttpControllersRouter.h | 65 ++++ lib/src/HttpResponseImpl.h | 8 + lib/src/HttpSimpleControllersRouter.cc | 168 +++++++++ lib/src/HttpSimpleControllersRouter.h | 56 +++ lib/src/WebsocketControllersRouter.cc | 72 ++++ lib/src/WebsocketControllersRouter.h | 50 +++ 11 files changed, 738 insertions(+), 499 deletions(-) create mode 100644 lib/src/HttpControllersRouter.cc create mode 100644 lib/src/HttpControllersRouter.h create mode 100644 lib/src/HttpSimpleControllersRouter.cc create mode 100644 lib/src/HttpSimpleControllersRouter.h create mode 100644 lib/src/WebsocketControllersRouter.cc create mode 100644 lib/src/WebsocketControllersRouter.h diff --git a/examples/simple_example/WebSocketTest.cc b/examples/simple_example/WebSocketTest.cc index 0448e425..01bd20ca 100644 --- a/examples/simple_example/WebSocketTest.cc +++ b/examples/simple_example/WebSocketTest.cc @@ -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!!!"); } diff --git a/lib/inc/drogon/HttpResponse.h b/lib/inc/drogon/HttpResponse.h index 03fbdd66..be2d0896 100755 --- a/lib/inc/drogon/HttpResponse.h +++ b/lib/inc/drogon/HttpResponse.h @@ -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; diff --git a/lib/src/HttpAppFrameworkImpl.cc b/lib/src/HttpAppFrameworkImpl.cc index 22f459b4..d49ef074 100755 --- a/lib/src/HttpAppFrameworkImpl.cc +++ b/lib/src/HttpAppFrameworkImpl.cc @@ -22,11 +22,7 @@ #include #include #include -#ifdef USE_OPENSSL -#include -#else -#include "ssl_funcs/Sha1.h" -#endif + #include #include #include @@ -109,184 +105,20 @@ void HttpAppFrameworkImpl::setFileTypes(const std::vector &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 &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(DrClassMap::newObject(ctrlName)); - auto ctrlPtr = std::dynamic_pointer_cast(objPtr); - assert(ctrlPtr); - std::lock_guard 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 &filtersAndMethods) { - assert(!pathName.empty()); - assert(!ctrlName.empty()); - - std::string path(pathName); - std::transform(pathName.begin(), pathName.end(), path.begin(), tolower); - std::lock_guard guard(_simpCtrlMutex); - std::vector validMethods; - std::vector filters; - for (auto &filterOrMethod : filtersAndMethods) - { - if (filterOrMethod.type() == typeid(std::string)) - { - filters.push_back(*any_cast(&filterOrMethod)); - } - else if (filterOrMethod.type() == typeid(const char *)) - { - filters.push_back(*any_cast(&filterOrMethod)); - } - else if (filterOrMethod.type() == typeid(HttpMethod)) - { - validMethods.push_back(*any_cast(&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 &validMethods, - const std::vector &filters) -{ - //path will be like /api/v1/service/method/{1}/{2}/xxx... - std::vector 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 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 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 guard(_ctrlMutex); - _ctrlVector.push_back(std::move(router)); - } -} void HttpAppFrameworkImpl::registerHttpController(const std::string &pathPattern, const internal::HttpBinderBasePtr &binder, const std::vector &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> servers; std::vector> loopThreads; - initRegex(); + _httpCtrlsRouter.init(); for (auto listener : _listeners) { LOG_TRACE << "thread num=" << _threadNum; @@ -543,7 +373,7 @@ void HttpAppFrameworkImpl::run() _sessionMapPtr = std::unique_ptr>(new CacheMap(&_loop, 0, 0, 0)); } } - _responseCacheMap = std::unique_ptr>(new CacheMap(&_loop, 1.0, 4, 50)); //Max timeout up to about 70 days; + _responseCachingMap = std::unique_ptr>(new CacheMap(&_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 &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 filtersName; - { - std::string pathLower(req->path()); - std::transform(pathLower.begin(), pathLower.end(), pathLower.begin(), tolower); - std::lock_guard 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(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(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 &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 controller; - HttpResponsePtr responsePtr; - { - //maybe update controller,so we use lock_guard to protect; - std::lock_guard guard(ctrlItem._mutex); - controller = ctrlItem.controller; - responsePtr = ctrlItem.responsePtr.lock(); - if (!controller) - { - auto _object = std::shared_ptr(DrClassMap::newObject(ctrlName)); - controller = std::dynamic_pointer_cast(_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(*std::dynamic_pointer_cast(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(resp)->makeHeaderString(); - { - auto &item = _simpCtrlMap[pathLower]; - std::lock_guard guard(item._mutex); - _responseCacheMap->insert(pathLower, resp, resp->expiredTime()); - item.responsePtr = resp; - } - } - if (needSetJsessionid) - { - //make a copy - newResp = std::make_shared(*std::dynamic_pointer_cast(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 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(*std::dynamic_pointer_cast(responsePtr)); - newResp->setExpiredTime(-1); //make it temporary - newResp->addCookie("JSESSIONID", session_id); - callback(newResp); - } - return; - } - - std::vector 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 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(resp)->makeHeaderString(); - { - auto &binderIterm = _ctrlVector[ctlIndex]; - std::lock_guard guard(*(binder->binderMtx)); - _responseCacheMap->insert(binderIterm.pathParameterPattern, resp, resp->expiredTime()); - binder->responsePtr = resp; - } - } - if (needSetJsessionid) - { - //make a copy - newResp = std::make_shared(*std::dynamic_pointer_cast(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 guard(_staticFilesCacheMutex); _staticFilesCache.erase(filePath); }); diff --git a/lib/src/HttpAppFrameworkImpl.h b/lib/src/HttpAppFrameworkImpl.h index f0eb637d..a64c656f 100644 --- a/lib/src/HttpAppFrameworkImpl.h +++ b/lib/src/HttpAppFrameworkImpl.h @@ -19,6 +19,10 @@ #include "HttpClientImpl.h" #include "SharedLibManager.h" #include "WebSockectConnectionImpl.h" +#include "HttpControllersRouter.h" +#include "HttpSimpleControllersRouter.h" +#include "WebsocketControllersRouter.h" + #include #include #include @@ -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 &filters, + const HttpRequestImplPtr &req, + const std::function &callback, + bool needSetJsessionid, + const std::string &session_id, + const std::function &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 &validMethods, const std::vector &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 SessionPtr; std::unique_ptr> _sessionMapPtr; + std::unique_ptr> _responseCachingMap; - std::unique_ptr> _responseCacheMap; - - void doFilters(const std::vector &filters, - const HttpRequestImplPtr &req, - const std::function &callback, - bool needSetJsessionid, - const std::string &session_id, - const std::function &missCallback); void doFilterChain(const std::shared_ptr>> &chain, const HttpRequestImplPtr &req, const std::function &callback, bool needSetJsessionid, const std::string &session_id, const std::function &missCallback); - // - struct SimpleControllerRouterItem - { - std::string controllerName; - std::vector filtersName; - std::vector _validMethodsFlags; - std::shared_ptr controller; - std::weak_ptr responsePtr; - std::mutex _mutex; - }; - std::unordered_map _simpCtrlMap; - std::mutex _simpCtrlMutex; - struct WebSocketControllerRouterItem - { - WebSocketControllerBasePtr controller; - std::vector filtersName; - }; - std::unordered_map _websockCtrlMap; - std::mutex _websockCtrlMutex; - struct CtrlBinder - { - internal::HttpBinderBasePtr binderPtr; - std::vector filtersName; - std::vector parameterPlaces; - std::map queryParametersPlaces; - std::unique_ptr binderMtx = std::unique_ptr(new std::mutex); - std::weak_ptr responsePtr; - }; - typedef std::shared_ptr CtrlBinderPtr; - struct HttpControllerRouterItem - { - std::string pathParameterPattern; - std::regex _regex; - CtrlBinderPtr _binders[Invalid]; //The enum value Invalid is the http methods number - }; - std::vector _ctrlVector; - std::mutex _ctrlMutex; + HttpSimpleControllersRouter _httpSimpleCtrlsRouter; + HttpControllersRouter _httpCtrlsRouter; + WebsocketControllersRouter _websockCtrlsRouter; - std::regex _ctrlRegex; bool _enableLastModify = true; std::set _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 _libFilePaths; diff --git a/lib/src/HttpControllersRouter.cc b/lib/src/HttpControllersRouter.cc new file mode 100644 index 00000000..53128ec3 --- /dev/null +++ b/lib/src/HttpControllersRouter.cc @@ -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 &validMethods, + const std::vector &filters) +{ + //path will be like /api/v1/service/method/{1}/{2}/xxx... + std::vector 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 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 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 guard(_ctrlMutex); + _ctrlVector.push_back(std::move(router)); + } +} + +void HttpControllersRouter::route(const HttpRequestImplPtr &req, + const std::function &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 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(*std::dynamic_pointer_cast(responsePtr)); + newResp->setExpiredTime(-1); //make it temporary + newResp->addCookie("JSESSIONID", session_id); + callback(newResp); + } + return; + } + + std::vector 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 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(resp)->makeHeaderString(); + { + std::lock_guard guard(*(binder->binderMtx)); + binder->responsePtr = resp; + } + } + if (needSetJsessionid) + { + //make a copy + newResp = std::make_shared(*std::dynamic_pointer_cast(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); + } +} \ No newline at end of file diff --git a/lib/src/HttpControllersRouter.h b/lib/src/HttpControllersRouter.h new file mode 100644 index 00000000..92353109 --- /dev/null +++ b/lib/src/HttpControllersRouter.h @@ -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 +#include +#include +#include +#include +#include +#include + +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 &validMethods, + const std::vector &filters); + void route(const HttpRequestImplPtr &req, + const std::function &callback, + bool needSetJsessionid, + const std::string &session_id); + + private: + struct CtrlBinder + { + internal::HttpBinderBasePtr binderPtr; + std::vector filtersName; + std::vector parameterPlaces; + std::map queryParametersPlaces; + std::unique_ptr binderMtx = std::unique_ptr(new std::mutex); + std::shared_ptr responsePtr; + }; + typedef std::shared_ptr CtrlBinderPtr; + struct HttpControllerRouterItem + { + std::string pathParameterPattern; + std::regex _regex; + CtrlBinderPtr _binders[Invalid]; //The enum value Invalid is the http methods number + }; + std::vector _ctrlVector; + std::mutex _ctrlMutex; + std::regex _ctrlRegex; + HttpAppFrameworkImpl &_appImpl; +}; +} // namespace drogon \ No newline at end of file diff --git a/lib/src/HttpResponseImpl.h b/lib/src/HttpResponseImpl.h index 14ff3f30..c85c4a0a 100755 --- a/lib/src/HttpResponseImpl.h +++ b/lib/src/HttpResponseImpl.h @@ -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 _headers; std::unordered_map _cookies; HttpStatusCode _statusCode; + trantor::Date _createDate; // FIXME: add http version Version _v; std::string _statusMessage; diff --git a/lib/src/HttpSimpleControllersRouter.cc b/lib/src/HttpSimpleControllersRouter.cc new file mode 100644 index 00000000..8e541f5a --- /dev/null +++ b/lib/src/HttpSimpleControllersRouter.cc @@ -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 &filtersAndMethods) +{ + assert(!pathName.empty()); + assert(!ctrlName.empty()); + + std::string path(pathName); + std::transform(pathName.begin(), pathName.end(), path.begin(), tolower); + std::lock_guard guard(_simpCtrlMutex); + std::vector validMethods; + std::vector filters; + for (auto &filterOrMethod : filtersAndMethods) + { + if (filterOrMethod.type() == typeid(std::string)) + { + filters.push_back(*any_cast(&filterOrMethod)); + } + else if (filterOrMethod.type() == typeid(const char *)) + { + filters.push_back(*any_cast(&filterOrMethod)); + } + else if (filterOrMethod.type() == typeid(HttpMethod)) + { + validMethods.push_back(*any_cast(&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 &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 controller; + HttpResponsePtr responsePtr; + { + //maybe update controller,so we use lock_guard to protect; + std::lock_guard guard(ctrlItem._mutex); + controller = ctrlItem.controller; + responsePtr = ctrlItem.responsePtr; + if (!controller) + { + auto _object = std::shared_ptr(DrClassMap::newObject(ctrlName)); + controller = std::dynamic_pointer_cast(_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(*std::dynamic_pointer_cast(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(resp)->makeHeaderString(); + { + auto &item = _simpCtrlMap[pathLower]; + std::lock_guard guard(item._mutex); + item.responsePtr = resp; + } + } + if (needSetJsessionid) + { + //make a copy + newResp = std::make_shared(*std::dynamic_pointer_cast(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; +} \ No newline at end of file diff --git a/lib/src/HttpSimpleControllersRouter.h b/lib/src/HttpSimpleControllersRouter.h new file mode 100644 index 00000000..347b4412 --- /dev/null +++ b/lib/src/HttpSimpleControllersRouter.h @@ -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 +#include +#include +#include +#include +#include +#include +#include + +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 &filtersAndMethods); + bool route(const HttpRequestImplPtr &req, + const std::function &callback, + bool needSetJsessionid, + const std::string &session_id); + + private: + HttpAppFrameworkImpl &_appImpl; + struct SimpleControllerRouterItem + { + std::string controllerName; + std::vector filtersName; + std::vector _validMethodsFlags; + std::shared_ptr controller; + std::shared_ptr responsePtr; + std::mutex _mutex; + }; + std::unordered_map _simpCtrlMap; + std::mutex _simpCtrlMutex; +}; +} // namespace drogon diff --git a/lib/src/WebsocketControllersRouter.cc b/lib/src/WebsocketControllersRouter.cc new file mode 100644 index 00000000..ce7f4b37 --- /dev/null +++ b/lib/src/WebsocketControllersRouter.cc @@ -0,0 +1,72 @@ +#include "WebsocketControllersRouter.h" +#include "HttpAppFrameworkImpl.h" +#ifdef USE_OPENSSL +#include +#else +#include "ssl_funcs/Sha1.h" +#endif +using namespace drogon; + +void WebsocketControllersRouter::registerWebSocketController(const std::string &pathName, + const std::string &ctrlName, + const std::vector &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(DrClassMap::newObject(ctrlName)); + auto ctrlPtr = std::dynamic_pointer_cast(objPtr); + assert(ctrlPtr); + std::lock_guard guard(_websockCtrlMutex); + + _websockCtrlMap[path].controller = ctrlPtr; + _websockCtrlMap[path].filtersName = filters; +} + +void WebsocketControllersRouter::route(const HttpRequestImplPtr &req, + const std::function &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 filtersName; + { + std::string pathLower(req->path()); + std::transform(pathLower.begin(), pathLower.end(), pathLower.begin(), tolower); + std::lock_guard 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(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(wsConnPtr); + assert(wsConnImplPtr); + wsConnImplPtr->setController(ctrlPtr); + ctrlPtr->handleNewConnection(req, wsConnPtr); + return; + }); + return; + } + } + auto resp = drogon::HttpResponse::newNotFoundResponse(); + resp->setCloseConnection(true); + callback(resp); +} \ No newline at end of file diff --git a/lib/src/WebsocketControllersRouter.h b/lib/src/WebsocketControllersRouter.h new file mode 100644 index 00000000..7dc21694 --- /dev/null +++ b/lib/src/WebsocketControllersRouter.h @@ -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 +#include +#include +#include +#include +#include +#include + +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 &filters); + void route(const HttpRequestImplPtr &req, + const std::function &callback, + const WebSocketConnectionPtr &wsConnPtr); + + private: + HttpAppFrameworkImpl &_appImpl; + struct WebSocketControllerRouterItem + { + WebSocketControllerBasePtr controller; + std::vector filtersName; + }; + std::unordered_map _websockCtrlMap; + std::mutex _websockCtrlMutex; +}; +} // namespace drogon \ No newline at end of file From f4b6c6a5e21dfa6f3b8590148ed43c00c9230e62 Mon Sep 17 00:00:00 2001 From: antao Date: Thu, 17 Jan 2019 14:16:02 +0800 Subject: [PATCH 2/2] Change the version to 0.9.15 --- get_version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/get_version.sh b/get_version.sh index 7283244d..0e1b4d42 100755 --- a/get_version.sh +++ b/get_version.sh @@ -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