diff --git a/cocos/network/Android.mk b/cocos/network/Android.mk index 0e5fa5254f..7d394972c9 100644 --- a/cocos/network/Android.mk +++ b/cocos/network/Android.mk @@ -5,7 +5,7 @@ LOCAL_MODULE := cocos_network_static LOCAL_MODULE_FILENAME := libnetwork -LOCAL_SRC_FILES := HttpClient.cpp \ +LOCAL_SRC_FILES := HttpClientAndroid.cpp \ SocketIO.cpp \ WebSocket.cpp diff --git a/cocos/network/HttpClientAndroid.cpp b/cocos/network/HttpClientAndroid.cpp new file mode 100644 index 0000000000..1c38be2b89 --- /dev/null +++ b/cocos/network/HttpClientAndroid.cpp @@ -0,0 +1,972 @@ +/**************************************************************************** + Copyright (c) 2012 greathqy + Copyright (c) 2012 cocos2d-x.org + Copyright (c) 2013-2014 Chukong Technologies Inc. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +#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" + +NS_CC_BEGIN + +namespace network { + +typedef std::vector HttpRequestHeaders; +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_pHttpClient = 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; + + +struct CookiesInfo +{ + std::string domain; + bool tailmatch; + std::string path; + bool secure; + std::string key; + std::string value; + std::string expires; +}; + +//static size_t writeData(void *ptr, size_t size, size_t nmemb, void *stream) +static size_t writeData(void* buffer, size_t sizes, HttpResponse* response) +{ + std::vector * recvBuffer = (std::vector*)response->getResponseData(); + recvBuffer->clear(); + recvBuffer->insert(recvBuffer->end(), (char*)buffer, ((char*)buffer) + sizes); + return sizes; +} + +//static size_t writeHeaderData(void *ptr, size_t size, size_t nmemb, void *stream) +size_t writeHeaderData(void* buffer, size_t sizes,HttpResponse* response) +{ + std::vector * recvBuffer = (std::vector*) response->getResponseHeader(); + recvBuffer->clear(); + recvBuffer->insert(recvBuffer->end(), (char*)buffer, (char*)buffer + sizes); + return sizes; +} + +static void processResponse(HttpResponse* response, std::string& responseMessage); + +class HttpURLConnection +{ +public: + HttpURLConnection():_httpURLConnection(nullptr), + _requestmethod(""), + _responseCookies(""), + _cookieFileName(""), + _contentLength(0) + { + + } + + ~HttpURLConnection() + { + + } + + void setRequestMethod(const char* method) + { + _requestmethod = method; + + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + "org/cocos2dx/lib/Cocos2dxHttpURLConnection", + "setRequestMethod", + "(Ljava/net/HttpURLConnection;Ljava/lang/String;)V")) + { + jstring jstr = methodInfo.env->NewStringUTF(_requestmethod.c_str()); + methodInfo.env->CallStaticVoidMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection, jstr); + methodInfo.env->DeleteLocalRef(jstr); + methodInfo.env->DeleteLocalRef(methodInfo.classID); + } + } + + bool init(HttpRequest* request) + { + createHttpURLConnection(request->getUrl()); + if(!configure()) + return false; + /* get custom header data (if set) */ + HttpRequestHeaders headers=request->getHeaders(); + if(!headers.empty()) + { + /* append custom headers one by one */ + for (HttpRequestHeadersIter it = headers.begin(); it != headers.end(); ++it) + { + std::string val = *it; + + int len = val.length(); + int pos = val.find(':'); + if (-1 == pos || pos >= len) + { + continue; + } + std::string str1 = val.substr(0, pos); + std::string str2 = val.substr(pos + 1, len - pos - 1); + addRequestHeader(str1.c_str(), str2.c_str()); + } + } + + addCookiesForRequestHeader(); + + return true; + } + + int connect() + { + int suc = 0; + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + "org/cocos2dx/lib/Cocos2dxHttpURLConnection", + "connect", + "(Ljava/net/HttpURLConnection;)I")) + { + suc = methodInfo.env->CallStaticIntMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection); + methodInfo.env->DeleteLocalRef(methodInfo.classID); + } + + return suc; + } + + void disconnect() + { + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + "org/cocos2dx/lib/Cocos2dxHttpURLConnection", + "disconnect", + "(Ljava/net/HttpURLConnection;)V")) + { + methodInfo.env->CallStaticVoidMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection); + methodInfo.env->DeleteLocalRef(methodInfo.classID); + } + } + + int getResponseCode() + { + int responseCode = 0; + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + "org/cocos2dx/lib/Cocos2dxHttpURLConnection", + "getResponseCode", + "(Ljava/net/HttpURLConnection;)I")) + { + responseCode = methodInfo.env->CallStaticIntMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection); + methodInfo.env->DeleteLocalRef(methodInfo.classID); + } + + return responseCode; + } + + char* getResponseMessage() + { + char* message = nullptr; + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + "org/cocos2dx/lib/Cocos2dxHttpURLConnection", + "getResponseMessage", + "(Ljava/net/HttpURLConnection;)Ljava/lang/String;")) + { + jobject jObj = methodInfo.env->CallStaticObjectMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection); + message = getBufferFromJString((jstring)jObj, methodInfo.env); + if (nullptr != jObj) + { + methodInfo.env->DeleteLocalRef(jObj); + } + methodInfo.env->DeleteLocalRef(methodInfo.classID); + } + + return message; + } + + void sendRequest(HttpRequest* request) + { + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + "org/cocos2dx/lib/Cocos2dxHttpURLConnection", + "sendRequest", + "(Ljava/net/HttpURLConnection;[B)V")) + { + + jbyteArray bytearray; + ssize_t dataSize = request->getRequestDataSize(); + bytearray = methodInfo.env->NewByteArray(dataSize); + methodInfo.env->SetByteArrayRegion(bytearray, 0, dataSize, (const jbyte*)request->getRequestData()); + methodInfo.env->CallStaticVoidMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection, bytearray); + methodInfo.env->DeleteLocalRef(bytearray); + methodInfo.env->DeleteLocalRef(methodInfo.classID); + } + } + + 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) + { + CCLOG("can't create or open response cookie files"); + return 0; + } + + fwrite(responseCookies, sizeof(char), count, fp); + + fclose(fp); + + return count; + } + + char* getResponseHeaders() + { + char* headers = nullptr; + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + "org/cocos2dx/lib/Cocos2dxHttpURLConnection", + "getResponseHeaders", + "(Ljava/net/HttpURLConnection;)Ljava/lang/String;")) + { + jobject jObj = methodInfo.env->CallStaticObjectMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection); + headers = getBufferFromJString((jstring)jObj, methodInfo.env); + if (nullptr != jObj) { + methodInfo.env->DeleteLocalRef(jObj); + } + methodInfo.env->DeleteLocalRef(methodInfo.classID); + } + + return headers; + + } + + char* getResponseContent(HttpResponse* response) + { + if (nullptr == response) + return nullptr; + + char* content = nullptr; + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + "org/cocos2dx/lib/Cocos2dxHttpURLConnection", + "getResponseContent", + "(Ljava/net/HttpURLConnection;)[B")) + { + jobject jObj = methodInfo.env->CallStaticObjectMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection); + + _contentLength = getCStrFromJByteArray((jbyteArray)jObj, methodInfo.env, &content); + if (nullptr != jObj) + { + methodInfo.env->DeleteLocalRef(jObj); + } + methodInfo.env->DeleteLocalRef(methodInfo.classID); + } + + return content; + } + + char* getResponseHeaderByKey(const char* key) + { + char* value = nullptr; + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + "org/cocos2dx/lib/Cocos2dxHttpURLConnection", + "getResponseHeaderByKey", + "(Ljava/net/HttpURLConnection;Ljava/lang/String;)Ljava/lang/String;")) + { + jstring jstrKey = methodInfo.env->NewStringUTF(key); + jobject jObj = methodInfo.env->CallStaticObjectMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection, jstrKey); + value = getBufferFromJString((jstring)jObj, methodInfo.env); + methodInfo.env->DeleteLocalRef(jstrKey); + if (nullptr != jObj) { + methodInfo.env->DeleteLocalRef(jObj); + } + methodInfo.env->DeleteLocalRef(methodInfo.classID); + } + + return value; + } + + int getResponseHeaderByKeyInt(const char* key) + { + int contentLength = 0; + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + "org/cocos2dx/lib/Cocos2dxHttpURLConnection", + "getResponseHeaderByKeyInt", + "(Ljava/net/HttpURLConnection;Ljava/lang/String;)I")) + { + jstring jstrKey = methodInfo.env->NewStringUTF(key); + contentLength = methodInfo.env->CallStaticIntMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection, jstrKey); + methodInfo.env->DeleteLocalRef(jstrKey); + methodInfo.env->DeleteLocalRef(methodInfo.classID); + } + + return contentLength; + } + + char* getResponseHeaderByIdx(int idx) + { + char* header = nullptr; + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + "org/cocos2dx/lib/Cocos2dxHttpURLConnection", + "getResponseHeaderByIdx", + "(Ljava/net/HttpURLConnection;I)Ljava/lang/String;")) + { + jobject jObj = methodInfo.env->CallStaticObjectMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection, idx); + header = getBufferFromJString((jstring)jObj, methodInfo.env); + if (nullptr != jObj) { + methodInfo.env->DeleteLocalRef(jObj); + } + methodInfo.env->DeleteLocalRef(methodInfo.classID); + } + + return header; + } + + const std::string getCookieFileName() + { + return _cookieFileName; + } + + void setCookieFileName(std::string& filename) + { + _cookieFileName = filename; + } + + int getContentLength() + { + return _contentLength; + } + +private: + void createHttpURLConnection(std::string url) + { + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + "org/cocos2dx/lib/Cocos2dxHttpURLConnection", + "createHttpURLConnection", + "(Ljava/lang/String;)Ljava/net/HttpURLConnection;")) + { + _url = url; + jstring jurl = methodInfo.env->NewStringUTF(url.c_str()); + jobject jObj = methodInfo.env->CallStaticObjectMethod(methodInfo.classID, methodInfo.methodID, jurl); + _httpURLConnection = methodInfo.env->NewGlobalRef(jObj); + methodInfo.env->DeleteLocalRef(jurl); + methodInfo.env->DeleteLocalRef(methodInfo.classID); + } + } + + void addRequestHeader(const char* key, const char* value) + { + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + "org/cocos2dx/lib/Cocos2dxHttpURLConnection", + "addRequestHeader", + "(Ljava/net/HttpURLConnection;Ljava/lang/String;Ljava/lang/String;)V")) + { + jstring jstrKey = methodInfo.env->NewStringUTF(key); + jstring jstrVal = methodInfo.env->NewStringUTF(value); + methodInfo.env->CallStaticVoidMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection, jstrKey, jstrVal); + methodInfo.env->DeleteLocalRef(jstrKey); + methodInfo.env->DeleteLocalRef(jstrVal); + methodInfo.env->DeleteLocalRef(methodInfo.classID); + } + } + + void addCookiesForRequestHeader() + { + if(s_cookieFilename.empty()) + return; + + _cookieFileName = FileUtils::getInstance()->fullPathForFilename(s_cookieFilename.c_str()); + + std::lock_guard lock(s_cookieFileMutex); + + std::string cookiesInfo = FileUtils::getInstance()->getStringFromFile(_cookieFileName.c_str()); + + if (cookiesInfo.empty()) + return; + + HttpCookies cookiesVec; + cookiesVec.clear(); + + std::stringstream stream(cookiesInfo); + std::string item; + while (std::getline(stream, item, '\n')) + { + cookiesVec.push_back(item); + } + + if (cookiesVec.empty()) + return; + + HttpCookiesIter iter = cookiesVec.begin(); + + std::vector cookiesInfoVec; + cookiesInfoVec.clear(); + + for (; iter != cookiesVec.end(); iter++) + { + std::string cookies = *iter; + if (cookies.find("#HttpOnly_") != std::string::npos) + { + cookies = cookies.substr(10); + } + + if(cookies.at(0) == '#') + continue; + + CookiesInfo co; + std::stringstream streamInfo(cookies); + std::string item; + std::vector elems; + + while (std::getline(streamInfo, item, '\t')) + { + elems.push_back(item); + } + + co.domain = elems[0]; + if (co.domain.at(0) == '.') + { + co.domain = co.domain.substr(1); + } + co.tailmatch = strcmp("TRUE", elems.at(1).c_str())?true: false; + co.path = elems.at(2); + co.secure = strcmp("TRUE", elems.at(3).c_str())?true: false; + co.expires = elems.at(4); + co.key = elems.at(5); + co.value = elems.at(6); + cookiesInfoVec.push_back(co); + } + + std::vector::iterator cookiesIter = cookiesInfoVec.begin(); + std::string sendCookiesInfo = ""; + int cookiesCount = 0; + for (; cookiesIter != cookiesInfoVec.end(); cookiesIter++) + { + if (_url.find(cookiesIter->domain) != std::string::npos) + { + std::string keyValue = cookiesIter->key; + keyValue.append("="); + keyValue.append(cookiesIter->value); + if (cookiesCount != 0) + sendCookiesInfo.append(";"); + + sendCookiesInfo.append(keyValue); + } + cookiesCount++; + } + + //set Cookie + addRequestHeader("Cookie",sendCookiesInfo.c_str()); + } + + void setReadAndConnectTimeout(int readMiliseconds, int connectMiliseconds) + { + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + "org/cocos2dx/lib/Cocos2dxHttpURLConnection", + "setReadAndConnectTimeout", + "(Ljava/net/HttpURLConnection;II)V")) + { + methodInfo.env->CallStaticVoidMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection, readMiliseconds, connectMiliseconds); + methodInfo.env->DeleteLocalRef(methodInfo.classID); + } + } + + void setVerifySSL() + { + if(s_sslCaFilename.empty()) + return; + + std::string fullpath = FileUtils::getInstance()->fullPathForFilename(s_sslCaFilename.c_str()); + + JniMethodInfo methodInfo; + if (JniHelper::getStaticMethodInfo(methodInfo, + "org/cocos2dx/lib/Cocos2dxHttpURLConnection", + "setVerifySSL", + "(Ljava/net/HttpURLConnection;Ljava/lang/String;)V")) + { + jstring jstrfullpath = methodInfo.env->NewStringUTF(fullpath.c_str()); + methodInfo.env->CallStaticVoidMethod( + methodInfo.classID, methodInfo.methodID, _httpURLConnection, jstrfullpath); + methodInfo.env->DeleteLocalRef(jstrfullpath); + methodInfo.env->DeleteLocalRef(methodInfo.classID); + } + } + + bool configure() + { + if(nullptr == _httpURLConnection) + return false; + + HttpClient* instnace = HttpClient::getInstance(); + if(nullptr == instnace) + return false; + + setReadAndConnectTimeout(instnace->getTimeoutForRead(), instnace->getTimeoutForConnect()); + + setVerifySSL(); + + return true; + } + + char* getBufferFromJString(jstring jstr, JNIEnv* env) + { + if (nullptr == jstr) + { + return nullptr; + } + + const char* str = nullptr; + char* ret = nullptr; + str = env->GetStringUTFChars(jstr, nullptr); + if (nullptr != str) + { + ret = strdup(str); + } + + env->ReleaseStringUTFChars(jstr, str); + + return ret; + } + + int getCStrFromJByteArray(jbyteArray jba, JNIEnv* env, char** ppData) + { + if (nullptr == jba) + { + *ppData = nullptr; + return 0; + } + + char* str = nullptr; + + int len = env->GetArrayLength(jba); + str = (char*)malloc(sizeof(char)*len); + env->GetByteArrayRegion(jba, 0, len, (jbyte*)str); + + *ppData = str; + return len; + } + + const std::string getCookieString() + { + return _responseCookies; + } +private: + jobject _httpURLConnection; + std::string _requestmethod; + std::string _responseCookies; + std::string _cookieFileName; + std::string _url; + int _contentLength; +}; + +// Process Response +static void processResponse(HttpResponse* response, std::string& responseMessage) +{ + auto request = response->getHttpRequest(); + HttpRequest::Type requestType = request->getRequestType(); + + if (HttpRequest::Type::GET != requestType && + HttpRequest::Type::POST != requestType && + HttpRequest::Type::PUT != requestType && + HttpRequest::Type::DELETE != requestType) + { + CCASSERT(true, "CCHttpClient: unkown request type, only GET、POST、PUT、DELETE are supported"); + return; + } + + long responseCode = -1; + int retValue = 0; + + HttpURLConnection urlConnection; + if(!urlConnection.init(request)) + { + response->setSucceed(false); + response->setErrorBuffer("HttpURLConnetcion init failed"); + return; + } + + switch (requestType) + { + case HttpRequest::Type::GET: + urlConnection.setRequestMethod("GET"); + break; + + case HttpRequest::Type::POST: + urlConnection.setRequestMethod("POST"); + break; + + case HttpRequest::Type::PUT: + urlConnection.setRequestMethod("PUT"); + break; + + case HttpRequest::Type::DELETE: + urlConnection.setRequestMethod("DELETE"); + break; + default: + break; + } + + int suc = urlConnection.connect(); + if (0 != suc) + { + response->setSucceed(false); + response->setErrorBuffer("connect failed"); + return; + } + + if (HttpRequest::Type::POST == requestType || + HttpRequest::Type::PUT == requestType) + { + urlConnection.sendRequest(request); + } + + responseCode = urlConnection.getResponseCode(); + + char* headers = urlConnection.getResponseHeaders(); + if (nullptr != headers) + { + writeHeaderData(headers, strlen(headers), response); + } + free(headers); + + //get and save cookies + char* cookiesInfo = urlConnection.getResponseHeaderByKey("set-cookie"); + if (nullptr != cookiesInfo) + { + urlConnection.saveResponseCookies(cookiesInfo, strlen(cookiesInfo)); + } + free(cookiesInfo); + + //content len + int contentLength = urlConnection.getResponseHeaderByKeyInt("Content-Length"); + char* contentInfo = urlConnection.getResponseContent(response); + if (nullptr != contentInfo) { +// response->setResponseDataString(contentInfo, contentLength); + std::vector * recvBuffer = (std::vector*)response->getResponseData(); + recvBuffer->clear(); + recvBuffer->insert(recvBuffer->begin(), (char*)contentInfo, ((char*)contentInfo) + urlConnection.getContentLength()); + } + free(contentInfo); + + responseMessage = urlConnection.getResponseMessage(); + urlConnection.disconnect(); + + // write data to HttpResponse + response->setResponseCode(responseCode); + + if (responseCode == -1) + { + response->setSucceed(false); + response->setErrorBuffer(responseMessage.c_str()); + } + else + { + response->setSucceed(true); + } +} + +// Worker thread +void HttpClient::networkThread() +{ + auto scheduler = Director::getInstance()->getScheduler(); + + 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); + } + request = s_requestQueue->at(0); + s_requestQueue->erase(0); + } + + if (request == s_requestSentinel) { + break; + } + + // Create a HttpResponse object, the default setting is http access failed + HttpResponse *response = new (std::nothrow) HttpResponse(request); + processResponse(response, s_responseMessage); + + // add response packet into queue + s_responseQueueMutex.lock(); + s_responseQueue->pushBack(response); + s_responseQueueMutex.unlock(); + + if (nullptr != s_pHttpClient) { + scheduler->performFunctionInCocosThread(CC_CALLBACK_0(HttpClient::dispatchResponseCallbacks, this)); + } + } + + // 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; + } + +} + +// Worker thread +void HttpClient::networkThreadAlone(HttpRequest* request) +{ + // Create a HttpResponse object, the default setting is http access failed + HttpResponse *response = new (std::nothrow) HttpResponse(request); + std::string responseMessage = ""; + 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(); + + 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(); + }); +} + +// HttpClient implementation +HttpClient* HttpClient::getInstance() +{ + if (s_pHttpClient == nullptr) { + s_pHttpClient = new (std::nothrow) HttpClient(); + } + + return s_pHttpClient; +} + +void HttpClient::destroyInstance() +{ + CC_SAFE_DELETE(s_pHttpClient); +} + +void HttpClient::enableCookies(const char* cookieFile) { + if (cookieFile) { + s_cookieFilename = std::string(cookieFile); + } + else { + s_cookieFilename = (FileUtils::getInstance()->getWritablePath() + "cookieFile.txt"); + } +} + +void HttpClient::setSSLVerification(const std::string& caFile) +{ + s_sslCaFilename = caFile; +} + +HttpClient::HttpClient() +: _timeoutForConnect(30*1000) +, _timeoutForRead(60*1000) +{ +} + +HttpClient::~HttpClient() +{ + if (s_requestQueue != nullptr) { + { + std::lock_guard lock(s_requestQueueMutex); + s_requestQueue->pushBack(s_requestSentinel); + } + s_SleepCondition.notify_one(); + } + + s_pHttpClient = nullptr; +} + +//Lazy create semaphore & mutex & thread +bool HttpClient::lazyInitThreadSemphore() +{ + if (s_requestQueue != nullptr) { + return true; + } else { + + s_requestQueue = new (std::nothrow) Vector(); + s_responseQueue = new (std::nothrow) Vector(); + + auto t = std::thread(CC_CALLBACK_0(HttpClient::networkThread, this)); + t.detach(); + } + + return true; +} + +//Add a get task to queue +void HttpClient::send(HttpRequest* request) +{ + if (false == lazyInitThreadSemphore()) + { + return; + } + + if (!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(); + } +} + +void HttpClient::sendImmediate(HttpRequest* request) +{ + if(!request) + { + return; + } + + request->retain(); + auto t = std::thread(&HttpClient::networkThreadAlone, this, request); + t.detach(); +} + +// Poll and notify main thread if responses exists in queue +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()) + { + response = s_responseQueue->at(0); + s_responseQueue->erase(0); + } + + s_responseQueueMutex.unlock(); + + if (response) + { + HttpRequest *request = response->getHttpRequest(); + 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(); + } +} + +} + +NS_CC_END + + diff --git a/cocos/network/HttpResponse.h b/cocos/network/HttpResponse.h index 472078d765..e2c89b7098 100644 --- a/cocos/network/HttpResponse.h +++ b/cocos/network/HttpResponse.h @@ -54,6 +54,7 @@ public: _succeed = false; _responseData.clear(); _errorBuffer.clear(); + _responseDataString = ""; } /** Destructor, it will be called in HttpClient internal, @@ -166,6 +167,17 @@ public: _errorBuffer.assign(value); }; + inline void setResponseDataString(const char* value, size_t n) + { + _responseDataString.clear(); + _responseDataString.assign(value, n); + } + + inline const char* getResponseDataString() + { + return _responseDataString.c_str(); + } + protected: bool initWithRequest(HttpRequest* request); @@ -175,7 +187,8 @@ protected: std::vector _responseData; /// the returned raw data. You can also dump it as a string std::vector _responseHeader; /// the returned raw header data. You can also dump it as a string long _responseCode; /// the status code returned from libcurl, e.g. 200, 404 - std::string _errorBuffer; /// if _responseCode != 200, please read _errorBuffer to find the reason + std::string _errorBuffer; /// if _responseCode != 200, please read _errorBuffer to find the reason + std::string _responseDataString; // the returned raw data. You can also dump it as a string }; diff --git a/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxHttpURLConnection.java b/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxHttpURLConnection.java new file mode 100644 index 0000000000..1df6eacdb1 --- /dev/null +++ b/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxHttpURLConnection.java @@ -0,0 +1,501 @@ +/**************************************************************************** +Copyright (c) 2010-2014 cocos2d-x.org + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + ****************************************************************************/ +package org.cocos2dx.lib; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.BufferedInputStream; +import java.io.InputStream; +import java.net.HttpURLConnection; + +import javax.net.ssl.HttpsURLConnection; + +import java.net.ProtocolException; +import java.net.URL; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; + +import javax.net.ssl.SSLContext; + +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.KeyStore; + +import javax.net.ssl.TrustManagerFactory; + +import org.cocos2dx.lib.Cocos2dxHelper; + +import android.content.res.AssetManager; +import android.util.Log; + +public class Cocos2dxHttpURLConnection +{ + private static final String POST_METHOD = "POST" ; + private static final String PUT_METHOD = "PUT" ; + + static HttpURLConnection createHttpURLConnection(String linkURL) + { + URL url; + HttpURLConnection urlConnection; + try + { + url = new URL(linkURL); + urlConnection = (HttpURLConnection) url.openConnection(); + //Accept-Encoding + urlConnection.setRequestProperty("Accept-Encoding", "identity"); + urlConnection.setDoInput(true); + } + catch (Exception e) + { + Log.e("Cocos2dxHttpURLConnection exception", e.toString()); + return null; + } + + return urlConnection; + } + + static void setReadAndConnectTimeout(HttpURLConnection urlConnection, int readMiliseconds, int connectMiliseconds) + { + urlConnection.setReadTimeout(readMiliseconds); + urlConnection.setConnectTimeout(connectMiliseconds); + } + + static void setRequestMethod(HttpURLConnection urlConnection, String method) + { + try + { + urlConnection.setRequestMethod(method); + if(method.equalsIgnoreCase(POST_METHOD) || method.equalsIgnoreCase(PUT_METHOD)) + { + urlConnection.setDoOutput(true); + } + } + catch (ProtocolException e) + { + Log.e("Cocos2dxHttpURLConnection exception", e.toString()); + } + + } + + static void setVerifySSL(HttpURLConnection urlConnection, String sslFilename) + { + if(!(urlConnection instanceof HttpsURLConnection)) + return; + + + HttpsURLConnection httpsURLConnection = (HttpsURLConnection)urlConnection; + + try + { + InputStream caInput = null; + if (sslFilename.startsWith("/")) { + caInput = new BufferedInputStream(new FileInputStream(sslFilename)); + }else { + String assetString = "assets/"; + String assetsfilenameString = sslFilename.substring(assetString.length()); + caInput = new BufferedInputStream(Cocos2dxHelper.getActivity().getAssets().open(assetsfilenameString)); + } + + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate ca; + ca = cf.generateCertificate(caInput); + System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN()); + caInput.close(); + + // Create a KeyStore containing our trusted CAs + String keyStoreType = KeyStore.getDefaultType(); + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + keyStore.load(null, null); + keyStore.setCertificateEntry("ca", ca); + + // Create a TrustManager that trusts the CAs in our KeyStore + String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); + tmf.init(keyStore); + + // Create an SSLContext that uses our TrustManager + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, tmf.getTrustManagers(), null); + + httpsURLConnection.setSSLSocketFactory(context.getSocketFactory()); + } + catch(Exception e) + { + Log.e("Cocos2dxHttpURLConnection exception", e.toString()); + } + } + + //Add header + static void addRequestHeader(HttpURLConnection urlConnection, String key, String value) + { + urlConnection.setRequestProperty(key, value); + } + + static int connect(HttpURLConnection http) + { + int suc = 0; + + try + { + http.connect(); + } + catch (IOException e) + { + Log.e("cocos2d-x debug info", "come in connect"); + Log.e("cocos2d-x debug info", e.toString()); + suc = 1; + } + + return suc; + } + + static void disconnect(HttpURLConnection http) + { + http.disconnect(); + } + + static void sendRequest(HttpURLConnection http, byte[] byteArray) + { + try { + OutputStream out = http.getOutputStream(); + if(null != byteArray) + { + out.write(byteArray); + out.flush(); + } + out.close(); + } catch (IOException e) { + Log.e("Cocos2dxHttpURLConnection exception", e.toString()); + } + } + + static String getResponseHeaders(HttpURLConnection http) + { + Map> headers = http.getHeaderFields(); + if (null == headers) + { + return null; + } + + String header = null; + + for (Entry> entry: headers.entrySet()) + { + String key = entry.getKey(); + if (null == key) + { + header += listToString(entry.getValue(), ","); + } + else + { + header += key + ":" + listToString(entry.getValue(), ","); + } + } + + return header; + } + + static String getResponseHeaderByIdx(HttpURLConnection http, int idx) + { + Map> headers = http.getHeaderFields(); + if (null == headers) { + return null; + } + + String header = null; + + int counter = 0; + for (Entry> entry: headers.entrySet()) + { + if (counter == idx) + { + String key = entry.getKey(); + if (null == key) + { + header = listToString(entry.getValue(), ",") + "\n"; + } else { + header = key + ":" + listToString(entry.getValue(), ",") + "\n"; + } + break; + } + counter++; + } + + return header; + } + + static String getResponseHeaderByKey(HttpURLConnection http, String key) { + if (null == key) { + return null; + } + + Map> headers = http.getHeaderFields(); + if (null == headers) { + return null; + } + + String header = null; + + for (Entry> entry: headers.entrySet()) { + if (key.equalsIgnoreCase(entry.getKey())) { + if ("set-cookie".equalsIgnoreCase(key)) { + header = combinCookies(entry.getValue(), http.getURL().getHost()); + } else { + header = listToString(entry.getValue(), ","); + } + break; + } + } + + return header; + } + + static int getResponseHeaderByKeyInt(HttpURLConnection http, String key) + { + String value = http.getHeaderField(key); + + if (null == value) + { + return 0; + } + else + { + return Integer.parseInt(value); + } + } + + static byte[] getResponseContent(HttpURLConnection http) + { + try + { + // BufferedInputStream bufIn = new BufferedInputStream(http.getInputStream()); + // int length = bufIn.available(); + // byte[] buffer = new byte[length]; + // bufIn.read(buffer, 0, length); + // return buffer; + +// //2 +// // DataInputStream in = new DataInputStream(http.getInputStream()); +// // +// // byte[] buffer = new byte[1024]; +// // byte[] retBuf = null; +// // int len = in.read(buffer); +// // +// // if (-1 == len) { +// // retBuf = new byte[1]; +// // retBuf[0] = 0; +// // } else { +// // retBuf = new byte[len+1]; +// // retBuf[0] = 1; +// // System.arraycopy(buffer, 0, retBuf, 1, len); +// // } +// // return retBuf; + +// byte[] readbuffer = new byte[1024]; +// int size = 0; +// ByteArrayOutputStream bytestream = new ByteArrayOutputStream(); +// while((size = bufIn.read(readbuffer, 0 , 1024)) != -1) +// { +// bytestream.write(readbuffer, 0, size); +// Log.e("cocos2d-x debug info", "getResponedContent"); +// Log.e("cocos2d-x debug info", bytestream.toString()); +// } + +// // ByteArrayOutputStream bytestream = new ByteArrayOutputStream(); +// // int ch; +// // while ((ch = bufIn.read()) != -1) +// // { +// // bytestream.write(ch); +// // } +// byte buffer[] = bytestream.toByteArray(); +// bytestream.close(); +// Log.e("cocos2d-x debug info", String.valueOf(bytestream.size())); +// return buffer; + + + DataInputStream in = new DataInputStream(http.getInputStream()); + byte[] buffer = new byte[1024]; + int size = 0; + ByteArrayOutputStream bytestream = new ByteArrayOutputStream(); + while((size = in.read(buffer, 0 , 1024)) != -1) + { + bytestream.write(buffer, 0, size); + } + byte retbuffer[] = bytestream.toByteArray(); + bytestream.close(); + return retbuffer; + + } + catch (Exception e) + { + Log.e("Cocos2dxHttpURLConnection exception", e.toString()); + } + return null; + } + + static int getResponseCode(HttpURLConnection http) + { + int code = 0; + try + { + code = http.getResponseCode(); + } + catch (IOException e) + { + Log.e("Cocos2dxHttpURLConnection exception", e.toString()); + } + return code; + } + + static String getResponseMessage(HttpURLConnection http) { + String msg; + try + { + msg = http.getResponseMessage(); + } + catch (IOException e) + { + msg = e.toString(); + Log.e("Cocos2dxHttpURLConnection exception", msg); + } + + return msg; + } + + public static String listToString(List list, String strInterVal) + { + if (list == null) + { + return null; + } + StringBuilder result = new StringBuilder(); + boolean flag = false; + for (String str : list) + { + if (flag) + { + result.append(strInterVal); + } + if (null == str) + { + str = ""; + } + result.append(str); + flag = true; + } + return result.toString(); + } + + public static String combinCookies(List list, String hostDomain) + { + StringBuilder sbCookies = new StringBuilder(); + String domain = hostDomain; + String tailmatch = "FALSE"; + String path = "/"; + String secure = "FALSE"; + String key = null; + String value = null; + String expires = null; + for (String str : list) { + String[] parts = str.split(";"); + for (String part : parts) { + int firstIndex = part.indexOf("="); + if (-1 == firstIndex) + continue; + + String[] item = {part.substring(0, firstIndex), part.substring(firstIndex + 1)}; + if ("expires".equalsIgnoreCase(item[0].trim())) + { + expires = str2Seconds(item[1].trim()); + } + else if("path".equalsIgnoreCase(item[0].trim())) + { + path = item[1]; + } + else if("secure".equalsIgnoreCase(item[0].trim())) + { + secure = item[1]; + } + else if("domain".equalsIgnoreCase(item[0].trim())) + { + domain = item[1]; + } + else if("version".equalsIgnoreCase(item[0].trim()) || "max-age".equalsIgnoreCase(item[0].trim())) + { + //do nothing + } + else + { + key = item[0]; + value = item[1]; + } + } + + if (null == domain) { + domain = "none"; + } + + sbCookies.append(domain); + sbCookies.append('\t'); + sbCookies.append(tailmatch); //access + sbCookies.append('\t'); + sbCookies.append(path); //path + sbCookies.append('\t'); + sbCookies.append(secure); //secure + sbCookies.append('\t'); + sbCookies.append(expires); //expires + sbCookies.append("\t"); + sbCookies.append(key); //key + sbCookies.append("\t"); + sbCookies.append(value); //value + sbCookies.append('\n'); + } + + return sbCookies.toString(); + } + + private static String str2Seconds(String strTime) { + Calendar c = Calendar.getInstance(); + long millisSecond = 0; + + try { + c.setTime(new SimpleDateFormat("EEE, dd-MMM-yy hh:mm:ss zzz", Locale.US).parse(strTime)); + millisSecond = c.getTimeInMillis()/1000; + } catch (ParseException e) { + Log.e("Cocos2dxHttpURLConnection exception", e.toString()); + } + + return Long.toString(millisSecond); + } +}