From 27e7101322ec8a095f5493a83eeb308e1e5f3144 Mon Sep 17 00:00:00 2001 From: samuele3hu Date: Fri, 22 May 2015 10:47:52 +0800 Subject: [PATCH] fix the memory leak of s_requestSentinel for httpclient and adjust the global static var into the object to avoid some unexpected result. --- cocos/network/HttpClient-android.cpp | 368 ++++++++++++-------- cocos/network/HttpClient-apple.mm | 467 +++++++++++++++----------- cocos/network/HttpClient.cpp | 479 +++++++++++++++------------ cocos/network/HttpClient.h | 81 ++++- 4 files changed, 837 insertions(+), 558 deletions(-) diff --git a/cocos/network/HttpClient-android.cpp b/cocos/network/HttpClient-android.cpp index 421c2b35a9..0eeec87b5a 100644 --- a/cocos/network/HttpClient-android.cpp +++ b/cocos/network/HttpClient-android.cpp @@ -26,17 +26,12 @@ #include "HttpClient.h" -#include #include #include -#include #include #include -#include "base/CCVector.h" #include "base/CCDirector.h" -#include "base/CCScheduler.h" - #include "platform/CCFileUtils.h" #include "platform/android/jni/JniHelper.h" @@ -49,24 +44,7 @@ typedef HttpRequestHeaders::iterator HttpRequestHeadersIter; typedef std::vector HttpCookies; typedef HttpCookies::iterator HttpCookiesIter; -static std::mutex s_requestQueueMutex; -static std::mutex s_responseQueueMutex; -static std::mutex s_cookieFileMutex; - -static std::condition_variable_any s_sleepCondition; - -static Vector* s_requestQueue = nullptr; -static Vector* s_responseQueue = nullptr; - -static HttpClient *s_httpClient = nullptr; // pointer to singleton - -static std::string s_responseMessage = ""; - -static std::string s_cookieFilename = ""; - -static std::string s_sslCaFilename = ""; - -static HttpRequest *s_requestSentinel = new HttpRequest; +static HttpClient* _httpClient = nullptr; // pointer to singleton struct CookiesInfo @@ -98,16 +76,16 @@ size_t writeHeaderData(void* buffer, size_t sizes,HttpResponse* response) return sizes; } -static void processResponse(HttpResponse* response, std::string& responseMessage); - class HttpURLConnection { public: - HttpURLConnection():_httpURLConnection(nullptr), - _requestmethod(""), - _responseCookies(""), - _cookieFileName(""), - _contentLength(0) + HttpURLConnection(HttpClient* httpClient) + :_httpURLConnection(nullptr) + ,_client(httpClient) + ,_requestmethod("") + ,_responseCookies("") + ,_cookieFileName("") + ,_contentLength(0) { } @@ -120,7 +98,7 @@ public: void setRequestMethod(const char* method) { _requestmethod = method; - + JniMethodInfo methodInfo; if (JniHelper::getStaticMethodInfo(methodInfo, "org/cocos2dx/lib/Cocos2dxHttpURLConnection", @@ -139,7 +117,9 @@ public: { createHttpURLConnection(request->getUrl()); if(!configure()) + { return false; + } /* get custom header data (if set) */ HttpRequestHeaders headers=request->getHeaders(); if(!headers.empty()) @@ -259,15 +239,15 @@ public: size_t saveResponseCookies(const char* responseCookies, size_t count) { if (nullptr == responseCookies || strlen(responseCookies) == 0 || count == 0) + { return 0; + } if (_cookieFileName.empty()) { _cookieFileName = FileUtils::getInstance()->getWritablePath() + "cookieFile.txt"; } - std::lock_guard lock(s_cookieFileMutex); - FILE* fp = fopen(_cookieFileName.c_str(), "w"); if (nullptr == fp) { @@ -307,7 +287,9 @@ public: char* getResponseContent(HttpResponse* response) { if (nullptr == response) + { return nullptr; + } char* content = nullptr; JniMethodInfo methodInfo; @@ -447,14 +429,14 @@ private: void addCookiesForRequestHeader() { - if(s_cookieFilename.empty()) + if(_client->getCookieFilename().empty()) + { return; + } - _cookieFileName = FileUtils::getInstance()->fullPathForFilename(s_cookieFilename.c_str()); + _cookieFileName = FileUtils::getInstance()->fullPathForFilename(_client->getCookieFilename()); - std::lock_guard lock(s_cookieFileMutex); - - std::string cookiesInfo = FileUtils::getInstance()->getStringFromFile(_cookieFileName.c_str()); + std::string cookiesInfo = FileUtils::getInstance()->getStringFromFile(_cookieFileName); if (cookiesInfo.empty()) return; @@ -550,10 +532,10 @@ private: void setVerifySSL() { - if(s_sslCaFilename.empty()) + if(_client->getSSLVerification().empty()) return; - std::string fullpath = FileUtils::getInstance()->fullPathForFilename(s_sslCaFilename.c_str()); + std::string fullpath = FileUtils::getInstance()->fullPathForFilename(_client->getSSLVerification()); JniMethodInfo methodInfo; if (JniHelper::getStaticMethodInfo(methodInfo, @@ -572,13 +554,16 @@ private: bool configure() { if(nullptr == _httpURLConnection) + { return false; + } - HttpClient* instnace = HttpClient::getInstance(); - if(nullptr == instnace) + if(nullptr == _client) + { return false; + } - setReadAndConnectTimeout(instnace->getTimeoutForRead() * 1000, instnace->getTimeoutForConnect() * 1000); + setReadAndConnectTimeout(_client->getTimeoutForRead() * 1000, _client->getTimeoutForConnect() * 1000); setVerifySSL(); @@ -587,7 +572,7 @@ private: char* getBufferFromJString(jstring jstr, JNIEnv* env) { - if (nullptr == jstr) + if (nullptr == jstr) { return nullptr; } @@ -595,7 +580,7 @@ private: const char* str = nullptr; char* ret = nullptr; str = env->GetStringUTFChars(jstr, nullptr); - if (nullptr != str) + if (nullptr != str) { ret = strdup(str); } @@ -605,9 +590,9 @@ private: return ret; } - int getCStrFromJByteArray(jbyteArray jba, JNIEnv* env, char** ppData) + int getCStrFromJByteArray(jbyteArray jba, JNIEnv* env, char** ppData) { - if (nullptr == jba) + if (nullptr == jba) { *ppData = nullptr; return 0; @@ -628,6 +613,7 @@ private: return _responseCookies; } private: + HttpClient* _client; jobject _httpURLConnection; std::string _requestmethod; std::string _responseCookies; @@ -637,13 +623,13 @@ private: }; // Process Response -static void processResponse(HttpResponse* response, std::string& responseMessage) +void HttpClient::processResponse(HttpResponse* response, char* responseMessage) { auto request = response->getHttpRequest(); HttpRequest::Type requestType = request->getRequestType(); - if (HttpRequest::Type::GET != requestType && - HttpRequest::Type::POST != requestType && + if (HttpRequest::Type::GET != requestType && + HttpRequest::Type::POST != requestType && HttpRequest::Type::PUT != requestType && HttpRequest::Type::DELETE != requestType) { @@ -654,7 +640,7 @@ static void processResponse(HttpResponse* response, std::string& responseMessage long responseCode = -1; int retValue = 0; - HttpURLConnection urlConnection; + HttpURLConnection urlConnection(this); if(!urlConnection.init(request)) { response->setSucceed(false); @@ -718,24 +704,24 @@ static void processResponse(HttpResponse* response, std::string& responseMessage //content len int contentLength = urlConnection.getResponseHeaderByKeyInt("Content-Length"); char* contentInfo = urlConnection.getResponseContent(response); - if (nullptr != contentInfo) { -// response->setResponseDataString(contentInfo, contentLength); + if (nullptr != contentInfo) + { std::vector * recvBuffer = (std::vector*)response->getResponseData(); recvBuffer->clear(); recvBuffer->insert(recvBuffer->begin(), (char*)contentInfo, ((char*)contentInfo) + urlConnection.getContentLength()); } free(contentInfo); - responseMessage = urlConnection.getResponseMessage(); + strcpy(responseMessage, urlConnection.getResponseMessage()); urlConnection.disconnect(); // write data to HttpResponse response->setResponseCode(responseCode); - if (responseCode == -1) + if (responseCode == -1) { response->setSucceed(false); - response->setErrorBuffer(responseMessage.c_str()); + response->setErrorBuffer(responseMessage); } else { @@ -746,174 +732,209 @@ static void processResponse(HttpResponse* response, std::string& responseMessage // Worker thread void HttpClient::networkThread() { - auto scheduler = Director::getInstance()->getScheduler(); - + increaseThreadCount(); + while (true) { HttpRequest *request; // step 1: send http request if the requestQueue isn't empty { - std::lock_guard lock(s_requestQueueMutex); - while (s_requestQueue->empty()) { - s_sleepCondition.wait(s_requestQueueMutex); + std::lock_guard lock(_requestQueueMutex); + while (_requestQueue.empty()) { + _sleepCondition.wait(_requestQueueMutex); } - request = s_requestQueue->at(0); - s_requestQueue->erase(0); + request = _requestQueue.at(0); + _requestQueue.erase(0); } - if (request == s_requestSentinel) { + if (request == _requestSentinel) { break; } // Create a HttpResponse object, the default setting is http access failed HttpResponse *response = new (std::nothrow) HttpResponse(request); - processResponse(response, s_responseMessage); + processResponse(response, _responseMessage); // add response packet into queue - s_responseQueueMutex.lock(); - s_responseQueue->pushBack(response); - s_responseQueueMutex.unlock(); + _responseQueueMutex.lock(); + _responseQueue.pushBack(response); + _responseQueueMutex.unlock(); - if (nullptr != s_httpClient) { - scheduler->performFunctionInCocosThread(CC_CALLBACK_0(HttpClient::dispatchResponseCallbacks, this)); + _schedulerMutex.lock(); + if (nullptr != _scheduler) + { + _scheduler->performFunctionInCocosThread(CC_CALLBACK_0(HttpClient::dispatchResponseCallbacks, this)); } + _schedulerMutex.unlock(); } // cleanup: if worker thread received quit signal, clean up un-completed request queue - s_requestQueueMutex.lock(); - s_requestQueue->clear(); - s_requestQueueMutex.unlock(); - - - if (s_requestQueue != nullptr) { - delete s_requestQueue; - s_requestQueue = nullptr; - delete s_responseQueue; - s_responseQueue = nullptr; - } + _requestQueueMutex.lock(); + _requestQueue.clear(); + _requestQueueMutex.unlock(); + _responseQueueMutex.lock(); + _responseQueue.clear(); + _responseQueueMutex.unlock(); + + decreaseThreadCountAndMayDeleteThis(); } // Worker thread void HttpClient::networkThreadAlone(HttpRequest* request, HttpResponse* response) { - std::string responseMessage = ""; + increaseThreadCount(); + + char responseMessage[RESPONSE_BUFFER_SIZE] = { 0 }; processResponse(response, responseMessage); - auto scheduler = Director::getInstance()->getScheduler(); - scheduler->performFunctionInCocosThread([response, request]{ - const ccHttpRequestCallback& callback = request->getCallback(); - Ref* pTarget = request->getTarget(); - SEL_HttpResponse pSelector = request->getSelector(); + _schedulerMutex.lock(); + if (_scheduler != nullptr) + { + _scheduler->performFunctionInCocosThread([this, response, request]{ + const ccHttpRequestCallback& callback = request->getCallback(); + Ref* pTarget = request->getTarget(); + SEL_HttpResponse pSelector = request->getSelector(); - if (callback != nullptr) - { - callback(s_httpClient, response); - } - else if (pTarget && pSelector) - { - (pTarget->*pSelector)(s_httpClient, response); - } - response->release(); - // do not release in other thread - request->release(); - }); + if (callback != nullptr) + { + callback(this, response); + } + else if (pTarget && pSelector) + { + (pTarget->*pSelector)(this, response); + } + response->release(); + // do not release in other thread + request->release(); + }); + } + _schedulerMutex.unlock(); + decreaseThreadCountAndMayDeleteThis(); } // HttpClient implementation HttpClient* HttpClient::getInstance() { - if (s_httpClient == nullptr) { - s_httpClient = new (std::nothrow) HttpClient(); + if (_httpClient == nullptr) + { + _httpClient = new (std::nothrow) HttpClient(); } - return s_httpClient; + return _httpClient; } void HttpClient::destroyInstance() { - CC_SAFE_DELETE(s_httpClient); + if (_httpClient == nullptr) + { + CCLOG("HttpClient singleton is nullptr"); + return; + } + + CCLOG("HttpClient::destroyInstance ..."); + + auto thiz = _httpClient; + _httpClient = nullptr; + + thiz->_scheduler->unscheduleAllForTarget(thiz); + + thiz->_schedulerMutex.lock(); + thiz->_scheduler = nullptr; + thiz->_schedulerMutex.unlock(); + + { + std::lock_guard lock(thiz->_requestQueueMutex); + thiz->_requestQueue.pushBack(thiz->_requestSentinel); + } + thiz->_sleepCondition.notify_one(); + + thiz->decreaseThreadCountAndMayDeleteThis(); + CCLOG("HttpClient::destroyInstance() finished!"); } -void HttpClient::enableCookies(const char* cookieFile) { - if (cookieFile) { - s_cookieFilename = std::string(cookieFile); +void HttpClient::enableCookies(const char* cookieFile) +{ + std::lock_guard lock(_cookieFileMutex); + if (cookieFile) + { + _cookieFilename = std::string(cookieFile); } - else { - s_cookieFilename = (FileUtils::getInstance()->getWritablePath() + "cookieFile.txt"); + else + { + _cookieFilename = (FileUtils::getInstance()->getWritablePath() + "cookieFile.txt"); } } void HttpClient::setSSLVerification(const std::string& caFile) { - s_sslCaFilename = caFile; + std::lock_guard lock(_sslCaFileMutex); + _sslCaFilename = caFile; } HttpClient::HttpClient() : _timeoutForConnect(30) , _timeoutForRead(60) +, _isInited(false) +, _threadCount(0) +, _requestSentinel(new HttpRequest()) +, _cookie(nullptr) { + CCLOG("In the constructor of HttpClient!"); + increaseThreadCount(); + _scheduler = Director::getInstance()->getScheduler(); } HttpClient::~HttpClient() { - if (s_requestQueue != nullptr) { - { - std::lock_guard lock(s_requestQueueMutex); - s_requestQueue->pushBack(s_requestSentinel); - } - s_sleepCondition.notify_one(); - } - - s_httpClient = nullptr; + CCLOG("In the destructor of HttpClient!"); + CC_SAFE_DELETE(_requestSentinel); } //Lazy create semaphore & mutex & thread bool HttpClient::lazyInitThreadSemphore() { - if (s_requestQueue != nullptr) { + if (_isInited) + { return true; - } else { - - s_requestQueue = new (std::nothrow) Vector(); - s_responseQueue = new (std::nothrow) Vector(); - + } + else + { auto t = std::thread(CC_CALLBACK_0(HttpClient::networkThread, this)); t.detach(); + _isInited = true; } - + return true; } //Add a get task to queue void HttpClient::send(HttpRequest* request) { - if (false == lazyInitThreadSemphore()) + if (!lazyInitThreadSemphore()) { return; } - if (!request) + if (nullptr == request) { return; } request->retain(); - if (nullptr != s_requestQueue) { - s_requestQueueMutex.lock(); - s_requestQueue->pushBack(request); - s_requestQueueMutex.unlock(); - - // Notify thread start to work - s_sleepCondition.notify_one(); - } + _requestQueueMutex.lock(); + _requestQueue.pushBack(request); + _requestQueueMutex.unlock(); + + // Notify thread start to work + _sleepCondition.notify_one(); } void HttpClient::sendImmediate(HttpRequest* request) { - if(!request) + if(nullptr == request) { return; } @@ -931,20 +952,17 @@ void HttpClient::dispatchResponseCallbacks() { // log("CCHttpClient::dispatchResponseCallbacks is running"); //occurs when cocos thread fires but the network thread has already quited - if (nullptr == s_responseQueue) { - return; - } HttpResponse* response = nullptr; - s_responseQueueMutex.lock(); + _responseQueueMutex.lock(); - if (!s_responseQueue->empty()) + if (!_responseQueue.empty()) { - response = s_responseQueue->at(0); - s_responseQueue->erase(0); + response = _responseQueue.at(0); + _responseQueue.erase(0); } - - s_responseQueueMutex.unlock(); + + _responseQueueMutex.unlock(); if (response) { @@ -968,6 +986,66 @@ void HttpClient::dispatchResponseCallbacks() } } +void HttpClient::increaseThreadCount() +{ + _threadCountMutex.lock(); + ++_threadCount; + _threadCountMutex.unlock(); +} + +void HttpClient::decreaseThreadCountAndMayDeleteThis() +{ + bool needDeleteThis = false; + _threadCountMutex.lock(); + --_threadCount; + if (0 == _threadCount) + { + needDeleteThis = true; + } + + _threadCountMutex.unlock(); + if (needDeleteThis) + { + delete this; + } +} + +void HttpClient::setTimeoutForConnect(int value) +{ + std::lock_guard lock(_timeoutForConnectMutex); + _timeoutForConnect = value; +} + +int HttpClient::getTimeoutForConnect() +{ + std::lock_guard lock(_timeoutForConnectMutex); + return _timeoutForConnect; +} + +void HttpClient::setTimeoutForRead(int value) +{ + std::lock_guard lock(_timeoutForReadMutex); + _timeoutForRead = value; +} + +int HttpClient::getTimeoutForRead() +{ + std::lock_guard lock(_timeoutForReadMutex); + return _timeoutForRead; +} + +const std::string& HttpClient::getCookieFilename() +{ + std::lock_guard lock(_cookieFileMutex); + return _cookieFilename; +} + +const std::string& HttpClient::getSSLVerification() +{ + std::lock_guard lock(_sslCaFileMutex); + return _sslCaFilename; +} + } NS_CC_END diff --git a/cocos/network/HttpClient-apple.mm b/cocos/network/HttpClient-apple.mm index 34c9f64d0e..0e144acb5c 100644 --- a/cocos/network/HttpClient-apple.mm +++ b/cocos/network/HttpClient-apple.mm @@ -26,56 +26,26 @@ #include "HttpClient.h" -#include #include -#include - #include -#import "HttpAsynConnection.h" -#include "HttpCookie.h" - -#include "base/CCVector.h" +#import "network/HttpAsynConnection.h" +#include "network/HttpCookie.h" #include "base/CCDirector.h" -#include "base/CCScheduler.h" - #include "platform/CCFileUtils.h" NS_CC_BEGIN namespace network { - -static std::mutex s_requestQueueMutex; -static std::mutex s_responseQueueMutex; - -static std::condition_variable_any s_SleepCondition; - -static Vector* s_requestQueue = nullptr; -static Vector* s_responseQueue = nullptr; - -static HttpClient *s_HttpClient = nullptr; // pointer to singleton -static HttpCookie *s_cookie = nullptr; - -static const int ERROR_SIZE = 256; +static HttpClient *_httpClient = nullptr; // pointer to singleton -static char s_errorBuffer[ERROR_SIZE] = {0}; - -static std::string s_cookieFilename = ""; - -static std::string s_sslCaFilename = ""; - - -static int processTask(HttpRequest *request, NSString *requestType, void *stream, long *errorCode, void *headerStream, char *errorBuffer); - -static void processResponse(HttpResponse* response, char* errorBuffer); - -static HttpRequest *s_requestSentinel = new HttpRequest; +static int processTask(HttpClient* client, HttpRequest *request, NSString *requestType, void *stream, long *errorCode, void *headerStream, char *errorBuffer); // Worker thread void HttpClient::networkThread() -{ - auto scheduler = Director::getInstance()->getScheduler(); +{ + increaseThreadCount(); while (true) @autoreleasepool { @@ -83,77 +53,90 @@ void HttpClient::networkThread() // step 1: send http request if the requestQueue isn't empty { - std::lock_guard lock(s_requestQueueMutex); - while (s_requestQueue->empty()) { - s_SleepCondition.wait(s_requestQueueMutex); + std::lock_guard lock(_requestQueueMutex); + while (_requestQueue.empty()) { + _sleepCondition.wait(_requestQueueMutex); } - request = s_requestQueue->at(0); - s_requestQueue->erase(0); + request = _requestQueue.at(0); + _requestQueue.erase(0); } - if (request == s_requestSentinel) { + if (request == _requestSentinel) { break; } // Create a HttpResponse object, the default setting is http access failed HttpResponse *response = new (std::nothrow) HttpResponse(request); - processResponse(response, s_errorBuffer); + processResponse(response, _responseMessage); // add response packet into queue - s_responseQueueMutex.lock(); - s_responseQueue->pushBack(response); - s_responseQueueMutex.unlock(); + _responseQueueMutex.lock(); + _responseQueue.pushBack(response); + _responseQueueMutex.unlock(); - if (nullptr != s_HttpClient) { - scheduler->performFunctionInCocosThread(CC_CALLBACK_0(HttpClient::dispatchResponseCallbacks, this)); + _schedulerMutex.lock(); + if (nullptr != _scheduler) + { + _scheduler->performFunctionInCocosThread(CC_CALLBACK_0(HttpClient::dispatchResponseCallbacks, this)); } + _schedulerMutex.unlock(); } // cleanup: if worker thread received quit signal, clean up un-completed request queue - s_requestQueueMutex.lock(); - s_requestQueue->clear(); - s_requestQueueMutex.unlock(); + _requestQueueMutex.lock(); + _requestQueue.clear(); + _requestQueueMutex.unlock(); + _responseQueueMutex.lock(); + _responseQueue.clear(); + _responseQueueMutex.unlock(); - if (s_requestQueue != nullptr) { - delete s_requestQueue; - s_requestQueue = nullptr; - delete s_responseQueue; - s_responseQueue = nullptr; - } - + decreaseThreadCountAndMayDeleteThis(); } // Worker thread void HttpClient::networkThreadAlone(HttpRequest* request, HttpResponse* response) { - char errorBuffer[ERROR_SIZE] = { 0 }; - processResponse(response, errorBuffer); - - auto scheduler = Director::getInstance()->getScheduler(); - scheduler->performFunctionInCocosThread([response, request]{ - const ccHttpRequestCallback& callback = request->getCallback(); - Ref* pTarget = request->getTarget(); - SEL_HttpResponse pSelector = request->getSelector(); - - if (callback != nullptr) - { - callback(s_HttpClient, response); - } - else if (pTarget && pSelector) - { - (pTarget->*pSelector)(s_HttpClient, response); - } - response->release(); - // do not release in other thread - request->release(); - }); + increaseThreadCount(); + + char responseMessage[RESPONSE_BUFFER_SIZE] = { 0 }; + processResponse(response, responseMessage); + + _schedulerMutex.lock(); + if (nullptr != _scheduler) + { + _scheduler->performFunctionInCocosThread([this, response, request]{ + const ccHttpRequestCallback& callback = request->getCallback(); + Ref* pTarget = request->getTarget(); + SEL_HttpResponse pSelector = request->getSelector(); + + if (callback != nullptr) + { + callback(this, response); + } + else if (pTarget && pSelector) + { + (pTarget->*pSelector)(this, response); + } + response->release(); + // do not release in other thread + request->release(); + }); + } + _schedulerMutex.unlock(); + decreaseThreadCountAndMayDeleteThis(); } //Process Request -static int processTask(HttpRequest *request, NSString* requestType, void *stream, long *responseCode, void *headerStream, char *errorBuffer) +static int processTask(HttpClient* client, HttpRequest* request, NSString* requestType, void* stream, long* responseCode, void* headerStream, char* errorBuffer) { + if (nullptr == client) + { + strcpy(errorBuffer, "client object is invalid"); + return 0; + } + //create request with url NSString* urlstring = [NSString stringWithUTF8String:request->getUrl()]; NSURL *url = [NSURL URLWithString:urlstring]; @@ -199,9 +182,10 @@ static int processTask(HttpRequest *request, NSString* requestType, void *stream } //read cookie propertities from file and set cookie - if(!s_cookieFilename.empty()) + std::string cookieFilename = client->getCookieFilename(); + if(!cookieFilename.empty() && nullptr != client->getCookie()) { - const CookiesInfo* cookieInfo = s_cookie->getMatchCookie(request->getUrl()); + const CookiesInfo* cookieInfo = client->getCookie()->getMatchCookie(request->getUrl()); if(cookieInfo != nullptr) { NSString *domain = [NSString stringWithCString:cookieInfo->domain.c_str() encoding:[NSString defaultCStringEncoding]]; @@ -227,12 +211,13 @@ static int processTask(HttpRequest *request, NSString* requestType, void *stream httpAsynConn.srcURL = urlstring; httpAsynConn.sslFile = nil; - if(!s_sslCaFilename.empty()) + std::string sslCaFileName = client->getSSLVerification(); + if(!sslCaFileName.empty()) { - long len = s_sslCaFilename.length(); - long pos = s_sslCaFilename.rfind('.', len-1); + long len = sslCaFileName.length(); + long pos = sslCaFileName.rfind('.', len-1); - httpAsynConn.sslFile = [NSString stringWithUTF8String:s_sslCaFilename.substr(0, pos-1).c_str()]; + httpAsynConn.sslFile = [NSString stringWithUTF8String:sslCaFileName.substr(0, pos-1).c_str()]; } [httpAsynConn startRequest:nsrequest]; @@ -259,7 +244,7 @@ static int processTask(HttpRequest *request, NSString* requestType, void *stream *responseCode = httpAsynConn.responseCode; //add cookie to cookies vector - if(!s_cookieFilename.empty()) + if(!cookieFilename.empty()) { NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:httpAsynConn.responseHeader forURL:url]; for (NSHTTPCookie *cookie in cookies) @@ -282,7 +267,7 @@ static int processTask(HttpRequest *request, NSString* requestType, void *stream cookieInfo.value = [value cStringUsingEncoding: NSUTF8StringEncoding]; cookieInfo.tailmatch = true; - s_cookie->updateOrAddCookie(&cookieInfo); + client->getCookie()->updateOrAddCookie(&cookieInfo); } } @@ -313,131 +298,112 @@ static int processTask(HttpRequest *request, NSString* requestType, void *stream return 1; } -// Process Response -static void processResponse(HttpResponse* response, char* errorBuffer) -{ - auto request = response->getHttpRequest(); - long responseCode = -1; - int retValue = 0; - NSString* requestType = nil; - - // Process the request -> get response packet - switch (request->getRequestType()) - { - case HttpRequest::Type::GET: // HTTP GET - requestType = @"GET"; - break; - - case HttpRequest::Type::POST: // HTTP POST - requestType = @"POST"; - break; - - case HttpRequest::Type::PUT: - requestType = @"PUT"; - break; - - case HttpRequest::Type::DELETE: - requestType = @"DELETE"; - break; - - default: - CCASSERT(true, "CCHttpClient: unknown request type, only GET and POSt are supported"); - break; - } - - retValue = processTask(request, - requestType, - response->getResponseData(), - &responseCode, - response->getResponseHeader(), - errorBuffer); - - // write data to HttpResponse - response->setResponseCode(responseCode); - - if (retValue != 0) - { - response->setSucceed(true); - } - else - { - response->setSucceed(false); - response->setErrorBuffer(errorBuffer); - } -} - // HttpClient implementation HttpClient* HttpClient::getInstance() { - if (s_HttpClient == nullptr) { - s_HttpClient = new (std::nothrow) HttpClient(); + if (_httpClient == nullptr) + { + _httpClient = new (std::nothrow) HttpClient(); } - return s_HttpClient; + return _httpClient; } void HttpClient::destroyInstance() { - CC_SAFE_DELETE(s_HttpClient); -} - -void HttpClient::enableCookies(const char* cookieFile) { - if (cookieFile) { - s_cookieFilename = std::string(cookieFile); - s_cookieFilename = FileUtils::getInstance()->fullPathForFilename(s_cookieFilename); - } - else { - s_cookieFilename = (FileUtils::getInstance()->getWritablePath() + "cookieFile.txt"); + if (nullptr == _httpClient) + { + CCLOG("HttpClient singleton is nullptr"); + return; } - s_cookie = new(std::nothrow)HttpCookie; - s_cookie->setCookieFileName(s_cookieFilename); - s_cookie->readFile(); + CCLOG("HttpClient::destroyInstance begin"); + + auto thiz = _httpClient; + _httpClient = nullptr; + + thiz->_scheduler->unscheduleAllForTarget(thiz); + thiz->_schedulerMutex.lock(); + thiz->_scheduler = nullptr; + thiz->_schedulerMutex.unlock(); + + thiz->_requestQueueMutex.lock(); + thiz->_requestQueue.pushBack(thiz->_requestSentinel); + thiz->_requestQueueMutex.unlock(); + + thiz->_sleepCondition.notify_one(); + thiz->decreaseThreadCountAndMayDeleteThis(); + + CCLOG("HttpClient::destroyInstance() finished!"); +} + +void HttpClient::enableCookies(const char* cookieFile) +{ + _cookieFileMutex.lock(); + if (cookieFile) + { + _cookieFilename = std::string(cookieFile); + _cookieFilename = FileUtils::getInstance()->fullPathForFilename(_cookieFilename); + } + else + { + _cookieFilename = (FileUtils::getInstance()->getWritablePath() + "cookieFile.txt"); + } + _cookieFileMutex.unlock(); + + if (nullptr == _cookie) + { + _cookie = new(std::nothrow)HttpCookie; + } + _cookie->setCookieFileName(_cookieFilename); + _cookie->readFile(); } void HttpClient::setSSLVerification(const std::string& caFile) { - s_sslCaFilename = caFile; + std::lock_guard lock(_sslCaFileMutex); + _sslCaFilename = caFile; } HttpClient::HttpClient() : _timeoutForConnect(30) , _timeoutForRead(60) +, _isInited(false) +, _threadCount(0) +, _requestSentinel(new HttpRequest()) +, _cookie(nullptr) { + + CCLOG("In the constructor of HttpClient!"); + memset(_responseMessage, 0, sizeof(char) * RESPONSE_BUFFER_SIZE); + _scheduler = Director::getInstance()->getScheduler(); + increaseThreadCount(); } + HttpClient::~HttpClient() { - if (s_requestQueue != nullptr) { - { - std::lock_guard lock(s_requestQueueMutex); - s_requestQueue->pushBack(s_requestSentinel); - } - s_SleepCondition.notify_one(); - } - - s_HttpClient = nullptr; - - if(!s_cookieFilename.empty()) + CC_SAFE_DELETE(_requestSentinel); + if (!_cookieFilename.empty() && nullptr != _cookie) { - s_cookie->writeFile(); - //delete s_cookie; + _cookie->writeFile(); + CC_SAFE_DELETE(_cookie); } - //s_cookie = nullptr; + CCLOG("HttpClient destructor"); } //Lazy create semaphore & mutex & thread bool HttpClient::lazyInitThreadSemphore() { - if (s_requestQueue != nullptr) { + if (_isInited) + { return true; - } else { - - s_requestQueue = new (std::nothrow) Vector(); - s_responseQueue = new (std::nothrow) Vector(); - + } + else + { auto t = std::thread(CC_CALLBACK_0(HttpClient::networkThread, this)); t.detach(); + _isInited = true; } return true; @@ -458,14 +424,12 @@ void HttpClient::send(HttpRequest* request) request->retain(); - if (nullptr != s_requestQueue) { - s_requestQueueMutex.lock(); - s_requestQueue->pushBack(request); - s_requestQueueMutex.unlock(); - - // Notify thread start to work - s_SleepCondition.notify_one(); - } + _requestQueueMutex.lock(); + _requestQueue.pushBack(request); + _requestQueueMutex.unlock(); + + // Notify thread start to work + _sleepCondition.notify_one(); } void HttpClient::sendImmediate(HttpRequest* request) @@ -488,20 +452,14 @@ void HttpClient::dispatchResponseCallbacks() { // log("CCHttpClient::dispatchResponseCallbacks is running"); //occurs when cocos thread fires but the network thread has already quited - if (nullptr == s_responseQueue) { - return; - } HttpResponse* response = nullptr; - - s_responseQueueMutex.lock(); - - if (!s_responseQueue->empty()) + _responseQueueMutex.lock(); + if (!_responseQueue.empty()) { - response = s_responseQueue->at(0); - s_responseQueue->erase(0); + response = _responseQueue.at(0); + _responseQueue.erase(0); } - - s_responseQueueMutex.unlock(); + _responseQueueMutex.unlock(); if (response) { @@ -524,7 +482,122 @@ void HttpClient::dispatchResponseCallbacks() request->release(); } } + +// Process Response +void HttpClient::processResponse(HttpResponse* response, char* responseMessage) +{ + auto request = response->getHttpRequest(); + long responseCode = -1; + int retValue = 0; + NSString* requestType = nil; + + // Process the request -> get response packet + switch (request->getRequestType()) + { + case HttpRequest::Type::GET: // HTTP GET + requestType = @"GET"; + break; + + case HttpRequest::Type::POST: // HTTP POST + requestType = @"POST"; + break; + + case HttpRequest::Type::PUT: + requestType = @"PUT"; + break; + + case HttpRequest::Type::DELETE: + requestType = @"DELETE"; + break; + + default: + CCASSERT(true, "CCHttpClient: unknown request type, only GET and POSt are supported"); + break; + } + + retValue = processTask(this, + request, + requestType, + response->getResponseData(), + &responseCode, + response->getResponseHeader(), + responseMessage); + + // write data to HttpResponse + response->setResponseCode(responseCode); + + if (retValue != 0) + { + response->setSucceed(true); + } + else + { + response->setSucceed(false); + response->setErrorBuffer(responseMessage); + } +} + +void HttpClient::increaseThreadCount() +{ + _threadCountMutex.lock(); + ++_threadCount; + _threadCountMutex.unlock(); +} + +void HttpClient::decreaseThreadCountAndMayDeleteThis() +{ + bool needDeleteThis = false; + _threadCountMutex.lock(); + --_threadCount; + if (0 == _threadCount) + { + needDeleteThis = true; + } + + _threadCountMutex.unlock(); + if (needDeleteThis) + { + delete this; + } +} + +void HttpClient::setTimeoutForConnect(int value) +{ + std::lock_guard lock(_timeoutForConnectMutex); + _timeoutForConnect = value; +} + +int HttpClient::getTimeoutForConnect() +{ + std::lock_guard lock(_timeoutForConnectMutex); + return _timeoutForConnect; +} + +void HttpClient::setTimeoutForRead(int value) +{ + std::lock_guard lock(_timeoutForReadMutex); + _timeoutForRead = value; +} + +int HttpClient::getTimeoutForRead() +{ + std::lock_guard lock(_timeoutForReadMutex); + return _timeoutForRead; +} + +const std::string& HttpClient::getCookieFilename() +{ + std::lock_guard lock(_cookieFileMutex); + return _cookieFilename; +} + +const std::string& HttpClient::getSSLVerification() +{ + std::lock_guard lock(_sslCaFileMutex); + return _sslCaFilename; +} + } NS_CC_END diff --git a/cocos/network/HttpClient.cpp b/cocos/network/HttpClient.cpp index c53e3742d0..53f13907ae 100644 --- a/cocos/network/HttpClient.cpp +++ b/cocos/network/HttpClient.cpp @@ -25,48 +25,24 @@ ****************************************************************************/ #include "HttpClient.h" - -#include #include -#include - #include - #include - -#include "base/CCVector.h" #include "base/CCDirector.h" -#include "base/CCScheduler.h" - #include "platform/CCFileUtils.h" NS_CC_BEGIN namespace network { -static std::mutex s_requestQueueMutex; -static std::mutex s_responseQueueMutex; - -static std::condition_variable_any s_SleepCondition; - - #if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) typedef int int32_t; #endif -static Vector* s_requestQueue = nullptr; -static Vector* s_responseQueue = nullptr; - -static HttpClient *s_pHttpClient = nullptr; // pointer to singleton - -static char s_errorBuffer[CURL_ERROR_SIZE] = {0}; +static HttpClient* _httpClient = nullptr; // pointer to singleton typedef size_t (*write_callback)(void *ptr, size_t size, size_t nmemb, void *stream); -static std::string s_cookieFilename = ""; - -static std::string s_sslCaFilename = ""; - // Callback function used by libcurl for collect response data static size_t writeData(void *ptr, size_t size, size_t nmemb, void *stream) { @@ -94,19 +70,16 @@ static size_t writeHeaderData(void *ptr, size_t size, size_t nmemb, void *stream } -static int processGetTask(HttpRequest *request, write_callback callback, void *stream, long *errorCode, write_callback headerCallback, void *headerStream, char *errorBuffer); -static int processPostTask(HttpRequest *request, write_callback callback, void *stream, long *errorCode, write_callback headerCallback, void *headerStream, char *errorBuffer); -static int processPutTask(HttpRequest *request, write_callback callback, void *stream, long *errorCode, write_callback headerCallback, void *headerStream, char *errorBuffer); -static int processDeleteTask(HttpRequest *request, write_callback callback, void *stream, long *errorCode, write_callback headerCallback, void *headerStream, char *errorBuffer); +static int processGetTask(HttpClient* client, HttpRequest* request, write_callback callback, void *stream, long *errorCode, write_callback headerCallback, void *headerStream, char* errorBuffer); +static int processPostTask(HttpClient* client, HttpRequest* request, write_callback callback, void *stream, long *errorCode, write_callback headerCallback, void *headerStream, char* errorBuffer); +static int processPutTask(HttpClient* client, HttpRequest* request, write_callback callback, void *stream, long *errorCode, write_callback headerCallback, void *headerStream, char* errorBuffer); +static int processDeleteTask(HttpClient* client, HttpRequest* request, write_callback callback, void *stream, long *errorCode, write_callback headerCallback, void *headerStream, char* errorBuffer); // int processDownloadTask(HttpRequest *task, write_callback callback, void *stream, int32_t *errorCode); -static void processResponse(HttpResponse* response, char* errorBuffer); - -static HttpRequest *s_requestSentinel = new HttpRequest; // Worker thread void HttpClient::networkThread() -{ - auto scheduler = Director::getInstance()->getScheduler(); +{ + increaseThreadCount(); while (true) { @@ -114,15 +87,16 @@ void HttpClient::networkThread() // step 1: send http request if the requestQueue isn't empty { - std::lock_guard lock(s_requestQueueMutex); - while (s_requestQueue->empty()) { - s_SleepCondition.wait(s_requestQueueMutex); + std::lock_guard lock(_requestQueueMutex); + while (_requestQueue.empty()) + { + _sleepCondition.wait(_requestQueueMutex); } - request = s_requestQueue->at(0); - s_requestQueue->erase(0); + request = _requestQueue.at(0); + _requestQueue.erase(0); } - if (request == s_requestSentinel) { + if (request == _requestSentinel) { break; } @@ -131,62 +105,70 @@ void HttpClient::networkThread() // Create a HttpResponse object, the default setting is http access failed HttpResponse *response = new (std::nothrow) HttpResponse(request); - processResponse(response, s_errorBuffer); + processResponse(response, _responseMessage); // add response packet into queue - s_responseQueueMutex.lock(); - s_responseQueue->pushBack(response); - s_responseQueueMutex.unlock(); + _responseQueueMutex.lock(); + _responseQueue.pushBack(response); + _responseQueueMutex.unlock(); - if (nullptr != s_pHttpClient) { - scheduler->performFunctionInCocosThread(CC_CALLBACK_0(HttpClient::dispatchResponseCallbacks, this)); - } + _schedulerMutex.lock(); + if (nullptr != _scheduler) + { + _scheduler->performFunctionInCocosThread(CC_CALLBACK_0(HttpClient::dispatchResponseCallbacks, this)); + } + _schedulerMutex.unlock(); } // cleanup: if worker thread received quit signal, clean up un-completed request queue - s_requestQueueMutex.lock(); - s_requestQueue->clear(); - s_requestQueueMutex.unlock(); - - - if (s_requestQueue != nullptr) { - delete s_requestQueue; - s_requestQueue = nullptr; - delete s_responseQueue; - s_responseQueue = nullptr; - } + _requestQueueMutex.lock(); + _requestQueue.clear(); + _requestQueueMutex.unlock(); + _responseQueueMutex.lock(); + _responseQueue.clear(); + _responseQueueMutex.unlock(); + + decreaseThreadCountAndMayDeleteThis(); } // Worker thread void HttpClient::networkThreadAlone(HttpRequest* request, HttpResponse* response) { - char errorBuffer[CURL_ERROR_SIZE] = { 0 }; - processResponse(response, errorBuffer); + increaseThreadCount(); - auto scheduler = Director::getInstance()->getScheduler(); - scheduler->performFunctionInCocosThread([response, request]{ - const ccHttpRequestCallback& callback = request->getCallback(); - Ref* pTarget = request->getTarget(); - SEL_HttpResponse pSelector = request->getSelector(); + char responseMessage[RESPONSE_BUFFER_SIZE] = { 0 }; + processResponse(response, responseMessage); + + _schedulerMutex.lock(); + if (nullptr != _scheduler) + { + _scheduler->performFunctionInCocosThread([this, response, request]{ + const ccHttpRequestCallback& callback = request->getCallback(); + Ref* pTarget = request->getTarget(); + SEL_HttpResponse pSelector = request->getSelector(); - if (callback != nullptr) - { - callback(s_pHttpClient, response); - } - else if (pTarget && pSelector) - { - (pTarget->*pSelector)(s_pHttpClient, response); - } - response->release(); - // do not release in other thread - request->release(); - }); + if (callback != nullptr) + { + callback(this, response); + } + else if (pTarget && pSelector) + { + (pTarget->*pSelector)(this, response); + } + response->release(); + // do not release in other thread + request->release(); + }); + } + _schedulerMutex.unlock(); + + decreaseThreadCountAndMayDeleteThis(); } //Configure curl's timeout property -static bool configureCURL(CURL *handle, char *errorBuffer) +static bool configureCURL(HttpClient* client, CURL* handle, char* errorBuffer) { if (!handle) { return false; @@ -205,13 +187,15 @@ static bool configureCURL(CURL *handle, char *errorBuffer) if (code != CURLE_OK) { return false; } - if (s_sslCaFilename.empty()) { + + std::string sslCaFilename = client->getSSLVerification(); + if (sslCaFilename.empty()) { curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L); } else { curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 1L); curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 2L); - curl_easy_setopt(handle, CURLOPT_CAINFO, s_sslCaFilename.c_str()); + curl_easy_setopt(handle, CURLOPT_CAINFO, sslCaFilename.c_str()); } // FIXED #3224: The subthread of CCHttpClient interrupts main thread if timeout comes. @@ -255,11 +239,11 @@ public: * @param callback Response write callback * @param stream Response write stream */ - bool init(HttpRequest *request, write_callback callback, void *stream, write_callback headerCallback, void *headerStream, char *errorBuffer) + bool init(HttpClient* client, HttpRequest* request, write_callback callback, void* stream, write_callback headerCallback, void* headerStream, char* errorBuffer) { if (!_curl) return false; - if (!configureCURL(_curl, errorBuffer)) + if (!configureCURL(client, _curl, errorBuffer)) return false; /* get custom header data (if set) */ @@ -273,11 +257,12 @@ public: if (!setOption(CURLOPT_HTTPHEADER, _headers)) return false; } - if (!s_cookieFilename.empty()) { - if (!setOption(CURLOPT_COOKIEFILE, s_cookieFilename.c_str())) { + std::string cookieFilename = client->getCookieFilename(); + if (!cookieFilename.empty()) { + if (!setOption(CURLOPT_COOKIEFILE, cookieFilename.c_str())) { return false; } - if (!setOption(CURLOPT_COOKIEJAR, s_cookieFilename.c_str())) { + if (!setOption(CURLOPT_COOKIEJAR, cookieFilename.c_str())) { return false; } } @@ -307,20 +292,20 @@ public: }; //Process Get Request -static int processGetTask(HttpRequest *request, write_callback callback, void *stream, long *responseCode, write_callback headerCallback, void *headerStream, char *errorBuffer) +static int processGetTask(HttpClient* client, HttpRequest* request, write_callback callback, void* stream, long* responseCode, write_callback headerCallback, void* headerStream, char* errorBuffer) { CURLRaii curl; - bool ok = curl.init(request, callback, stream, headerCallback, headerStream, errorBuffer) + bool ok = curl.init(client, request, callback, stream, headerCallback, headerStream, errorBuffer) && curl.setOption(CURLOPT_FOLLOWLOCATION, true) && curl.perform(responseCode); return ok ? 0 : 1; } //Process POST Request -static int processPostTask(HttpRequest *request, write_callback callback, void *stream, long *responseCode, write_callback headerCallback, void *headerStream, char *errorBuffer) +static int processPostTask(HttpClient* client, HttpRequest* request, write_callback callback, void* stream, long* responseCode, write_callback headerCallback, void* headerStream, char* errorBuffer) { CURLRaii curl; - bool ok = curl.init(request, callback, stream, headerCallback, headerStream, errorBuffer) + bool ok = curl.init(client, request, callback, stream, headerCallback, headerStream, errorBuffer) && curl.setOption(CURLOPT_POST, 1) && curl.setOption(CURLOPT_POSTFIELDS, request->getRequestData()) && curl.setOption(CURLOPT_POSTFIELDSIZE, request->getRequestDataSize()) @@ -329,10 +314,10 @@ static int processPostTask(HttpRequest *request, write_callback callback, void * } //Process PUT Request -static int processPutTask(HttpRequest *request, write_callback callback, void *stream, long *responseCode, write_callback headerCallback, void *headerStream, char *errorBuffer) +static int processPutTask(HttpClient* client, HttpRequest* request, write_callback callback, void* stream, long* responseCode, write_callback headerCallback, void* headerStream, char* errorBuffer) { CURLRaii curl; - bool ok = curl.init(request, callback, stream, headerCallback, headerStream, errorBuffer) + bool ok = curl.init(client, request, callback, stream, headerCallback, headerStream, errorBuffer) && curl.setOption(CURLOPT_CUSTOMREQUEST, "PUT") && curl.setOption(CURLOPT_POSTFIELDS, request->getRequestData()) && curl.setOption(CURLOPT_POSTFIELDSIZE, request->getRequestDataSize()) @@ -341,146 +326,105 @@ static int processPutTask(HttpRequest *request, write_callback callback, void *s } //Process DELETE Request -static int processDeleteTask(HttpRequest *request, write_callback callback, void *stream, long *responseCode, write_callback headerCallback, void *headerStream, char *errorBuffer) +static int processDeleteTask(HttpClient* client, HttpRequest* request, write_callback callback, void* stream, long* responseCode, write_callback headerCallback, void* headerStream, char* errorBuffer) { CURLRaii curl; - bool ok = curl.init(request, callback, stream, headerCallback, headerStream, errorBuffer) + bool ok = curl.init(client, request, callback, stream, headerCallback, headerStream, errorBuffer) && curl.setOption(CURLOPT_CUSTOMREQUEST, "DELETE") && curl.setOption(CURLOPT_FOLLOWLOCATION, true) && curl.perform(responseCode); return ok ? 0 : 1; } - -// Process Response -static void processResponse(HttpResponse* response, char* errorBuffer) -{ - auto request = response->getHttpRequest(); - long responseCode = -1; - int retValue = 0; - - // Process the request -> get response packet - switch (request->getRequestType()) - { - case HttpRequest::Type::GET: // HTTP GET - retValue = processGetTask(request, - writeData, - response->getResponseData(), - &responseCode, - writeHeaderData, - response->getResponseHeader(), - errorBuffer); - break; - - case HttpRequest::Type::POST: // HTTP POST - retValue = processPostTask(request, - writeData, - response->getResponseData(), - &responseCode, - writeHeaderData, - response->getResponseHeader(), - errorBuffer); - break; - - case HttpRequest::Type::PUT: - retValue = processPutTask(request, - writeData, - response->getResponseData(), - &responseCode, - writeHeaderData, - response->getResponseHeader(), - errorBuffer); - break; - - case HttpRequest::Type::DELETE: - retValue = processDeleteTask(request, - writeData, - response->getResponseData(), - &responseCode, - writeHeaderData, - response->getResponseHeader(), - errorBuffer); - break; - - default: - CCASSERT(true, "CCHttpClient: unknown request type, only GET and POSt are supported"); - break; - } - - // write data to HttpResponse - response->setResponseCode(responseCode); - - if (retValue != 0) - { - response->setSucceed(false); - response->setErrorBuffer(errorBuffer); - } - else - { - response->setSucceed(true); - } -} - // HttpClient implementation HttpClient* HttpClient::getInstance() { - if (s_pHttpClient == nullptr) { - s_pHttpClient = new (std::nothrow) HttpClient(); + if (_httpClient == nullptr) + { + _httpClient = new (std::nothrow) HttpClient(); } - return s_pHttpClient; + return _httpClient; } void HttpClient::destroyInstance() { - CC_SAFE_DELETE(s_pHttpClient); + if (nullptr == _httpClient) + { + CCLOG("HttpClient singleton is nullptr"); + return; + } + + CCLOG("HttpClient::destroyInstance begin"); + auto thiz = _httpClient; + _httpClient = nullptr; + + thiz->_scheduler->unscheduleAllForTarget(thiz); + thiz->_schedulerMutex.lock(); + thiz->_scheduler = nullptr; + thiz->_schedulerMutex.unlock(); + + thiz->_requestQueueMutex.lock(); + thiz->_requestQueue.pushBack(thiz->_requestSentinel); + thiz->_requestQueueMutex.unlock(); + + thiz->_sleepCondition.notify_one(); + thiz->decreaseThreadCountAndMayDeleteThis(); + + CCLOG("HttpClient::destroyInstance() finished!"); } -void HttpClient::enableCookies(const char* cookieFile) { - if (cookieFile) { - s_cookieFilename = std::string(cookieFile); +void HttpClient::enableCookies(const char* cookieFile) +{ + std::lock_guard lock(_cookieFileMutex); + if (cookieFile) + { + _cookieFilename = std::string(cookieFile); } - else { - s_cookieFilename = (FileUtils::getInstance()->getWritablePath() + "cookieFile.txt"); + else + { + _cookieFilename = (FileUtils::getInstance()->getWritablePath() + "cookieFile.txt"); } } void HttpClient::setSSLVerification(const std::string& caFile) { - s_sslCaFilename = caFile; + std::lock_guard lock(_sslCaFileMutex); + _sslCaFilename = caFile; } HttpClient::HttpClient() : _timeoutForConnect(30) , _timeoutForRead(60) +, _isInited(false) +, _threadCount(0) +, _requestSentinel(new HttpRequest()) +, _cookie(nullptr) { + CCLOG("In the constructor of HttpClient!"); + memset(_responseMessage, 0, RESPONSE_BUFFER_SIZE * sizeof(char)); + _scheduler = Director::getInstance()->getScheduler(); + increaseThreadCount(); } HttpClient::~HttpClient() { - if (s_requestQueue != nullptr) { - { - std::lock_guard lock(s_requestQueueMutex); - s_requestQueue->pushBack(s_requestSentinel); - } - s_SleepCondition.notify_one(); - } - - s_pHttpClient = nullptr; + CC_SAFE_DELETE(_requestSentinel); + CCLOG("HttpClient destructor"); } //Lazy create semaphore & mutex & thread bool HttpClient::lazyInitThreadSemphore() { - if (s_requestQueue != nullptr) { + if (_isInited) + { return true; - } else { - - s_requestQueue = new (std::nothrow) Vector(); - s_responseQueue = new (std::nothrow) Vector(); - + } + else + { auto t = std::thread(CC_CALLBACK_0(HttpClient::networkThread, this)); t.detach(); + _isInited = true; } return true; @@ -500,15 +444,13 @@ void HttpClient::send(HttpRequest* request) } request->retain(); - - if (nullptr != s_requestQueue) { - s_requestQueueMutex.lock(); - s_requestQueue->pushBack(request); - s_requestQueueMutex.unlock(); - - // Notify thread start to work - s_SleepCondition.notify_one(); - } + + _requestQueueMutex.lock(); + _requestQueue.pushBack(request); + _requestQueueMutex.unlock(); + + // Notify thread start to work + _sleepCondition.notify_one(); } void HttpClient::sendImmediate(HttpRequest* request) @@ -531,20 +473,15 @@ void HttpClient::dispatchResponseCallbacks() { // log("CCHttpClient::dispatchResponseCallbacks is running"); //occurs when cocos thread fires but the network thread has already quited - if (nullptr == s_responseQueue) { - return; - } HttpResponse* response = nullptr; - - s_responseQueueMutex.lock(); - if (!s_responseQueue->empty()) + _responseQueueMutex.lock(); + if (!_responseQueue.empty()) { - response = s_responseQueue->at(0); - s_responseQueue->erase(0); + response = _responseQueue.at(0); + _responseQueue.erase(0); } - - s_responseQueueMutex.unlock(); + _responseQueueMutex.unlock(); if (response) { @@ -568,6 +505,134 @@ void HttpClient::dispatchResponseCallbacks() } } +// Process Response +void HttpClient::processResponse(HttpResponse* response, char* responseMessage) +{ + auto request = response->getHttpRequest(); + long responseCode = -1; + int retValue = 0; + + // Process the request -> get response packet + switch (request->getRequestType()) + { + case HttpRequest::Type::GET: // HTTP GET + retValue = processGetTask(this, request, + writeData, + response->getResponseData(), + &responseCode, + writeHeaderData, + response->getResponseHeader(), + responseMessage); + break; + + case HttpRequest::Type::POST: // HTTP POST + retValue = processPostTask(this, request, + writeData, + response->getResponseData(), + &responseCode, + writeHeaderData, + response->getResponseHeader(), + responseMessage); + break; + + case HttpRequest::Type::PUT: + retValue = processPutTask(this, request, + writeData, + response->getResponseData(), + &responseCode, + writeHeaderData, + response->getResponseHeader(), + responseMessage); + break; + + case HttpRequest::Type::DELETE: + retValue = processDeleteTask(this, request, + writeData, + response->getResponseData(), + &responseCode, + writeHeaderData, + response->getResponseHeader(), + responseMessage); + break; + + default: + CCASSERT(true, "CCHttpClient: unknown request type, only GET and POSt are supported"); + break; + } + + // write data to HttpResponse + response->setResponseCode(responseCode); + if (retValue != 0) + { + response->setSucceed(false); + response->setErrorBuffer(responseMessage); + } + else + { + response->setSucceed(true); + } +} + +void HttpClient::increaseThreadCount() +{ + _threadCountMutex.lock(); + ++_threadCount; + _threadCountMutex.unlock(); +} + +void HttpClient::decreaseThreadCountAndMayDeleteThis() +{ + bool needDeleteThis = false; + _threadCountMutex.lock(); + --_threadCount; + if (0 == _threadCount) + { + needDeleteThis = true; + } + + _threadCountMutex.unlock(); + if (needDeleteThis) + { + delete this; + } +} + +void HttpClient::setTimeoutForConnect(int value) +{ + std::lock_guard lock(_timeoutForConnectMutex); + _timeoutForConnect = value; +} + +int HttpClient::getTimeoutForConnect() +{ + std::lock_guard lock(_timeoutForConnectMutex); + return _timeoutForConnect; +} + +void HttpClient::setTimeoutForRead(int value) +{ + std::lock_guard lock(_timeoutForReadMutex); + _timeoutForRead = value; +} + +int HttpClient::getTimeoutForRead() +{ + std::lock_guard lock(_timeoutForReadMutex); + return _timeoutForRead; +} + +const std::string& HttpClient::getCookieFilename() +{ + std::lock_guard lock(_cookieFileMutex); + return _cookieFilename; +} + +const std::string& HttpClient::getSSLVerification() +{ + std::lock_guard lock(_sslCaFileMutex); + return _sslCaFilename; +} + } NS_CC_END diff --git a/cocos/network/HttpClient.h b/cocos/network/HttpClient.h index 9cc2119a6c..4424594b1a 100644 --- a/cocos/network/HttpClient.h +++ b/cocos/network/HttpClient.h @@ -27,8 +27,13 @@ #ifndef __CCHTTPCLIENT_H__ #define __CCHTTPCLIENT_H__ +#include +#include +#include "base/CCVector.h" +#include "base/CCScheduler.h" #include "network/HttpRequest.h" #include "network/HttpResponse.h" +#include "network/HttpCookie.h" /** * @addtogroup core @@ -38,6 +43,8 @@ NS_CC_BEGIN namespace network { + + /** Singleton that handles asynchrounous http requests. * @@ -48,6 +55,11 @@ namespace network { class CC_DLL HttpClient { public: + /** + * The buffer size of _responseMessage + */ + static const int RESPONSE_BUFFER_SIZE = 256; + /** * Get instance of HttpClient. * @@ -67,12 +79,26 @@ public: */ void enableCookies(const char* cookieFile); + /** + * Get the cookie filename + * + * @return the cookie filename + */ + const std::string& getCookieFilename(); + /** * Set root certificate path for SSL verification. * * @param caFile a full path of root certificate.if it is empty, SSL verification is disabled. */ void setSSLVerification(const std::string& caFile); + + /** + * Get ths ssl CA filename + * + * @return the ssl CA filename + */ + const std::string& getSSLVerification(); /** * Add a get request to task queue @@ -89,38 +115,40 @@ public: please make sure request->_requestData is clear before calling "sendImmediate" here. */ void sendImmediate(HttpRequest* request); - /** * Set the timeout value for connecting. * * @param value the timeout value for connecting. */ - inline void setTimeoutForConnect(int value) {_timeoutForConnect = value;}; + void setTimeoutForConnect(int value); /** * Get the timeout value for connecting. * * @return int the timeout value for connecting. */ - inline int getTimeoutForConnect() {return _timeoutForConnect;} - + int getTimeoutForConnect(); /** * Set the timeout value for reading. * * @param value the timeout value for reading. */ - inline void setTimeoutForRead(int value) {_timeoutForRead = value;}; - + void setTimeoutForRead(int value); /** * Get the timeout value for reading. * * @return int the timeout value for reading. */ - inline int getTimeoutForRead() {return _timeoutForRead;}; - + int getTimeoutForRead(); + + HttpCookie* getCookie() const {return _cookie; } + + std::mutex& getCookieFileMutex() {return _cookieFileMutex;} + + std::mutex& getSSLCaFileMutex() {return _sslCaFileMutex;} private: HttpClient(); virtual ~HttpClient(); @@ -136,9 +164,44 @@ private: /** Poll function called from main thread to dispatch callbacks when http requests finished **/ void dispatchResponseCallbacks(); + void processResponse(HttpResponse* response, char* responseMessage); + void increaseThreadCount(); + void decreaseThreadCountAndMayDeleteThis(); + private: + bool _isInited; + int _timeoutForConnect; + std::mutex _timeoutForConnectMutex; + int _timeoutForRead; + std::mutex _timeoutForReadMutex; + + int _threadCount; + std::mutex _threadCountMutex; + + Scheduler* _scheduler; + std::mutex _schedulerMutex; + + Vector _requestQueue; + std::mutex _requestQueueMutex; + + Vector _responseQueue; + std::mutex _responseQueueMutex; + + std::string _cookieFilename; + std::mutex _cookieFileMutex; + + std::string _sslCaFilename; + std::mutex _sslCaFileMutex; + + HttpCookie* _cookie; + + std::condition_variable_any _sleepCondition; + + char _responseMessage[RESPONSE_BUFFER_SIZE]; + + HttpRequest* _requestSentinel; }; } @@ -148,4 +211,4 @@ NS_CC_END // end group /// @} -#endif //__CCHTTPREQUEST_H__ +#endif //__CCHTTPCLIENT_H__