Merge pull request #28 from an-tao/dev

Split the HttpAppFrameworkImpl class
This commit is contained in:
An Tao 2019-01-17 14:23:12 +08:00 committed by GitHub
commit d1ae4fcc2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 739 additions and 500 deletions

View File

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

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

@ -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

View File

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

View File

@ -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;
}

View File

@ -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

View File

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

View File

@ -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