From 16f7af90d33ad4ac6f677cb67f90103a3f759e21 Mon Sep 17 00:00:00 2001 From: antao Date: Thu, 11 Oct 2018 11:20:40 +0800 Subject: [PATCH] Add response cache --- examples/simple_example/TestController.cc | 1 + examples/simple_example/api_v1_ApiTest.cc | 7 ++ examples/simple_example/api_v1_ApiTest.h | 2 + lib/inc/drogon/CacheMap.h | 7 +- lib/inc/drogon/HttpResponse.h | 5 ++ lib/src/HttpAppFrameworkImpl.cc | 59 ++++++++++-- lib/src/HttpAppFrameworkImpl.h | 5 ++ lib/src/HttpResponseImpl.cc | 105 ++++++++++++---------- lib/src/HttpResponseImpl.h | 15 ++++ lib/src/HttpServer.cc | 3 +- 10 files changed, 149 insertions(+), 60 deletions(-) diff --git a/examples/simple_example/TestController.cc b/examples/simple_example/TestController.cc index 00dc026a..0ecc511d 100755 --- a/examples/simple_example/TestController.cc +++ b/examples/simple_example/TestController.cc @@ -5,5 +5,6 @@ void TestController::asyncHandleHttpRequest(const HttpRequestPtr& req,const std: //write your application logic here auto resp=HttpResponse::newHttpResponse(); resp->setBody("

Hello, world!

"); + resp->setExpiredTime(0); callback(resp); } \ No newline at end of file diff --git a/examples/simple_example/api_v1_ApiTest.cc b/examples/simple_example/api_v1_ApiTest.cc index 5a7c3bfe..95f7cedc 100755 --- a/examples/simple_example/api_v1_ApiTest.cc +++ b/examples/simple_example/api_v1_ApiTest.cc @@ -23,4 +23,11 @@ void ApiTest::your_method_name(const HttpRequestPtr& req,const std::function&callback) +{ + auto resp=HttpResponse::newHttpResponse(); + resp->setBody("staticApi,hello!!"); + resp->setExpiredTime(0);//cache the response forever; + callback(resp); +} diff --git a/examples/simple_example/api_v1_ApiTest.h b/examples/simple_example/api_v1_ApiTest.h index 46c2a21c..cdee1b76 100755 --- a/examples/simple_example/api_v1_ApiTest.h +++ b/examples/simple_example/api_v1_ApiTest.h @@ -12,11 +12,13 @@ namespace api //use METHOD_ADD to add your custom processing function here; METHOD_ADD(ApiTest::get,"/get/{2}/{1}","drogon::GetFilter");//path will be /api/v1/apitest/get/{arg2}/{arg1} METHOD_ADD(ApiTest::your_method_name,"/{1}/list?p2={2}","drogon::GetFilter");//path will be /api/v1/apitest/{arg1}/list + METHOD_ADD(ApiTest::staticApi,"/static"); METHOD_LIST_END //your declaration of processing function maybe like this: void get(const HttpRequestPtr& req,const std::function&callback,int p1,std::string &&p2); void your_method_name(const HttpRequestPtr& req,const std::function&callback,double p1,int p2) const; + void staticApi(const HttpRequestPtr& req,const std::function&callback); }; } } diff --git a/lib/inc/drogon/CacheMap.h b/lib/inc/drogon/CacheMap.h index 32bd6124..d1ac4ad2 100755 --- a/lib/inc/drogon/CacheMap.h +++ b/lib/inc/drogon/CacheMap.h @@ -133,13 +133,14 @@ public: //If timeout>0,the value will be erased //within the 'timeout' seconds after the last access - void insert(const T1& key,T2&& value,size_t timeout=0,std::function timeoutCallback=std::function()) + template + void insert(const T1& key,V&& value,size_t timeout=0,std::function timeoutCallback=std::function()) { if(timeout>0) { std::lock_guard lock(mtx_); - _map[key].value=std::forward(value); + _map[key].value=std::forward(value); _map[key].timeout=timeout; _map[key]._timeoutCallback=std::move(timeoutCallback); @@ -148,7 +149,7 @@ public: else { std::lock_guard lock(mtx_); - _map[key].value=std::forward(value); + _map[key].value=std::forward(value); _map[key].timeout=timeout; _map[key]._timeoutCallback=std::function(); _map[key]._weakEntryPtr=WeakCallbackEntryPtr(); diff --git a/lib/inc/drogon/HttpResponse.h b/lib/inc/drogon/HttpResponse.h index e4a8355d..8f076e61 100755 --- a/lib/inc/drogon/HttpResponse.h +++ b/lib/inc/drogon/HttpResponse.h @@ -144,6 +144,8 @@ namespace drogon virtual void addCookie(const Cookie &cookie)=0; + virtual void removeCookie(const std::string& key)=0; + virtual void setBody(const std::string& body)=0; virtual void setBody(std::string&& body)=0; @@ -152,6 +154,9 @@ namespace drogon virtual void clear()=0; + virtual void setExpiredTime(ssize_t expiredTime)=0; + virtual ssize_t expiredTime() const=0; + virtual const std::string & getBody() const=0; virtual std::string & getBody() = 0; diff --git a/lib/src/HttpAppFrameworkImpl.cc b/lib/src/HttpAppFrameworkImpl.cc index d43549d8..5fc9631e 100755 --- a/lib/src/HttpAppFrameworkImpl.cc +++ b/lib/src/HttpAppFrameworkImpl.cc @@ -106,7 +106,7 @@ void HttpAppFrameworkImpl::setFileTypes(const std::vector &types) } void HttpAppFrameworkImpl::initRegex() { std::string regString; - for(auto binder:_apiCtrlVector) + for(auto &binder:_apiCtrlVector) { std::regex reg("\\(\\[\\^/\\]\\*\\)"); std::string tmp=std::regex_replace(binder.pathParameterPattern,reg,"[^/]*"); @@ -430,6 +430,8 @@ void HttpAppFrameworkImpl::run() { _sessionMapPtr=std::unique_ptr>(new CacheMap(&_loop)); } + _responseCacheMap=std::unique_ptr> + (new CacheMap(&_loop,1.0,4,50));//Max timeout up to about 70 days; _loop.loop(); } @@ -658,7 +660,7 @@ void HttpAppFrameworkImpl::onAsyncRequest(const HttpRequestPtr& req,const std::f LOG_TRACE << "new request:"<peerAddr().toIpPort()<<"->"<localAddr().toIpPort(); LOG_TRACE << "Headers " << req->methodString() << " " << req->path(); -#if 1 +#if 0 const std::map& headers = req->headers(); for (std::map::const_iterator it = headers.begin(); it != headers.end(); @@ -751,10 +753,12 @@ void HttpAppFrameworkImpl::onAsyncRequest(const HttpRequestPtr& req,const std::f doFilters(filters,req,callback,needSetJsessionid,session_id,[=](){ const std::string &ctrlName = _simpCtrlMap[pathLower].controllerName; std::shared_ptr controller; + HttpResponsePtr responsePtr; { //maybe update controller,so we use lock_guard to protect; std::lock_guard guard(_simpCtrlMap[pathLower]._mutex); controller=_simpCtrlMap[pathLower].controller; + responsePtr=_simpCtrlMap[pathLower].responsePtr.lock(); if(!controller) { auto _object = std::shared_ptr(DrClassMap::newObject(ctrlName)); @@ -765,11 +769,27 @@ void HttpAppFrameworkImpl::onAsyncRequest(const HttpRequestPtr& req,const std::f if(controller) { - controller->asyncHandleHttpRequest(req, [=](const HttpResponsePtr& resp){ - if(needSetJsessionid) - resp->addCookie("JSESSIONID",session_id); - callback(resp); - }); + if(responsePtr&&!needSetJsessionid) + { + callback(responsePtr); + } + else + { + controller->asyncHandleHttpRequest(req, [=](const HttpResponsePtr& resp){ + if(needSetJsessionid) + resp->addCookie("JSESSIONID",session_id); + callback(resp); + if(resp->expiredTime()>=0&&!needSetJsessionid) + { + //cache the response; + std::lock_guard guard(_simpCtrlMap[pathLower]._mutex); + _responseCacheMap->insert(pathLower,resp,resp->expiredTime()); + _simpCtrlMap[pathLower].responsePtr=resp; + + } + }); + } + return; } else { @@ -795,6 +815,23 @@ void HttpAppFrameworkImpl::onAsyncRequest(const HttpRequestPtr& req,const std::f LOG_TRACE<<"got api access,regex="< guard(*(binder.binderMtx)); + responsePtr=binder.responsePtr.lock(); + } + if(responsePtr) + { + //use cached response! + LOG_TRACE<<"Use cached response"; + callback(responsePtr); + return; + } + } std::vector params(binder.parameterPlaces.size()); std::smatch r; if(std::regex_match(req->path(),r,std::regex(binder.pathParameterPattern))) @@ -835,6 +872,14 @@ void HttpAppFrameworkImpl::onAsyncRequest(const HttpRequestPtr& req,const std::f if(needSetJsessionid) resp->addCookie("JSESSIONID",session_id); callback(resp); + if(resp->expiredTime()>=0&&!needSetJsessionid) + { + //cache the response; + std::lock_guard guard(*(_apiCtrlVector[ctlIndex].binderMtx)); + _responseCacheMap->insert(_apiCtrlVector[ctlIndex].pathParameterPattern,resp,resp->expiredTime()); + _apiCtrlVector[ctlIndex].responsePtr=resp; + + } }); return; }); diff --git a/lib/src/HttpAppFrameworkImpl.h b/lib/src/HttpAppFrameworkImpl.h index c3850d70..ab6c700b 100644 --- a/lib/src/HttpAppFrameworkImpl.h +++ b/lib/src/HttpAppFrameworkImpl.h @@ -100,6 +100,8 @@ namespace drogon typedef std::shared_ptr SessionPtr; std::unique_ptr> _sessionMapPtr; + std::unique_ptr> _responseCacheMap; + void doFilters(const std::vector &filters, const HttpRequestPtr& req, const std::function & callback, @@ -118,6 +120,7 @@ namespace drogon std::string controllerName; std::vector filtersName; std::shared_ptr controller; + std::weak_ptr responsePtr; std::mutex _mutex; }; std::unordered_map_simpCtrlMap; @@ -137,6 +140,8 @@ namespace drogon std::map queryParametersPlaces; HttpApiBinderBasePtr binderPtr; std::vector filtersName; + std::unique_ptr binderMtx=std::unique_ptr(new std::mutex); + std::weak_ptr responsePtr; }; //std::unordered_map_apiCtrlMap; std::vector_apiCtrlVector; diff --git a/lib/src/HttpResponseImpl.cc b/lib/src/HttpResponseImpl.cc index f7ac4ddd..fddec094 100755 --- a/lib/src/HttpResponseImpl.cc +++ b/lib/src/HttpResponseImpl.cc @@ -312,65 +312,72 @@ std::string HttpResponseImpl::web_response_code_to_string(int code) return "Undefined Error"; } } -void HttpResponseImpl::appendToBuffer(MsgBuffer* output) const -{ - char buf[64]; - snprintf(buf, sizeof buf, "HTTP/1.1 %d ", _statusCode); - output->append(buf); - output->append(_statusMessage); - output->append("\r\n"); - if(_sendfileName.empty()) +void HttpResponseImpl::appendToBuffer(MsgBuffer* output) const { + if(_fullHeaderString.empty()) { - snprintf(buf, sizeof buf, "Content-Length: %lu\r\n", _body.size()); + char buf[64]; + snprintf(buf, sizeof buf, "HTTP/1.1 %d ", _statusCode); + output->append(buf); + output->append(_statusMessage); + output->append("\r\n"); + if (_sendfileName.empty()) { + snprintf(buf, sizeof buf, "Content-Length: %lu\r\n", _body.size()); + } else { + struct stat filestat; + if (stat(_sendfileName.c_str(), &filestat) < 0) { + LOG_SYSERR << _sendfileName << " stat error"; + return; + } +#ifdef __linux__ + snprintf(buf, sizeof buf, "Content-Length: %ld\r\n",filestat.st_size); +#else + snprintf(buf, sizeof buf, "Content-Length: %lld\r\n", filestat.st_size); +#endif + } + + output->append(buf); + if (_headers.find("Connection") == _headers.end()) { + if (_closeConnection) { + output->append("Connection: close\r\n"); + } else { + + //output->append("Connection: Keep-Alive\r\n"); + } + } + + + for (auto it = _headers.begin(); + it != _headers.end(); + ++it) { + output->append(it->first); + output->append(": "); + output->append(it->second); + output->append("\r\n"); + } + + output->append("Server: drogon/"); + output->append(drogon::getVersion()); + output->append("\r\n"); + + if(_cookies.size() > 0) { + for(auto it = _cookies.begin(); it != _cookies.end(); it++) { + + output->append(it->second.cookieString()); + } + } + + if(_expriedTime>=0) + _fullHeaderString=std::string(output->peek(),output->readableBytes()); } else { - struct stat filestat; - if(stat(_sendfileName.c_str(), &filestat)<0) - { - LOG_SYSERR<<_sendfileName<<" stat error"; - return; - } -#ifdef __linux__ - snprintf(buf, sizeof buf, "Content-Length: %ld\r\n",filestat.st_size); -#else - snprintf(buf, sizeof buf, "Content-Length: %lld\r\n",filestat.st_size); -#endif - } - - output->append(buf); - if(_headers.find("Connection")==_headers.end()) - { - if (_closeConnection) { - output->append("Connection: close\r\n"); - } else { - - //output->append("Connection: Keep-Alive\r\n"); - } + output->append(_fullHeaderString); } - for (auto it = _headers.begin(); - it != _headers.end(); - ++it) { - output->append(it->first); - output->append(": "); - output->append(it->second); - output->append("\r\n"); - } //output Date header output->append("Date: "); output->append(getHttpFullDate(trantor::Date::date())); - output->append("\r\n"); - - if(_cookies.size() > 0) { - for(auto it = _cookies.begin(); it != _cookies.end(); it++) { - - output->append(it->second.cookieString()); - } - } - output->append("Server: drogon/"); - output->append(drogon::getVersion()); output->append("\r\n\r\n"); LOG_TRACE<<"reponse(no body):"<peek(); diff --git a/lib/src/HttpResponseImpl.h b/lib/src/HttpResponseImpl.h index 06630e88..594327bb 100755 --- a/lib/src/HttpResponseImpl.h +++ b/lib/src/HttpResponseImpl.h @@ -193,6 +193,11 @@ namespace drogon _cookies.insert(std::make_pair(cookie.key(),cookie)); } + virtual void removeCookie(const std::string& key) override + { + _cookies.erase(key); + } + virtual void setBody(const std::string& body) override { _body = body; @@ -220,6 +225,13 @@ namespace drogon _current_chunk_length = 0; } + virtual void setExpiredTime(ssize_t expiredTime) override + { + _expriedTime=expiredTime; + } + + virtual ssize_t expiredTime() const override {return _expriedTime;} + // void setReceiveTime(trantor::Date t) // { // receiveTime_ = t; @@ -294,8 +306,11 @@ namespace drogon size_t _current_chunk_length; uint8_t _contentType=CT_TEXT_HTML; + ssize_t _expriedTime=-1; std::string _sendfileName; mutable std::shared_ptr _jsonPtr; + + mutable std::string _fullHeaderString; //trantor::Date receiveTime_; void setContentType(const std::string& contentType) diff --git a/lib/src/HttpServer.cc b/lib/src/HttpServer.cc index 5da3407e..01715e31 100755 --- a/lib/src/HttpServer.cc +++ b/lib/src/HttpServer.cc @@ -182,7 +182,8 @@ void HttpServer::onRequest(const TcpConnectionPtr& conn, const HttpRequestPtr& r if(sendfileName.empty()&& response->getContentTypeCode()getBody().length()>1024&& - req->getHeader("Accept-Encoding").find("gzip")!=std::string::npos) + req->getHeader("Accept-Encoding").find("gzip")!=std::string::npos&& + response->getHeader("Content-Encoding")!="") { //use gzip LOG_TRACE<<"Use gzip to compress the body";