初始化

This commit is contained in:
SmallMain
2022-06-25 00:23:03 +08:00
commit ef0589e8e5
2264 changed files with 617829 additions and 0 deletions

View File

@@ -0,0 +1,319 @@
/****************************************************************************
Copyright (c) 2015-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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 "network/CCDownloader-android.h"
#include "network/CCDownloader.h"
#include "platform/android/jni/JniHelper.h"
#include "platform/android/jni/JniImp.h"
#include <mutex>
#ifndef JCLS_DOWNLOADER
#define JCLS_DOWNLOADER "org/cocos2dx/lib/Cocos2dxDownloader"
#endif
#define JARG_STR "Ljava/lang/String;"
#define JARG_DOWNLOADER "L" JCLS_DOWNLOADER ";"
#ifndef ORG_DOWNLOADER_CLASS_NAME
#define ORG_DOWNLOADER_CLASS_NAME org_cocos2dx_lib_Cocos2dxDownloader
#endif
#define JNI_DOWNLOADER(FUNC) JNI_METHOD1(ORG_DOWNLOADER_CLASS_NAME,FUNC)
std::unordered_map<int, cocos2d::network::DownloaderAndroid*> sDownloaderMap;
std::mutex sDownloaderMutex;
static void _insertDownloaderAndroid(int id, cocos2d::network::DownloaderAndroid* downloaderPtr)
{
std::lock_guard<std::mutex> guard(sDownloaderMutex);
sDownloaderMap.insert(std::make_pair(id, downloaderPtr));
}
static void _eraseDownloaderAndroid(int id)
{
std::lock_guard<std::mutex> guard(sDownloaderMutex);
sDownloaderMap.erase(id);
}
/**
* If not found, return nullptr, otherwise return the Downloader
*/
static cocos2d::network::DownloaderAndroid* _findDownloaderAndroid(int id)
{
std::lock_guard<std::mutex> guard(sDownloaderMutex);
auto iter = sDownloaderMap.find(id);
if (sDownloaderMap.end() == iter) {
return nullptr;
} else {
return iter->second;
}
}
namespace cocos2d { namespace network {
static int sTaskCounter = 0;
static int sDownloaderCounter = 0;
struct DownloadTaskAndroid : public IDownloadTask
{
DownloadTaskAndroid()
:id(++sTaskCounter)
{
DLLOG("Construct DownloadTaskAndroid: %p", this);
}
virtual ~DownloadTaskAndroid()
{
DLLOG("Destruct DownloadTaskAndroid: %p", this);
}
int id;
std::shared_ptr<const DownloadTask> task; // reference to DownloadTask, when task finish, release
};
DownloaderAndroid::DownloaderAndroid(const DownloaderHints& hints)
: _id(++sDownloaderCounter)
, _impl(nullptr)
{
DLLOG("Construct DownloaderAndroid: %p", this);
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo,
JCLS_DOWNLOADER,
"createDownloader",
"(II" JARG_STR "I)" JARG_DOWNLOADER))
{
jobject jStr = methodInfo.env->NewStringUTF(hints.tempFileNameSuffix.c_str());
jobject jObj = methodInfo.env->CallStaticObjectMethod(
methodInfo.classID,
methodInfo.methodID,
_id,
hints.timeoutInSeconds,
jStr,
hints.countOfMaxProcessingTasks
);
_impl = methodInfo.env->NewGlobalRef(jObj);
DLLOG("android downloader: jObj: %p, _impl: %p", jObj, _impl);
//It's not thread-safe here, use thread-safe method instead
//sDownloaderMap.insert(make_pair(_id, this));
_insertDownloaderAndroid(_id, this);
methodInfo.env->DeleteLocalRef(jStr);
methodInfo.env->DeleteLocalRef(jObj);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
}
DownloaderAndroid::~DownloaderAndroid()
{
if(_impl != nullptr)
{
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo,
JCLS_DOWNLOADER,
"cancelAllRequests",
"(" JARG_DOWNLOADER ")V"))
{
methodInfo.env->CallStaticVoidMethod(
methodInfo.classID,
methodInfo.methodID,
_impl
);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
//It's not thread-safe here, use thread-safe method instead
//sDownloaderMap.erase(_id);
_eraseDownloaderAndroid(_id);
JniHelper::getEnv()->DeleteGlobalRef(_impl);
}
DLLOG("Destruct DownloaderAndroid: %p", this);
}
IDownloadTask *DownloaderAndroid::createCoTask(std::shared_ptr<const DownloadTask>& task)
{
DownloadTaskAndroid *coTask = new DownloadTaskAndroid;
coTask->task = task;
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo,
JCLS_DOWNLOADER,
"createTask",
"(" JARG_DOWNLOADER "I" JARG_STR JARG_STR "[" JARG_STR")V"))
{
jclass jclassString = methodInfo.env->FindClass("java/lang/String");
jstring jstrURL = methodInfo.env->NewStringUTF(task->requestURL.c_str());
jstring jstrPath = methodInfo.env->NewStringUTF(task->storagePath.c_str());
jobjectArray jarrayHeader = methodInfo.env->NewObjectArray(task->header.size()*2, jclassString, NULL);
const std::map<std::string, std::string> &headMap = task->header;
int index = 0;
for (auto it = headMap.cbegin(); it != headMap.cend(); ++it) {
methodInfo.env->SetObjectArrayElement(jarrayHeader, index++, methodInfo.env->NewStringUTF(it->first.c_str()));
methodInfo.env->SetObjectArrayElement(jarrayHeader, index++, methodInfo.env->NewStringUTF(it->second.c_str()));
}
methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, _impl, coTask->id, jstrURL, jstrPath, jarrayHeader);
for (int i = 0; i < index; ++i) {
methodInfo.env->DeleteLocalRef(methodInfo.env->GetObjectArrayElement(jarrayHeader, i));
}
methodInfo.env->DeleteLocalRef(jclassString);
methodInfo.env->DeleteLocalRef(jstrURL);
methodInfo.env->DeleteLocalRef(jstrPath);
methodInfo.env->DeleteLocalRef(jarrayHeader);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
}
DLLOG("DownloaderAndroid::createCoTask id: %d", coTask->id);
_taskMap.insert(std::make_pair(coTask->id, coTask));
return coTask;
}
void DownloaderAndroid::abort(const std::unique_ptr<IDownloadTask>& task) {
auto iter = _taskMap.begin();
for (; iter != _taskMap.end(); iter++) {
if (task.get() == iter->second) {
break;
}
}
if(_impl != nullptr && iter != _taskMap.end())
{
JniMethodInfo methodInfo;
if (JniHelper::getStaticMethodInfo(methodInfo,
JCLS_DOWNLOADER,
"abort",
"(" JARG_DOWNLOADER "I" ")V"))
{
methodInfo.env->CallStaticVoidMethod(
methodInfo.classID,
methodInfo.methodID,
_impl,
iter->first
);
methodInfo.env->DeleteLocalRef(methodInfo.classID);
DownloadTaskAndroid *coTask = iter->second;
_taskMap.erase(iter);
std::vector<unsigned char> emptyBuffer;
onTaskFinish(*coTask->task,
DownloadTask::ERROR_ABORT,
DownloadTask::ERROR_ABORT,
"downloadFile:fail abort",
emptyBuffer
);
coTask->task.reset();
}
}
DLLOG("DownloaderAndroid:abort");
}
void DownloaderAndroid::_onProcess(int taskId, int64_t dl, int64_t dlNow, int64_t dlTotal)
{
DLLOG("DownloaderAndroid::onProgress(taskId: %d, dl: %lld, dlnow: %lld, dltotal: %lld)", taskId, dl, dlNow, dlTotal);
auto iter = _taskMap.find(taskId);
if (_taskMap.end() == iter)
{
DLLOG("DownloaderAndroid::onProgress can't find task with id: %d", taskId);
return;
}
DownloadTaskAndroid *coTask = iter->second;
std::function<int64_t(void*, int64_t)> transferDataToBuffer;
onTaskProgress(*coTask->task, dl, dlNow, dlTotal, transferDataToBuffer);
}
void DownloaderAndroid::_onFinish(int taskId, int errCode, const char *errStr, std::vector<unsigned char>& data)
{
DLLOG("DownloaderAndroid::_onFinish(taskId: %d, errCode: %d, errStr: %s)", taskId, errCode, (errStr)?errStr:"null");
auto iter = _taskMap.find(taskId);
if (_taskMap.end() == iter)
{
DLLOG("DownloaderAndroid::_onFinish can't find task with id: %d", taskId);
return;
}
DownloadTaskAndroid *coTask = iter->second;
std::string str = (errStr ? errStr : "");
_taskMap.erase(iter);
onTaskFinish(*coTask->task,
errStr ? DownloadTask::ERROR_IMPL_INTERNAL : DownloadTask::ERROR_NO_ERROR,
errCode,
str,
data
);
coTask->task.reset();
}
}
} // namespace cocos2d::network
extern "C" {
JNIEXPORT void JNICALL JNI_DOWNLOADER(nativeOnProgress)(JNIEnv *env, jclass clazz, jint id, jint taskId, jlong dl, jlong dlnow, jlong dltotal)
{
if(getApplicationExited()) {
return;
}
DLLOG("_nativeOnProgress(id: %d, taskId: %d, dl: %lld, dlnow: %lld, dltotal: %lld)", id, taskId, dl, dlnow, dltotal);
//It's not thread-safe here, use thread-safe method instead
cocos2d::network::DownloaderAndroid *downloader = _findDownloaderAndroid(id);
if (nullptr == downloader)
{
DLLOG("_nativeOnProgress can't find downloader by key: %p for task: %d", clazz, id);
return;
}
downloader->_onProcess((int)taskId, (int64_t)dl, (int64_t)dlnow, (int64_t)dltotal);
}
JNIEXPORT void JNICALL JNI_DOWNLOADER(nativeOnFinish)(JNIEnv *env, jclass clazz, jint id, jint taskId, jint errCode, jstring errStr, jbyteArray data)
{
if(getApplicationExited())
{
return;
}
DLLOG("_nativeOnFinish(id: %d, taskId: %d)", id, taskId);
//It's not thread-safe here, use thread-safe method instead
cocos2d::network::DownloaderAndroid *downloader = _findDownloaderAndroid(id);
if (nullptr == downloader)
{
DLLOG("_nativeOnFinish can't find downloader id: %d for task: %d", id, taskId);
return;
}
std::vector<unsigned char> buf;
if (errStr)
{
// failure
const char *nativeErrStr = env->GetStringUTFChars(errStr, JNI_FALSE);
downloader->_onFinish((int)taskId, (int)errCode, nativeErrStr, buf);
env->ReleaseStringUTFChars(errStr, nativeErrStr);
return;
}
// success
if (data)
{
int len = env->GetArrayLength(data);
if (len)
{
buf.resize(len);
env->GetByteArrayRegion(data, 0, len, reinterpret_cast<jbyte*>(buf.data()));
}
}
downloader->_onFinish((int)taskId, (int)errCode, nullptr, buf);
}
} // extern "C" {

View File

@@ -0,0 +1,56 @@
/****************************************************************************
Copyright (c) 2015-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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.
****************************************************************************/
#pragma once
#include "network/CCIDownloaderImpl.h"
class _jobject;
namespace cocos2d { namespace network
{
class DownloadTaskAndroid;
class DownloaderHints;
class DownloaderAndroid : public IDownloaderImpl
{
public:
DownloaderAndroid(const DownloaderHints& hints);
virtual ~DownloaderAndroid();
virtual IDownloadTask *createCoTask(std::shared_ptr<const DownloadTask>& task) override;
virtual void abort(const std::unique_ptr<IDownloadTask>& task) override;
// designed called by internal
void _onProcess(int taskId, int64_t dl, int64_t dlNow, int64_t dlTotal);
void _onFinish(int taskId, int errCode, const char *errStr, std::vector<unsigned char>& data);
protected:
int _id;
_jobject* _impl;
std::unordered_map<int, DownloadTaskAndroid*> _taskMap;
};
}} // namespace cocos2d::network

View File

@@ -0,0 +1,880 @@
/****************************************************************************
Copyright (c) 2015-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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 "network/CCDownloader-curl.h"
#include <set>
#include <curl/curl.h>
#include <deque>
#include "base/CCScheduler.h"
#include "platform/CCFileUtils.h"
#include "platform/CCApplication.h"
#include "network/CCDownloader.h"
// **NOTE**
// In the file:
// member function with suffix "Proc" designed called in DownloaderCURL::_threadProc
// member function without suffix designed called in main thread
#ifndef CC_CURL_POLL_TIMEOUT_MS
#define CC_CURL_POLL_TIMEOUT_MS 50
#endif
namespace cocos2d { namespace network {
using namespace std;
////////////////////////////////////////////////////////////////////////////////
// Implementation DownloadTaskCURL
class DownloadTaskCURL : public IDownloadTask
{
static int _sSerialId;
// if more than one task write to one file, cause file broken
// so use a set to check this situation
static set<string> _sStoragePathSet;
public:
int serialId;
DownloadTaskCURL()
: serialId(_sSerialId++)
, _fp(nullptr)
{
_initInternal();
DLLOG("Construct DownloadTaskCURL %p", this);
}
virtual ~DownloadTaskCURL()
{
// if task destroyed unnormally, we should release WritenFileName stored in set.
// Normally, this action should done when task finished.
if (_tempFileName.length() && _sStoragePathSet.end() != _sStoragePathSet.find(_tempFileName))
{
DownloadTaskCURL::_sStoragePathSet.erase(_tempFileName);
}
if (_fp)
{
fclose(_fp);
_fp = nullptr;
}
DLLOG("Destruct DownloadTaskCURL %p", this);
}
bool init(const string& filename, const string& tempSuffix)
{
if (0 == filename.length())
{
// data task
_buf.reserve(CURL_MAX_WRITE_SIZE);
return true;
}
// file task
_fileName = filename;
_tempFileName = filename;
_tempFileName.append(tempSuffix);
if (_sStoragePathSet.end() != _sStoragePathSet.find(_tempFileName))
{
// there is another task uses this storage path
_errCode = DownloadTask::ERROR_FILE_OP_FAILED;
_errCodeInternal = 0;
_errDescription = "More than one download file task write to same file:";
_errDescription.append(_tempFileName);
return false;
}
_sStoragePathSet.insert(_tempFileName);
// open temp file handle for write
bool ret = false;
do
{
string dir;
size_t found = _tempFileName.find_last_of("/\\");
if (found == string::npos)
{
_errCode = DownloadTask::ERROR_INVALID_PARAMS;
_errCodeInternal = 0;
_errDescription = "Can't find dirname in storagePath.";
break;
}
// ensure directory is exist
auto util = FileUtils::getInstance();
dir = _tempFileName.substr(0, found+1);
if (false == util->isDirectoryExist(dir))
{
if (false == util->createDirectory(dir))
{
_errCode = DownloadTask::ERROR_FILE_OP_FAILED;
_errCodeInternal = 0;
_errDescription = "Can't create dir:";
_errDescription.append(dir);
break;
}
}
// open file
_fp = fopen(util->getSuitableFOpen(_tempFileName).c_str(), "ab");
if (nullptr == _fp)
{
_errCode = DownloadTask::ERROR_FILE_OP_FAILED;
_errCodeInternal = 0;
_errDescription = "Can't open file:";
_errDescription.append(_tempFileName);
}
ret = true;
} while (0);
return ret;
}
void initProc()
{
lock_guard<mutex> lock(_mutex);
_initInternal();
}
void setErrorProc(int code, int codeInternal, const char *desc)
{
lock_guard<mutex> lock(_mutex);
_errCode = code;
_errCodeInternal = codeInternal;
_errDescription = desc;
}
size_t writeDataProc(unsigned char *buffer, size_t size, size_t count)
{
lock_guard<mutex> lock(_mutex);
size_t ret = 0;
if (_fp)
{
ret = fwrite(buffer, size, count, _fp);
}
else
{
ret = size * count;
auto cap = _buf.capacity();
auto bufSize = _buf.size();
if (cap < bufSize + ret)
{
_buf.reserve(bufSize * 2);
}
_buf.insert(_buf.end() , buffer, buffer + ret);
}
if (ret)
{
_bytesReceived += ret;
_totalBytesReceived += ret;
}
return ret;
}
private:
friend class DownloaderCURL;
// for lock object instance
mutex _mutex;
// header info
bool _acceptRanges;
bool _headerAchieved;
int64_t _totalBytesExpected;
string _header; // temp buffer for receive header string, only used in thread proc
// progress
int64_t _bytesReceived;
int64_t _totalBytesReceived;
// error
int _errCode;
int _errCodeInternal;
string _errDescription;
// for saving data
string _fileName;
string _tempFileName;
vector<unsigned char> _buf;
FILE* _fp;
void _initInternal()
{
_acceptRanges = (false);
_headerAchieved = (false);
_bytesReceived = (0);
_totalBytesReceived = (0);
_totalBytesExpected = (0);
_errCode = (DownloadTask::ERROR_NO_ERROR);
_errCodeInternal = (CURLE_OK);
_header.resize(0);
_header.reserve(384); // pre alloc header string buffer
}
};
int DownloadTaskCURL::_sSerialId;
set<string> DownloadTaskCURL::_sStoragePathSet;
typedef pair< shared_ptr<const DownloadTask>, DownloadTaskCURL *> TaskWrapper;
////////////////////////////////////////////////////////////////////////////////
// Implementation DownloaderCURL::Impl
// This class shared by DownloaderCURL and work thread.
class DownloaderCURL::Impl : public enable_shared_from_this<DownloaderCURL::Impl>
{
public:
DownloaderHints hints;
Impl()
// : _thread(nullptr)
{
DLLOG("Construct DownloaderCURL::Impl %p", this);
}
~Impl()
{
DLLOG("Destruct DownloaderCURL::Impl %p %d", this, _thread.joinable());
}
void addTask(std::shared_ptr<const DownloadTask> task, DownloadTaskCURL* coTask)
{
if (DownloadTask::ERROR_NO_ERROR == coTask->_errCode)
{
lock_guard<mutex> lock(_requestMutex);
_requestQueue.push_back(make_pair(task, coTask));
}
else
{
lock_guard<mutex> lock(_finishedMutex);
_finishedQueue.push_back(make_pair(task, coTask));
}
}
void run()
{
lock_guard<mutex> lock(_threadMutex);
if (false == _thread.joinable())
{
thread newThread(&DownloaderCURL::Impl::_threadProc, this);
_thread.swap(newThread);
}
}
void stop()
{
lock_guard<mutex> lock(_threadMutex);
if (_thread.joinable())
{
_thread.detach();
}
}
bool stoped()
{
lock_guard<mutex> lock(_threadMutex);
return false == _thread.joinable() ? true : false;
}
void getProcessTasks(vector<TaskWrapper>& outList)
{
lock_guard<mutex> lock(_processMutex);
outList.reserve(_processSet.size());
outList.insert(outList.end(), _processSet.begin(), _processSet.end());
}
void getFinishedTasks(vector<TaskWrapper>& outList)
{
lock_guard<mutex> lock(_finishedMutex);
outList.reserve(_finishedQueue.size());
outList.insert(outList.end(), _finishedQueue.begin(), _finishedQueue.end());
_finishedQueue.clear();
}
private:
static size_t _outputHeaderCallbackProc(void *buffer, size_t size, size_t count, void *userdata)
{
int strLen = int(size * count);
DLLOG(" _outputHeaderCallbackProc: %.*s", strLen, buffer);
DownloadTaskCURL& coTask = *((DownloadTaskCURL*)(userdata));
coTask._header.append((const char *)buffer, strLen);
return strLen;
}
static size_t _outputDataCallbackProc(void *buffer, size_t size, size_t count, void *userdata)
{
// DLLOG(" _outputDataCallbackProc: size(%ld), count(%ld)", size, count);
DownloadTaskCURL *coTask = (DownloadTaskCURL*)userdata;
// If your callback function returns CURL_WRITEFUNC_PAUSE it will cause this transfer to become paused.
return coTask->writeDataProc((unsigned char *)buffer, size, count);
}
// this function designed call in work thread
// the curl handle destroyed in _threadProc
// handle inited for get header
void _initCurlHandleProc(CURL *handle, TaskWrapper& wrapper, bool forContent = false)
{
const DownloadTask& task = *wrapper.first;
const DownloadTaskCURL* coTask = wrapper.second;
// set url
curl_easy_setopt(handle, CURLOPT_URL, task.requestURL.c_str());
// set write func
if (forContent)
{
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, DownloaderCURL::Impl::_outputDataCallbackProc);
}
else
{
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, DownloaderCURL::Impl::_outputHeaderCallbackProc);
}
curl_easy_setopt(handle, CURLOPT_WRITEDATA, coTask);
curl_easy_setopt(handle, CURLOPT_NOPROGRESS, true);
// curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, DownloaderCURL::Impl::_progressCallbackProc);
// curl_easy_setopt(handle, CURLOPT_XFERINFODATA, coTask);
curl_easy_setopt(handle, CURLOPT_FAILONERROR, true);
curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1L);
if (forContent)
{
/** if server acceptRanges and local has part of file, we continue to download **/
if (coTask->_acceptRanges && coTask->_totalBytesReceived > 0)
{
curl_easy_setopt(handle, CURLOPT_RESUME_FROM_LARGE,(curl_off_t)coTask->_totalBytesReceived);
}
}
else
{
// get header options
curl_easy_setopt(handle, CURLOPT_HEADER, 1);
curl_easy_setopt(handle, CURLOPT_NOBODY, 1);
}
// if (!sProxy.empty())
// {
// curl_easy_setopt(curl, CURLOPT_PROXY, sProxy.c_str());
// }
if (hints.timeoutInSeconds)
{
curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, hints.timeoutInSeconds);
}
static const long LOW_SPEED_LIMIT = 1;
static const long LOW_SPEED_TIME = 10;
curl_easy_setopt(handle, CURLOPT_LOW_SPEED_LIMIT, LOW_SPEED_LIMIT);
curl_easy_setopt(handle, CURLOPT_LOW_SPEED_TIME, LOW_SPEED_TIME);
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, false);
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, false);
static const int MAX_REDIRS = 5;
if (MAX_REDIRS)
{
curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, true);
curl_easy_setopt(handle, CURLOPT_MAXREDIRS, MAX_REDIRS);
}
}
// get header info, if success set handle to content download state
bool _getHeaderInfoProc(CURL *handle, TaskWrapper& wrapper)
{
DownloadTaskCURL& coTask = *wrapper.second;
CURLcode rc = CURLE_OK;
do
{
long httpResponseCode = 0;
rc = curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &httpResponseCode);
if (CURLE_OK != rc)
{
break;
}
if (200 != httpResponseCode)
{
char buf[256] = {0};
sprintf(buf
, "When request url(%s) header info, return unexcept http response code(%ld)"
, wrapper.first->requestURL.c_str()
, httpResponseCode);
coTask.setErrorProc(DownloadTask::ERROR_IMPL_INTERNAL, CURLE_OK, buf);
}
// curl_easy_getinfo(handle, CURLINFO_EFFECTIVE_URL, &effectiveUrl);
// curl_easy_getinfo(handle, CURLINFO_CONTENT_TYPE, &contentType);
double contentLen = 0;
rc = curl_easy_getinfo(handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &contentLen);
if (CURLE_OK != rc)
{
break;
}
bool acceptRanges = (string::npos != coTask._header.find("Accept-Ranges")) ? true : false;
// get current file size
int64_t fileSize = 0;
if (acceptRanges && coTask._tempFileName.length())
{
fileSize = FileUtils::getInstance()->getFileSize(coTask._tempFileName);
}
// set header info to coTask
lock_guard<mutex> lock(coTask._mutex);
coTask._totalBytesExpected = (int64_t)contentLen;
coTask._acceptRanges = acceptRanges;
if (acceptRanges && fileSize > 0)
{
coTask._totalBytesReceived = fileSize;
}
coTask._headerAchieved = true;
} while (0);
if (CURLE_OK != rc)
{
coTask.setErrorProc(DownloadTask::ERROR_IMPL_INTERNAL, rc, curl_easy_strerror(rc));
}
return coTask._headerAchieved;
}
void _threadProc()
{
DLLOG("++++DownloaderCURL::Impl::_threadProc begin %p", this);
// the holder prevent DownloaderCURL::Impl class instance be destruct in main thread
auto holder = this->shared_from_this();
auto thisThreadId = this_thread::get_id();
uint32_t countOfMaxProcessingTasks = this->hints.countOfMaxProcessingTasks;
// init curl content
CURLM* curlmHandle = curl_multi_init();
unordered_map<CURL*, TaskWrapper> coTaskMap;
int runningHandles = 0;
CURLMcode mcode = CURLM_OK;
int rc = 0; // select return code
do
{
// check the thread should exit or not
{
lock_guard<mutex> lock(_threadMutex);
// if the Impl stoped, this->_thread.reset will be called, thus _thread.get_id() not equal with thisThreadId
if (thisThreadId != this->_thread.get_id())
{
break;
}
}
if (runningHandles)
{
// get timeout setting from multi-handle
long timeoutMS = -1;
curl_multi_timeout(curlmHandle, &timeoutMS);
if(timeoutMS < 0)
{
timeoutMS = 1000;
}
/* get file descriptors from the transfers */
fd_set fdread;
fd_set fdwrite;
fd_set fdexcep;
int maxfd = -1;
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_ZERO(&fdexcep);
mcode = curl_multi_fdset(curlmHandle, &fdread, &fdwrite, &fdexcep, &maxfd);
if (CURLM_OK != mcode)
{
break;
}
// do wait action
if(maxfd == -1)
{
this_thread::sleep_for(chrono::milliseconds(CC_CURL_POLL_TIMEOUT_MS));
rc = 0;
}
else
{
struct timeval timeout;
timeout.tv_sec = timeoutMS / 1000;
timeout.tv_usec = (timeoutMS % 1000) * 1000;
rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);
}
if (rc < 0)
{
DLLOG(" _threadProc: select return unexpect code: %d", rc);
}
}
if (coTaskMap.size())
{
mcode = CURLM_CALL_MULTI_PERFORM;
while(CURLM_CALL_MULTI_PERFORM == mcode)
{
mcode = curl_multi_perform(curlmHandle, &runningHandles);
}
if (CURLM_OK != mcode)
{
break;
}
struct CURLMsg *m;
do {
int msgq = 0;
m = curl_multi_info_read(curlmHandle, &msgq);
if(m && (m->msg == CURLMSG_DONE))
{
CURL *curlHandle = m->easy_handle;
CURLcode errCode = m->data.result;
TaskWrapper wrapper = coTaskMap[curlHandle];
// remove from multi-handle
curl_multi_remove_handle(curlmHandle, curlHandle);
bool reinited = false;
do
{
if (CURLE_OK != errCode)
{
wrapper.second->setErrorProc(DownloadTask::ERROR_IMPL_INTERNAL, errCode, curl_easy_strerror(errCode));
break;
}
// if the task is content download task, cleanup the handle
if (wrapper.second->_headerAchieved)
{
break;
}
// the task is get header task
// first, we get info from response
if (false == _getHeaderInfoProc(curlHandle, wrapper))
{
// the error info has been set in _getHeaderInfoProc
break;
}
// after get header info success
// wrapper.second->_totalBytesReceived inited by local file size
// if the local file size equal with the content size from header, the file has downloaded finish
if (wrapper.second->_totalBytesReceived &&
wrapper.second->_totalBytesReceived == wrapper.second->_totalBytesExpected)
{
// the file has download complete
// break to move this task to finish queue
break;
}
// reinit curl handle for download content
curl_easy_reset(curlHandle);
_initCurlHandleProc(curlHandle, wrapper, true);
mcode = curl_multi_add_handle(curlmHandle, curlHandle);
if (CURLM_OK != mcode)
{
wrapper.second->setErrorProc(DownloadTask::ERROR_IMPL_INTERNAL, mcode, curl_multi_strerror(mcode));
break;
}
reinited = true;
} while (0);
if (reinited)
{
continue;
}
curl_easy_cleanup(curlHandle);
DLLOG(" _threadProc task clean cur handle :%p with errCode:%d", curlHandle, errCode);
// remove from coTaskMap
coTaskMap.erase(curlHandle);
// remove from _processSet
{
lock_guard<mutex> lock(_processMutex);
if (_processSet.end() != _processSet.find(wrapper)) {
_processSet.erase(wrapper);
}
}
// add to finishedQueue
{
lock_guard<mutex> lock(_finishedMutex);
_finishedQueue.push_back(wrapper);
}
}
} while(m);
}
// process tasks in _requestList
auto size = coTaskMap.size();
while (0 == countOfMaxProcessingTasks || size < countOfMaxProcessingTasks)
{
// get task wrapper from request queue
TaskWrapper wrapper;
{
lock_guard<mutex> lock(_requestMutex);
if (_requestQueue.size())
{
wrapper = _requestQueue.front();
_requestQueue.pop_front();
}
}
// if request queue is empty, the wrapper.first is nullptr
if (! wrapper.first)
{
break;
}
wrapper.second->initProc();
// create curl handle from task and add into curl multi handle
CURL* curlHandle = curl_easy_init();
if (nullptr == curlHandle)
{
wrapper.second->setErrorProc(DownloadTask::ERROR_IMPL_INTERNAL, 0, "Alloc curl handle failed.");
lock_guard<mutex> lock(_finishedMutex);
_finishedQueue.push_back(wrapper);
continue;
}
// init curl handle for get header info
_initCurlHandleProc(curlHandle, wrapper);
// add curl handle to process list
mcode = curl_multi_add_handle(curlmHandle, curlHandle);
if (CURLM_OK != mcode)
{
wrapper.second->setErrorProc(DownloadTask::ERROR_IMPL_INTERNAL, mcode, curl_multi_strerror(mcode));
lock_guard<mutex> lock(_finishedMutex);
_finishedQueue.push_back(wrapper);
continue;
}
DLLOG(" _threadProc task create curl handle:%p", curlHandle);
coTaskMap[curlHandle] = wrapper;
lock_guard<mutex> lock(_processMutex);
_processSet.insert(wrapper);
}
} while (coTaskMap.size());
curl_multi_cleanup(curlmHandle);
this->stop();
DLLOG("----DownloaderCURL::Impl::_threadProc end");
}
thread _thread;
deque<TaskWrapper> _requestQueue;
set<TaskWrapper> _processSet;
deque<TaskWrapper> _finishedQueue;
mutex _threadMutex;
mutex _requestMutex;
mutex _processMutex;
mutex _finishedMutex;
};
////////////////////////////////////////////////////////////////////////////////
// Implementation DownloaderCURL
DownloaderCURL::DownloaderCURL(const DownloaderHints& hints)
: _impl(std::make_shared<Impl>())
, _currTask(nullptr)
{
DLLOG("Construct DownloaderCURL %p", this);
_impl->hints = hints;
_scheduler = Application::getInstance()->getScheduler();
_transferDataToBuffer = [this](void *buf, int64_t len)->int64_t
{
DownloadTaskCURL& coTask = *_currTask;
int64_t dataLen = coTask._buf.size();
if (len < dataLen)
{
return 0;
}
memcpy(buf, coTask._buf.data(), dataLen);
coTask._buf.resize(0);
return dataLen;
};
char key[128];
sprintf(key, "DownloaderCURL(%p)", this);
_schedulerKey = key;
if(auto sche = _scheduler.lock())
{
sche->schedule(bind(&DownloaderCURL::_onSchedule, this, placeholders::_1),
this,
0.1f,
true,
_schedulerKey);
}
}
DownloaderCURL::~DownloaderCURL()
{
if(auto sche = _scheduler.lock())
{
sche->unschedule(_schedulerKey, this);
}
_impl->stop();
DLLOG("Destruct DownloaderCURL %p", this);
}
IDownloadTask *DownloaderCURL::createCoTask(std::shared_ptr<const DownloadTask>& task)
{
DownloadTaskCURL *coTask = new (std::nothrow) DownloadTaskCURL;
coTask->init(task->storagePath, _impl->hints.tempFileNameSuffix);
DLLOG(" DownloaderCURL: createTask: Id(%d)", coTask->serialId);
_impl->addTask(task, coTask);
_impl->run();
if(auto sche = _scheduler.lock())
{
sche->resumeTarget(this);
}
return coTask;
}
void DownloaderCURL::abort(const std::unique_ptr<IDownloadTask>& task) {
// REFINE
// https://github.com/cocos-creator/cocos2d-x-lite/pull/1291
DLLOG("%s isn't implemented!\n", __FUNCTION__);
}
void DownloaderCURL::_onSchedule(float)
{
vector<TaskWrapper> tasks;
// update processing tasks
_impl->getProcessTasks(tasks);
for (auto& wrapper : tasks)
{
const DownloadTask& task = *wrapper.first;
DownloadTaskCURL& coTask = *wrapper.second;
lock_guard<mutex> lock(coTask._mutex);
if (coTask._bytesReceived)
{
_currTask = &coTask;
onTaskProgress(task,
coTask._bytesReceived,
coTask._totalBytesReceived,
coTask._totalBytesExpected,
_transferDataToBuffer);
_currTask = nullptr;
coTask._bytesReceived = 0;
}
}
tasks.resize(0);
// update finished tasks
_impl->getFinishedTasks(tasks);
if (_impl->stoped())
{
if (auto sche = _scheduler.lock())
{
sche->pauseTarget(this);
}
}
for (auto& wrapper : tasks)
{
const DownloadTask& task = *wrapper.first;
DownloadTaskCURL& coTask = *wrapper.second;
// if there is bytesReceived, call progress update first
if (coTask._bytesReceived)
{
_currTask = &coTask;
onTaskProgress(task,
coTask._bytesReceived,
coTask._totalBytesReceived,
coTask._totalBytesExpected,
_transferDataToBuffer);
coTask._bytesReceived = 0;
_currTask = nullptr;
}
// if file task, close file handle and rename file if needed
if (coTask._fp)
{
fclose(coTask._fp);
coTask._fp = nullptr;
do
{
if (0 == coTask._fileName.length())
{
break;
}
auto util = FileUtils::getInstance();
// if file already exist, remove it
if (util->isFileExist(coTask._fileName))
{
if (false == util->removeFile(coTask._fileName))
{
coTask._errCode = DownloadTask::ERROR_FILE_OP_FAILED;
coTask._errCodeInternal = 0;
coTask._errDescription = "Can't remove old file: ";
coTask._errDescription.append(coTask._fileName);
break;
}
}
// rename file
if (util->renameFile(coTask._tempFileName, coTask._fileName))
{
// success, remove storage from set
DownloadTaskCURL::_sStoragePathSet.erase(coTask._tempFileName);
break;
}
// failed
coTask._errCode = DownloadTask::ERROR_FILE_OP_FAILED;
coTask._errCodeInternal = 0;
coTask._errDescription = "Can't renamefile from: ";
coTask._errDescription.append(coTask._tempFileName);
coTask._errDescription.append(" to: ");
coTask._errDescription.append(coTask._fileName);
} while (0);
}
// needn't lock coTask here, because tasks has removed form _impl
onTaskFinish(task, coTask._errCode, coTask._errCodeInternal, coTask._errDescription, coTask._buf);
DLLOG(" DownloaderCURL: finish Task: Id(%d)", coTask.serialId);
}
}
}} // namespace cocos2d::network

View File

@@ -0,0 +1,64 @@
/****************************************************************************
Copyright (c) 2015-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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.
****************************************************************************/
#pragma once
#include "network/CCIDownloaderImpl.h"
namespace cocos2d {
class Scheduler;
}
namespace cocos2d { namespace network
{
class DownloadTaskCURL;
class DownloaderHints;
class DownloaderCURL : public IDownloaderImpl
{
public:
DownloaderCURL(const DownloaderHints& hints);
virtual ~DownloaderCURL();
virtual IDownloadTask *createCoTask(std::shared_ptr<const DownloadTask>& task) override;
virtual void abort(const std::unique_ptr<IDownloadTask>& task) override;
protected:
class Impl;
std::shared_ptr<Impl> _impl;
// for transfer data on schedule
DownloadTaskCURL* _currTask; // temp ref
std::function<int64_t(void*, int64_t)> _transferDataToBuffer;
// scheduler for update processing and finished task in main schedule
void _onSchedule(float);
std::string _schedulerKey;
std::weak_ptr<Scheduler> _scheduler;
};
}} // namespace cocos2d::network

View File

@@ -0,0 +1,198 @@
/****************************************************************************
Copyright (c) 2015-2016 cocos2d-x.org
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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 "network/CCDownloader.h"
// include platform specific implement class
#if (CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
#include "network/CCDownloaderImpl-apple.h"
#define DownloaderImpl DownloaderApple
#elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
#include "network/CCDownloader-android.h"
#define DownloaderImpl DownloaderAndroid
#else
#include "network/CCDownloader-curl.h"
#define DownloaderImpl DownloaderCURL
#endif
namespace cocos2d { namespace network {
DownloadTask::DownloadTask()
{
DLLOG("Construct DownloadTask %p", this);
}
DownloadTask::~DownloadTask()
{
DLLOG("Destruct DownloadTask %p", this);
}
////////////////////////////////////////////////////////////////////////////////
// Implement Downloader
Downloader::Downloader()
{
DownloaderHints hints =
{
6,
45,
".tmp"
};
new(this)Downloader(hints);
}
Downloader::Downloader(const DownloaderHints& hints)
{
DLLOG("Construct Downloader %p", this);
_impl.reset(new DownloaderImpl(hints));
_impl->onTaskProgress = [this](const DownloadTask& task,
int64_t bytesReceived,
int64_t totalBytesReceived,
int64_t totalBytesExpected,
std::function<int64_t(void *buffer, int64_t len)>& /*transferDataToBuffer*/)
{
if (onTaskProgress)
{
onTaskProgress(task, bytesReceived, totalBytesReceived, totalBytesExpected);
}
};
_impl->onTaskFinish = [this](const DownloadTask& task,
int errorCode,
int errorCodeInternal,
const std::string& errorStr,
std::vector<unsigned char>& data)
{
if (DownloadTask::ERROR_NO_ERROR != errorCode)
{
if (onTaskError)
{
onTaskError(task, errorCode, errorCodeInternal, errorStr);
}
return;
}
// success callback
if (task.storagePath.length())
{
if (onFileTaskSuccess)
{
onFileTaskSuccess(task);
}
}
else
{
// data task
if (onDataTaskSuccess)
{
onDataTaskSuccess(task, data);
}
}
};
}
Downloader::~Downloader()
{
DLLOG("Destruct Downloader %p", this);
}
std::shared_ptr<const DownloadTask> Downloader::createDownloadDataTask(const std::string& srcUrl, const std::string& identifier/* = ""*/)
{
DownloadTask *task_ = new (std::nothrow) DownloadTask();
std::shared_ptr<const DownloadTask> task(task_);
do
{
task_->requestURL = srcUrl;
task_->identifier = identifier;
if (0 == srcUrl.length())
{
if (onTaskError)
{
onTaskError(*task, DownloadTask::ERROR_INVALID_PARAMS, 0, "URL or is empty.");
}
task.reset();
break;
}
task_->_coTask.reset(_impl->createCoTask(task));
} while (0);
return task;
}
std::shared_ptr<const DownloadTask> Downloader::createDownloadFileTask(const std::string& srcUrl,
const std::string& storagePath,
const std::map<std::string, std::string> &header,
const std::string& identifier/* = ""*/)
{
DownloadTask *task_ = new (std::nothrow) DownloadTask();
std::shared_ptr<const DownloadTask> task(task_);
do
{
task_->requestURL = srcUrl;
task_->storagePath = storagePath;
task_->identifier = identifier;
task_->header = header;
if (0 == srcUrl.length() || 0 == storagePath.length())
{
if (onTaskError)
{
onTaskError(*task, DownloadTask::ERROR_INVALID_PARAMS, 0, "URL or storage path is empty.");
}
task.reset();
break;
}
task_->_coTask.reset(_impl->createCoTask(task));
} while (0);
return task;
}
std::shared_ptr<const DownloadTask> Downloader::createDownloadFileTask(const std::string& srcUrl,
const std::string& storagePath,
const std::string& identifier/* = ""*/) {
const std::map<std::string, std::string> emptyHeader;
return createDownloadFileTask(srcUrl, storagePath, emptyHeader, identifier);
}
void Downloader::abort(const DownloadTask& task) {
_impl->abort(task._coTask);
}
//std::string Downloader::getFileNameFromUrl(const std::string& srcUrl)
//{
// // Find file name and file extension
// std::string filename;
// unsigned long found = srcUrl.find_last_of("/\\");
// if (found != std::string::npos)
// filename = srcUrl.substr(found+1);
// return filename;
//}
}} // namespace cocos2d::network

View File

@@ -0,0 +1,120 @@
/****************************************************************************
Copyright (c) 2015-2016 cocos2d-x.org
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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.
****************************************************************************/
#pragma once
#include <functional>
#include <map>
#include <string>
#include <memory>
#include <vector>
#include "base/ccMacros.h"
namespace cocos2d { namespace network {
class IDownloadTask;
class IDownloaderImpl;
class Downloader;
class CC_DLL DownloadTask final
{
public:
const static int ERROR_NO_ERROR = 0;
const static int ERROR_INVALID_PARAMS = -1;
const static int ERROR_FILE_OP_FAILED = -2;
const static int ERROR_IMPL_INTERNAL = -3;
const static int ERROR_ABORT = -4;
std::string identifier;
std::string requestURL;
std::string storagePath;
std::map<std::string, std::string> header;
DownloadTask();
virtual ~DownloadTask();
private:
friend class Downloader;
std::unique_ptr<IDownloadTask> _coTask;
};
class CC_DLL DownloaderHints
{
public:
uint32_t countOfMaxProcessingTasks;
uint32_t timeoutInSeconds;
std::string tempFileNameSuffix;
};
class CC_DLL Downloader final
{
public:
Downloader();
Downloader(const DownloaderHints& hints);
~Downloader();
std::function<void(const DownloadTask& task,
std::vector<unsigned char>& data)> onDataTaskSuccess;
std::function<void(const DownloadTask& task)> onFileTaskSuccess;
std::function<void(const DownloadTask& task,
int64_t bytesReceived,
int64_t totalBytesReceived,
int64_t totalBytesExpected)> onTaskProgress;
std::function<void(const DownloadTask& task,
int errorCode,
int errorCodeInternal,
const std::string& errorStr)> onTaskError;
void setOnFileTaskSuccess(const std::function<void(const DownloadTask& task)>& callback) {onFileTaskSuccess = callback;};
void setOnTaskProgress(const std::function<void(const DownloadTask& task,
int64_t bytesReceived,
int64_t totalBytesReceived,
int64_t totalBytesExpected)>& callback) {onTaskProgress = callback;};
void setOnTaskError(const std::function<void(const DownloadTask& task,
int errorCode,
int errorCodeInternal,
const std::string& errorStr)>& callback) {onTaskError = callback;};
std::shared_ptr<const DownloadTask> createDownloadDataTask(const std::string& srcUrl, const std::string& identifier = "");
std::shared_ptr<const DownloadTask> createDownloadFileTask(const std::string& srcUrl, const std::string& storagePath, const std::string& identifier = "");
std::shared_ptr<const DownloadTask> createDownloadFileTask(const std::string& srcUrl, const std::string& storagePath, const std::map<std::string, std::string>& header, const std::string& identifier = "");
void abort(const DownloadTask& task);
private:
std::unique_ptr<IDownloaderImpl> _impl;
};
}} // namespace cocos2d::network

View File

@@ -0,0 +1,47 @@
/****************************************************************************
Copyright (c) 2015-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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.
****************************************************************************/
#pragma once
#include "network/CCIDownloaderImpl.h"
namespace cocos2d { namespace network
{
class DownloaderHints;
class DownloaderApple : public IDownloaderImpl
{
public:
DownloaderApple(const DownloaderHints& hints);
virtual ~DownloaderApple();
virtual IDownloadTask *createCoTask(std::shared_ptr<const DownloadTask>& task) override;
virtual void abort(const std::unique_ptr<IDownloadTask>& task) override;
private:
void* _impl;
};
}} // namespace cocos2d::network

View File

@@ -0,0 +1,714 @@
/****************************************************************************
Copyright (c) 2015-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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 "network/CCDownloaderImpl-apple.h"
#include <queue>
#import <Foundation/Foundation.h>
#include "network/CCDownloader.h"
#include "base/ccUTF8.h"
////////////////////////////////////////////////////////////////////////////////
// OC Classes Declaration
// this wrapper used to wrap C++ class DownloadTask into NSMutableDictionary
@interface DownloadTaskWrapper : NSObject
{
std::shared_ptr<const cocos2d::network::DownloadTask> _task;
NSMutableArray *_dataArray;
}
// temp vars for dataTask: didReceivedData callback
@property (nonatomic) int64_t bytesReceived;
@property (nonatomic) int64_t totalBytesReceived;
-(id)init:(std::shared_ptr<const cocos2d::network::DownloadTask>&)t;
-(cocos2d::network::DownloadTask *)get;
-(void) addData:(NSData*) data;
-(int64_t) transferDataToBuffer:(void*)buffer lengthOfBuffer:(int64_t)len;
@end
@interface DownloaderAppleImpl : NSObject <NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
{
const cocos2d::network::DownloaderApple *_outer;
cocos2d::network::DownloaderHints _hints;
std::queue<NSURLSessionTask*> _taskQueue;
}
@property (nonatomic, strong) NSURLSession *downloadSession;
@property (nonatomic, strong) NSMutableDictionary *taskDict; // ocTask: DownloadTaskWrapper
-(id)init:(const cocos2d::network::DownloaderApple *)o hints:(const cocos2d::network::DownloaderHints&) hints;
-(const cocos2d::network::DownloaderHints&)getHints;
-(NSURLSessionDataTask *)createDataTask:(std::shared_ptr<const cocos2d::network::DownloadTask>&) task;
-(NSURLSessionDownloadTask *)createFileTask:(std::shared_ptr<const cocos2d::network::DownloadTask>&) task;
-(void)abort:(NSURLSessionTask *)task;
-(void)doDestroy;
@end
////////////////////////////////////////////////////////////////////////////////
// C++ Classes Implementation
namespace cocos2d { namespace network {
struct DownloadTaskApple : public IDownloadTask
{
DownloadTaskApple()
: dataTask(nil)
, downloadTask(nil)
{
DLLOG("Construct DownloadTaskApple %p", this);
}
virtual ~DownloadTaskApple()
{
DLLOG("Destruct DownloadTaskApple %p", this);
}
NSURLSessionDataTask *dataTask;
NSURLSessionDownloadTask *downloadTask;
};
#define DeclareDownloaderImplVar DownloaderAppleImpl *impl = (__bridge DownloaderAppleImpl *)_impl
// the _impl's type is id, we should convert it to subclass before call it's methods
DownloaderApple::DownloaderApple(const DownloaderHints& hints)
: _impl(nil)
{
DLLOG("Construct DownloaderApple %p", this);
_impl = (__bridge void*)[[DownloaderAppleImpl alloc] init: this hints:hints];
}
DownloaderApple::~DownloaderApple()
{
DeclareDownloaderImplVar;
[impl doDestroy];
DLLOG("Destruct DownloaderApple %p", this);
}
IDownloadTask *DownloaderApple::createCoTask(std::shared_ptr<const DownloadTask>& task)
{
DownloadTaskApple* coTask = new (std::nothrow) DownloadTaskApple();
DeclareDownloaderImplVar;
if (task->storagePath.length())
{
coTask->downloadTask = [impl createFileTask:task];
}
else
{
coTask->dataTask = [impl createDataTask:task];
}
return coTask;
}
void DownloaderApple::abort(const std::unique_ptr<IDownloadTask> &task) {
DLLOG("DownloaderApple:abort");
DeclareDownloaderImplVar;
cocos2d::network::DownloadTaskApple *downloadTask = (cocos2d::network::DownloadTaskApple *)task.get();
NSURLSessionTask *taskOC = downloadTask->dataTask ? downloadTask->dataTask : downloadTask->downloadTask;
[impl abort:taskOC];
}
}} // namespace cocos2d::network
////////////////////////////////////////////////////////////////////////////////
// OC Classes Implementation
@implementation DownloadTaskWrapper
- (id)init: (std::shared_ptr<const cocos2d::network::DownloadTask>&)t
{
DLLOG("Construct DonloadTaskWrapper %p", self);
_dataArray = [NSMutableArray arrayWithCapacity:8];
[_dataArray retain];
_task = t;
return self;
}
-(cocos2d::network::DownloadTask *)get
{
return const_cast<cocos2d::network::DownloadTask *>(_task.get());
}
-(void) addData:(NSData*) data
{
[_dataArray addObject:data];
self.bytesReceived += data.length;
self.totalBytesReceived += data.length;
}
-(int64_t) transferDataToBuffer:(void*)buffer lengthOfBuffer:(int64_t)len
{
int64_t bytesReceived = 0;
int receivedDataObject = 0;
__block char *p = (char *)buffer;
for (NSData* data in _dataArray)
{
// check
if (bytesReceived + data.length > len)
{
break;
}
// copy data
[data enumerateByteRangesUsingBlock:^(const void *bytes,
NSRange byteRange,
BOOL *stop)
{
memcpy(p, bytes, byteRange.length);
p += byteRange.length;
*stop = NO;
}];
// accumulate
bytesReceived += data.length;
++receivedDataObject;
}
// remove receivedNSDataObject from dataArray
[_dataArray removeObjectsInRange:NSMakeRange(0, receivedDataObject)];
self.bytesReceived -= bytesReceived;
return bytesReceived;
}
-(void)dealloc
{
[_dataArray release];
[super dealloc];
DLLOG("Destruct DownloadTaskWrapper %p", self);
}
@end
@implementation DownloaderAppleImpl
- (id)init: (const cocos2d::network::DownloaderApple*)o hints:(const cocos2d::network::DownloaderHints&) hints
{
DLLOG("Construct DownloaderAppleImpl %p", self);
// save outer task ref
_outer = o;
_hints = hints;
// create task dictionary
self.taskDict = [NSMutableDictionary dictionary];
// create download session
NSURLSessionConfiguration *defaultConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
self.downloadSession = [NSURLSession sessionWithConfiguration:defaultConfig delegate:self delegateQueue:[NSOperationQueue mainQueue]];
// self.downloadSession.sessionDescription = kCurrentSession;
return self;
}
-(const cocos2d::network::DownloaderHints&)getHints
{
return _hints;
}
-(NSURLSessionDataTask *)createDataTask:(std::shared_ptr<const cocos2d::network::DownloadTask>&) task
{
const char *urlStr = task->requestURL.c_str();
DLLOG("DownloaderAppleImpl createDataTask: %s", urlStr);
NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:urlStr]];
NSURLRequest *request = nil;
if (_hints.timeoutInSeconds > 0)
{
request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:(NSTimeInterval)_hints.timeoutInSeconds];
}
else
{
request = [NSURLRequest requestWithURL:url];
}
NSURLSessionDataTask *ocTask = [self.downloadSession dataTaskWithRequest:request];
DownloadTaskWrapper* taskWrapper = [[DownloadTaskWrapper alloc] init:task];
[self.taskDict setObject:taskWrapper forKey:ocTask];
[taskWrapper release];
if (_taskQueue.size() < _hints.countOfMaxProcessingTasks) {
[ocTask resume];
_taskQueue.push(nil);
} else {
_taskQueue.push(ocTask);
}
return ocTask;
};
-(NSURLSessionDownloadTask *)createFileTask:(std::shared_ptr<const cocos2d::network::DownloadTask>&) task
{
const char *urlStr = task->requestURL.c_str();
DLLOG("DownloaderAppleImpl createFileTask: %s", urlStr);
NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:urlStr]];
NSMutableURLRequest *request = nil;
if (_hints.timeoutInSeconds > 0)
{
request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:(NSTimeInterval)_hints.timeoutInSeconds];
}
else
{
request = [NSMutableURLRequest requestWithURL:url];
}
for (auto it = task->header.begin(); it != task->header.end(); ++it) {
NSString *keyStr = [NSString stringWithUTF8String:it->first.c_str()];
NSString *valueStr = [NSString stringWithUTF8String:it->second.c_str()];
[request setValue:valueStr forHTTPHeaderField:keyStr];
}
NSString *tempFilePath = [NSString stringWithFormat:@"%s%s", task->storagePath.c_str(), _hints.tempFileNameSuffix.c_str()];
NSData *resumeData = [NSData dataWithContentsOfFile:tempFilePath];
NSURLSessionDownloadTask *ocTask = nil;
if (resumeData && task->header.size() <= 0)
{
ocTask = [self.downloadSession downloadTaskWithResumeData:resumeData];
}
else
{
ocTask = [self.downloadSession downloadTaskWithRequest:request];
}
DownloadTaskWrapper* taskWrapper = [[DownloadTaskWrapper alloc] init:task];
[self.taskDict setObject:taskWrapper forKey:ocTask];
[taskWrapper release];
if (_taskQueue.size() < _hints.countOfMaxProcessingTasks) {
[ocTask resume];
_taskQueue.push(nil);
} else {
_taskQueue.push(ocTask);
}
return ocTask;
};
-(void)abort:(NSURLSessionTask *)task
{
// cancel all download task
NSEnumerator * enumeratorKey = [self.taskDict keyEnumerator];
for (NSURLSessionTask *taskKey in enumeratorKey)
{
if (task != taskKey) {
continue;
}
DownloadTaskWrapper *wrapper = [self.taskDict objectForKey:taskKey];
// no resume support for a data task
std::string storagePath = [wrapper get]->storagePath;
if(storagePath.length() == 0) {
[taskKey cancel];
}
else {
[(NSURLSessionDownloadTask *)taskKey cancelByProducingResumeData:^(NSData *resumeData) {
NSString *tempFilePath = [NSString stringWithFormat:@"%s%s", storagePath.c_str(), _hints.tempFileNameSuffix.c_str()];
[self saveResumeData: resumeData templFile:tempFilePath];
}];
}
break;
}
}
-(void)doDestroy
{
// cancel all download task
NSEnumerator * enumeratorKey = [self.taskDict keyEnumerator];
for (NSURLSessionDownloadTask *task in enumeratorKey)
{
DownloadTaskWrapper *wrapper = [self.taskDict objectForKey:task];
// no resume support for a data task
std::string storagePath = [wrapper get]->storagePath;
if(storagePath.length() == 0) {
[task cancel];
}
else {
[task cancelByProducingResumeData:^(NSData *resumeData) {
NSString *tempFilePath = [NSString stringWithFormat:@"%s%s", storagePath.c_str(), _hints.tempFileNameSuffix.c_str()];
[self saveResumeData: resumeData templFile:tempFilePath];
}];
}
}
_outer = nullptr;
while(!_taskQueue.empty())
_taskQueue.pop();
[self.downloadSession invalidateAndCancel];
[self release];
}
-(void)dealloc
{
DLLOG("Destruct DownloaderAppleImpl %p", self);
self.downloadSession = nil;
self.taskDict = nil;
[super dealloc];
}
-(void)saveResumeData:(NSData*)resumeData
templFile:(NSString *)templFile
{
if (!resumeData)
{
return;
}
NSString *tempFileDir = [templFile stringByDeletingLastPathComponent];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isDir = false;
if ([fileManager fileExistsAtPath:tempFileDir
isDirectory:&isDir])
{
if (NO == isDir)
{
// REFINE: the directory is a file, not a directory, how to echo to developer?
DLLOG("DownloaderAppleImpl temp dir is a file!");
return;
}
}
else
{
NSURL *tempFileURL = [NSURL fileURLWithPath:tempFileDir];
if (NO == [fileManager createDirectoryAtURL:tempFileURL
withIntermediateDirectories:YES
attributes:nil
error:nil])
{
// create directory failed
DLLOG("DownloaderAppleImpl create temp dir failed");
return;
}
}
[resumeData writeToFile:templFile atomically:YES];
}
#pragma mark - NSURLSessionTaskDelegate methods
//@optional
/* An HTTP request is attempting to perform a redirection to a different
* URL. You must invoke the completion routine to allow the
* redirection, allow the redirection with a modified request, or
* pass nil to the completionHandler to cause the body of the redirection
* response to be delivered as the payload of this request. The default
* is to follow redirections.
*
* For tasks in background sessions, redirections will always be followed and this method will not be called.
*/
//- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
//willPerformHTTPRedirection:(NSHTTPURLResponse *)response
// newRequest:(NSURLRequest *)request
// completionHandler:(void (^)(NSURLRequest *))completionHandler;
/* The task has received a request specific authentication challenge.
* If this delegate is not implemented, the session specific authentication challenge
* will *NOT* be called and the behavior will be the same as using the default handling
* disposition.
*/
//- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
//didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
// completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;
/* Sent if a task requires a new, unopened body stream. This may be
* necessary when authentication has failed for any request that
* involves a body stream.
*/
//- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
// needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler;
/* Sent periodically to notify the delegate of upload progress. This
* information is also available as properties of the task.
*/
//- (void)URLSession:(NSURLSession *)session task :(NSURLSessionTask *)task
// didSendBodyData:(int64_t)bytesSent
// totalBytesSent:(int64_t)totalBytesSent
// totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;
/* Sent as the last message related to a specific task. Error may be
* nil, which implies that no error occurred and this task is complete.
*/
- (void)URLSession:(NSURLSession *)session task :(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
DLLOG("DownloaderAppleImpl task: \"%s\" didCompleteWithError: %d errDesc: %s"
, [task.originalRequest.URL.absoluteString cStringUsingEncoding:NSUTF8StringEncoding]
, (error ? (int)error.code: 0)
, [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding]);
// clean wrapper C++ object
DownloadTaskWrapper *wrapper = [self.taskDict objectForKey:task];
if(_outer)
{
if(error)
{
int errorCode = (int)error.code;
NSString *errorMsg = error.localizedDescription;
if (error.code == NSURLErrorCancelled) {
//cancel
errorCode = cocos2d::network::DownloadTask::ERROR_ABORT;
errorMsg = @"downloadFile:fail abort";
}
NSString *tempFilePath = [NSString stringWithFormat:@"%s%s", [wrapper get]->storagePath.c_str(), _hints.tempFileNameSuffix.c_str()];
if (error.code == 2 && [wrapper get]->storagePath.length() > 0) {
// fix Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager removeItemAtURL:[NSURL fileURLWithPath:tempFilePath] error:NULL];
[wrapper get]->storagePath = "";
}
else {
[self saveResumeData: error.userInfo[NSURLSessionDownloadTaskResumeData] templFile:tempFilePath];
}
std::vector<unsigned char> buf; // just a placeholder
_outer->onTaskFinish(*[wrapper get],
cocos2d::network::DownloadTask::ERROR_IMPL_INTERNAL,
errorCode,
[errorMsg cStringUsingEncoding:NSUTF8StringEncoding],
buf);
}
else if (![wrapper get]->storagePath.length())
{
// call onTaskFinish for a data task
// (for a file download task, callback is called in didFinishDownloadingToURL)
std::string errorString;
const int64_t buflen = [wrapper totalBytesReceived];
std::vector<unsigned char> data((size_t)buflen);
char* buf = (char*)data.data();
[wrapper transferDataToBuffer:buf lengthOfBuffer:buflen];
_outer->onTaskFinish(*[wrapper get],
cocos2d::network::DownloadTask::ERROR_NO_ERROR,
0,
errorString,
data);
}
else
{
NSInteger statusCode = ((NSHTTPURLResponse*)task.response).statusCode;
// Check for error status code
if (statusCode >= 400)
{
std::vector<unsigned char> buf; // just a placeholder
const char *originalURL = [task.originalRequest.URL.absoluteString cStringUsingEncoding:NSUTF8StringEncoding];
std::string errorMessage = cocos2d::StringUtils::format("Downloader: Failed to download %s with status code (%d)", originalURL, (int)statusCode);
_outer->onTaskFinish(*[wrapper get],
cocos2d::network::DownloadTask::ERROR_IMPL_INTERNAL,
0,
errorMessage,
buf);
}
}
}
[self.taskDict removeObjectForKey:task];
while (!_taskQueue.empty() && _taskQueue.front() == nil) {
_taskQueue.pop();
}
if (!_taskQueue.empty()) {
[_taskQueue.front() resume];
_taskQueue.pop();
}
}
#pragma mark - NSURLSessionDataDelegate methods
//@optional
/* The task has received a response and no further messages will be
* received until the completion block is called. The disposition
* allows you to cancel a request or to turn a data task into a
* download task. This delegate message is optional - if you do not
* implement it, you can get the response as a property of the task.
*
* This method will not be called for background upload tasks (which cannot be converted to download tasks).
*/
//- (void)URLSession:(NSURLSession *)session dataTask :(NSURLSessionDataTask *)dataTask
// didReceiveResponse:(NSURLResponse *)response
// completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
//{
// DLLOG("DownloaderAppleImpl dataTask: response:%s", [response.description cStringUsingEncoding:NSUTF8StringEncoding]);
// completionHandler(NSURLSessionResponseAllow);
//}
/* Notification that a data task has become a download task. No
* future messages will be sent to the data task.
*/
//- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
//didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask;
/* Sent when data is available for the delegate to consume. It is
* assumed that the delegate will retain and not copy the data. As
* the data may be discontiguous, you should use
* [NSData enumerateByteRangesUsingBlock:] to access it.
*/
- (void)URLSession:(NSURLSession *)session dataTask :(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
DLLOG("DownloaderAppleImpl dataTask: \"%s\" didReceiveDataLen %d",
[dataTask.originalRequest.URL.absoluteString cStringUsingEncoding:NSUTF8StringEncoding],
(int)data.length);
if (nullptr == _outer)
{
return;
}
DownloadTaskWrapper *wrapper = [self.taskDict objectForKey:dataTask];
[wrapper addData:data];
std::function<int64_t(void *, int64_t)> transferDataToBuffer =
[wrapper](void *buffer, int64_t bufLen)->int64_t
{
return [wrapper transferDataToBuffer:buffer lengthOfBuffer: bufLen];
};
_outer->onTaskProgress(*[wrapper get],
wrapper.bytesReceived,
wrapper.totalBytesReceived,
dataTask.countOfBytesExpectedToReceive,
transferDataToBuffer);
}
/* Invoke the completion routine with a valid NSCachedURLResponse to
* allow the resulting data to be cached, or pass nil to prevent
* caching. Note that there is no guarantee that caching will be
* attempted for a given resource, and you should not rely on this
* message to receive the resource data.
*/
//- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
// willCacheResponse:(NSCachedURLResponse *)proposedResponse
// completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler;
#pragma mark - NSURLSessionDownloadDelegate methods
/* Sent when a download task that has completed a download. The delegate should
* copy or move the file at the given location to a new location as it will be
* removed when the delegate message returns. URLSession:task:didCompleteWithError: will
* still be called.
*/
- (void)URLSession:(NSURLSession *)session downloadTask :(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
DLLOG("DownloaderAppleImpl downloadTask: \"%s\" didFinishDownloadingToURL %s",
[downloadTask.originalRequest.URL.absoluteString cStringUsingEncoding:NSUTF8StringEncoding],
[location.absoluteString cStringUsingEncoding:NSUTF8StringEncoding]);
if (nullptr == _outer)
{
return;
}
// On iOS 9 a response with status code 4xx(Client Error) or 5xx(Server Error)
// might end up calling this delegate method, saving the error message to the storage path
// and treating this download task as a successful one, so we need to check the status code here
NSInteger statusCode = ((NSHTTPURLResponse*)downloadTask.response).statusCode;
if (statusCode >= 400)
{
return;
}
DownloadTaskWrapper *wrapper = [self.taskDict objectForKey:downloadTask];
const char * storagePath = [wrapper get]->storagePath.c_str();
NSString *destPath = [NSString stringWithUTF8String:storagePath];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *destURL = nil;
do
{
if ([destPath hasPrefix:@"file://"])
{
break;
}
if ('/' == [destPath characterAtIndex:0])
{
destURL = [NSURL fileURLWithPath:destPath];
break;
}
// relative path, store to user domain default
NSArray *URLs = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
NSURL *documentsDirectory = URLs[0];
destURL = [documentsDirectory URLByAppendingPathComponent:destPath];
} while (0);
// Make sure we overwrite anything that's already there
[fileManager removeItemAtURL:destURL error:NULL];
// copy file to dest location
int errorCode = cocos2d::network::DownloadTask::ERROR_NO_ERROR;
int errorCodeInternal = 0;
std::string errorString;
NSError *error = nil;
if ([fileManager copyItemAtURL:location toURL:destURL error:&error])
{
// success, remove temp file if it exist
if (_hints.tempFileNameSuffix.length())
{
NSString *tempStr = [[destURL absoluteString] stringByAppendingFormat:@"%s", _hints.tempFileNameSuffix.c_str()];
NSURL *tempDestUrl = [NSURL URLWithString:tempStr];
[fileManager removeItemAtURL:tempDestUrl error:NULL];
}
}
else
{
errorCode = cocos2d::network::DownloadTask::ERROR_FILE_OP_FAILED;
if (error)
{
errorCodeInternal = (int)error.code;
errorString = [error.localizedDescription cStringUsingEncoding:NSUTF8StringEncoding];
}
}
std::vector<unsigned char> buf; // just a placeholder
_outer->onTaskFinish(*[wrapper get], errorCode, errorCodeInternal, errorString, buf);
}
// @optional
/* Sent periodically to notify the delegate of download progress. */
- (void)URLSession:(NSURLSession *)session downloadTask :(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
// NSLog(@"DownloaderAppleImpl downloadTask: \"%@\" received: %lld total: %lld", downloadTask.originalRequest.URL, totalBytesWritten, totalBytesExpectedToWrite);
if (nullptr == _outer)
{
return;
}
DownloadTaskWrapper *wrapper = [self.taskDict objectForKey:downloadTask];
std::function<int64_t(void *, int64_t)> transferDataToBuffer; // just a placeholder
_outer->onTaskProgress(*[wrapper get], bytesWritten, totalBytesWritten, totalBytesExpectedToWrite, transferDataToBuffer);
}
/* Sent when a download has been resumed. If a download failed with an
* error, the -userInfo dictionary of the error will contain an
* NSURLSessionDownloadTaskResumeData key, whose value is the resume
* data.
*/
- (void)URLSession:(NSURLSession *)session downloadTask :(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
// NSLog(@"[REFINE]DownloaderAppleImpl downloadTask: \"%@\" didResumeAtOffset: %lld", downloadTask.originalRequest.URL, fileOffset);
// 下载失败
// self.downloadFail([self getDownloadRespose:XZDownloadFail identifier:self.identifier progress:0.00 downloadUrl:nil downloadSaveFileUrl:nil downloadData:nil downloadResult:@"下载失败"]);
}
@end

View File

@@ -0,0 +1,76 @@
/****************************************************************************
Copyright (c) 2015-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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.
****************************************************************************/
#pragma once
#include <string>
#include <unordered_map>
#include <memory>
#include <vector>
#include <functional>
#include "base/ccMacros.h"
//#define CC_DOWNLOADER_DEBUG
#ifdef CC_DOWNLOADER_DEBUG
#define DLLOG(format, ...) cocos2d::log(format, ##__VA_ARGS__)
#else
#define DLLOG(...) do {} while (0)
#endif
namespace cocos2d { namespace network
{
class DownloadTask;
class CC_DLL IDownloadTask
{
public:
virtual ~IDownloadTask(){}
};
class IDownloaderImpl
{
public:
virtual ~IDownloaderImpl(){}
std::function<void(const DownloadTask& task,
int64_t bytesReceived,
int64_t totalBytesReceived,
int64_t totalBytesExpected,
std::function<int64_t(void *buffer, int64_t len)>& transferDataToBuffer)> onTaskProgress;
std::function<void(const DownloadTask& task,
int errorCode,
int errorCodeInternal,
const std::string& errorStr,
std::vector<unsigned char>& data)> onTaskFinish;
virtual IDownloadTask *createCoTask(std::shared_ptr<const DownloadTask>& task) = 0;
virtual void abort(const std::unique_ptr<IDownloadTask>& task) = 0;
};
}} // namespace cocos2d::network

View File

@@ -0,0 +1,72 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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.
****************************************************************************/
#ifndef __HTTPASYNCONNECTION_H__
#define __HTTPASYNCONNECTION_H__
/// @cond DO_NOT_SHOW
#include "platform/CCPlatformConfig.h"
#if (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
#import <Foundation/Foundation.h>
#import <Security/Security.h>
/// @cond
@interface HttpAsynConnection : NSObject <NSURLConnectionDelegate, NSURLConnectionDataDelegate>
{
}
// The original URL to download. Due to redirects the actual content may come from another URL
@property (strong) NSString *srcURL;
@property (strong) NSString *sslFile;
@property (copy) NSDictionary *responseHeader;
@property (strong) NSMutableData *responseData;
@property (readonly) NSInteger getDataTime;
@property (readonly) NSInteger responseCode;
@property (readonly) NSString *statusString;
@property (strong) NSError *responseError;
@property (strong) NSError *connError;
@property (strong) NSURLConnection *conn;
@property bool finish;
@property (strong) NSRunLoop *runLoop;
// instructs the class to start the request.
-(void) startRequest:(NSURLRequest*)request;
@end
#endif // #if (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
/// @endcond
#endif //__HTTPASYNCONNECTION_H__

View File

@@ -0,0 +1,230 @@
/****************************************************************************
Copyright (c) 2013-2017 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 "platform/CCPlatformConfig.h"
#if (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
#import "network/HttpAsynConnection-apple.h"
@interface HttpAsynConnection ()
@property (readwrite) NSString *statusString;
@end
@implementation HttpAsynConnection
@synthesize srcURL = srcURL;
@synthesize sslFile = sslFile;
@synthesize responseHeader = responseHeader;
@synthesize responseData = responseData;
@synthesize getDataTime = getDataTime;
@synthesize responseCode = responseCode;
@synthesize statusString = statusString;
@synthesize responseError = responseError;
@synthesize connError = connError;
@synthesize conn = conn;
@synthesize finish = finish;
@synthesize runLoop = runLoop;
- (void)dealloc
{
[srcURL release];
[sslFile release];
[responseHeader release];
[responseData release];
[responseError release];
[conn release];
[runLoop release];
[connError release];
[super dealloc];
}
- (void) startRequest:(NSURLRequest *)request
{
#ifdef COCOS2D_DEBUG
// NSLog(@"Starting to load %@", srcURL);
#endif
finish = false;
self.responseData = [NSMutableData data];
getDataTime = 0;
self.responseError = nil;
self.connError = nil;
// create the connection with the target request and this class as the delegate
self.conn = [[[NSURLConnection alloc] initWithRequest:request
delegate:self
startImmediately:NO] autorelease];
[self.conn scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
// start the connection
[self.conn start];
}
#pragma mark NSURLConnectionDelegate methods
/**
* This delegate method is called when the NSURLConnection connects to the server. It contains the
* NSURLResponse object with the headers returned by the server. This method may be called multiple times.
* Therefore, it is important to reset the data on each call. Do not assume that it is the first call
* of this method.
**/
- (void) connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response {
#ifdef COCOS2D_DEBUG
// NSLog(@"Received response from request to url %@", srcURL);
#endif
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
//NSLog(@"All headers = %@", [httpResponse allHeaderFields]);
self.responseHeader = [httpResponse allHeaderFields];
responseCode = httpResponse.statusCode;
self.statusString = [NSHTTPURLResponse localizedStringForStatusCode:responseCode];
if(responseCode == 200)
self.statusString = @"OK";
/*The individual values of the numeric status codes defined for HTTP/1.1
| "200" ; OK
| "201" ; Created
| "202" ; Accepted
| "203" ; Non-Authoritative Information
| "204" ; No Content
| "205" ; Reset Content
| "206" ; Partial Content
*/
if (responseCode < 200 || responseCode >= 300)
{// something went wrong, abort the whole thing
self.responseError = [NSError errorWithDomain:@"CCBackendDomain"
code:responseCode
userInfo:@{NSLocalizedDescriptionKey: @"Bad HTTP Response Code"}];
}
[responseData setLength:0];
}
/**
* This delegate method is called for each chunk of data received from the server. The chunk size
* is dependent on the network type and the server configuration.
*/
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data
{
//NSLog(@"get some data");
[responseData appendData:data];
getDataTime++;
}
/**
* This delegate method is called if the connection cannot be established to the server.
* The error object will have a description of the error
**/
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
//NSLog(@"Load failed with error %@", [error localizedDescription]);
self.connError = error;
finish = true;
}
/**
* This delegate method is called when the data load is complete. The delegate will be released
* following this call
**/
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
finish = true;
}
//Server evaluates client's certificate
- (BOOL) shouldTrustProtectionSpace:(NSURLProtectionSpace*)protectionSpace
{
if(sslFile == nil)
return YES;
//load the bundle client certificate
NSString *certPath = [[NSBundle mainBundle] pathForResource:sslFile ofType:@"der"];
NSData *certData = [[NSData alloc] initWithContentsOfFile:certPath];
CFDataRef certDataRef = (CFDataRef)certData;
SecCertificateRef cert = SecCertificateCreateWithData(NULL, certDataRef);
//Establish a chain of trust anchored on our bundled certificate
CFArrayRef certArrayRef = CFArrayCreate(NULL, (void*)&cert, 1, NULL);
SecTrustRef serverTrust = protectionSpace.serverTrust;
SecTrustSetAnchorCertificates(serverTrust, certArrayRef);
//Verify that trust
SecTrustResultType trustResult;
SecTrustEvaluate(serverTrust, &trustResult);
if(trustResult == kSecTrustResultRecoverableTrustFailure)
{
CFDataRef errDataRef = SecTrustCopyExceptions(serverTrust);
SecTrustSetExceptions(serverTrust, errDataRef);
SecTrustEvaluate(serverTrust, &trustResult);
CFRelease(errDataRef);
}
[certData release];
if (cert)
{
CFRelease(cert);
}
if (certArrayRef)
{
CFRelease(certArrayRef);
}
//Did our custom trust chain evaluate successfully?
return trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed;
}
- (void) connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
id <NSURLAuthenticationChallengeSender> sender = challenge.sender;
NSURLProtectionSpace *protectionSpace = challenge.protectionSpace;
//Should server trust client?
if([self shouldTrustProtectionSpace:protectionSpace])
{
SecTrustRef trust = [protectionSpace serverTrust];
//
// SecCertificateRef certificate = SecTrustGetCertificateAtIndex(trust, 0);
//
// NSData *serverCertificateData = (NSData*)SecCertificateCopyData(certificate);
// NSString *serverCertificateDataHash = [[serverCertificateData base64EncodedString] ]
NSURLCredential *credential = [NSURLCredential credentialForTrust:trust];
[sender useCredential:credential forAuthenticationChallenge:challenge];
}
else
{
[sender cancelAuthenticationChallenge:challenge];
}
}
@end
#endif // #if (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,593 @@
/****************************************************************************
Copyright (c) 2012 greathqy
Copyright (c) 2012 cocos2d-x.org
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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 "network/HttpClient.h"
#include <queue>
#include <errno.h>
#import "network/HttpAsynConnection-apple.h"
#include "network/HttpCookie.h"
#include "platform/CCFileUtils.h"
#include "platform/CCApplication.h"
NS_CC_BEGIN
namespace network {
static HttpClient *_httpClient = nullptr; // pointer to singleton
static int processTask(HttpClient* client, HttpRequest *request, NSString *requestType, void *stream, long *errorCode, void *headerStream, char *errorBuffer);
// Worker thread
void HttpClient::networkThread()
{
increaseThreadCount();
while (true) @autoreleasepool {
HttpRequest *request;
// step 1: send http request if the requestQueue isn't empty
{
std::lock_guard<std::mutex> lock(_requestQueueMutex);
while (_requestQueue.empty()) {
_sleepCondition.wait(_requestQueueMutex);
}
request = _requestQueue.at(0);
_requestQueue.erase(0);
}
if (request == _requestSentinel) {
break;
}
// Create a HttpResponse object, the default setting is http access failed
HttpResponse *response = new (std::nothrow) HttpResponse(request);
processResponse(response, _responseMessage);
// add response packet into queue
_responseQueueMutex.lock();
_responseQueue.pushBack(response);
_responseQueueMutex.unlock();
_schedulerMutex.lock();
if (auto sche = _scheduler.lock())
{
sche->performFunctionInCocosThread(CC_CALLBACK_0(HttpClient::dispatchResponseCallbacks, this));
}
_schedulerMutex.unlock();
}
// cleanup: if worker thread received quit signal, clean up un-completed request queue
_requestQueueMutex.lock();
_requestQueue.clear();
_requestQueueMutex.unlock();
_responseQueueMutex.lock();
_responseQueue.clear();
_responseQueueMutex.unlock();
decreaseThreadCountAndMayDeleteThis();
}
// Worker thread
void HttpClient::networkThreadAlone(HttpRequest* request, HttpResponse* response)
{
increaseThreadCount();
char responseMessage[RESPONSE_BUFFER_SIZE] = { 0 };
processResponse(response, responseMessage);
_schedulerMutex.lock();
if (auto sche = _scheduler.lock())
{
sche->performFunctionInCocosThread([this, response, request]{
const ccHttpRequestCallback& callback = request->getResponseCallback();
if (callback != nullptr)
{
callback(this, response);
}
response->release();
// do not release in other thread
request->release();
});
}
_schedulerMutex.unlock();
decreaseThreadCountAndMayDeleteThis();
}
//Process Request
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];
NSMutableURLRequest *nsrequest = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval:request->getTimeout()];
//set request type
[nsrequest setHTTPMethod:requestType];
/* get custom header data (if set) */
std::vector<std::string> headers=request->getHeaders();
if(!headers.empty())
{
/* append custom headers one by one */
for (auto& header : headers)
{
unsigned long i = header.find(':', 0);
unsigned long length = header.size();
std::string field = header.substr(0, i);
std::string value = header.substr(i+1, length-i);
NSString *headerField = [NSString stringWithUTF8String:field.c_str()];
NSString *headerValue = [NSString stringWithUTF8String:value.c_str()];
[nsrequest setValue:headerValue forHTTPHeaderField:headerField];
}
}
//if request type is post or put,set header and data
if([requestType isEqual: @"POST"] || [requestType isEqual: @"PUT"])
{
char* requestDataBuffer = request->getRequestData();
if (nullptr != requestDataBuffer && 0 != request->getRequestDataSize())
{
NSData *postData = [NSData dataWithBytes:requestDataBuffer length:request->getRequestDataSize()];
[nsrequest setHTTPBody:postData];
}
}
//read cookie properties from file and set cookie
std::string cookieFilename = client->getCookieFilename();
if(!cookieFilename.empty() && nullptr != client->getCookie())
{
const CookiesInfo* cookieInfo = client->getCookie()->getMatchCookie(request->getUrl());
if(cookieInfo != nullptr)
{
NSString *domain = [NSString stringWithCString:cookieInfo->domain.c_str() encoding:[NSString defaultCStringEncoding]];
NSString *path = [NSString stringWithCString:cookieInfo->path.c_str() encoding:[NSString defaultCStringEncoding]];
NSString *value = [NSString stringWithCString:cookieInfo->value.c_str() encoding:[NSString defaultCStringEncoding]];
NSString *name = [NSString stringWithCString:cookieInfo->name.c_str() encoding:[NSString defaultCStringEncoding]];
// create the properties for a cookie
NSDictionary *properties = [NSDictionary dictionaryWithObjectsAndKeys: name,NSHTTPCookieName,
value, NSHTTPCookieValue, path, NSHTTPCookiePath,
domain, NSHTTPCookieDomain,
nil];
// create the cookie from the properties
NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:properties];
// add the cookie to the cookie storage
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
}
}
HttpAsynConnection *httpAsynConn = [[HttpAsynConnection new] autorelease];
httpAsynConn.srcURL = urlstring;
httpAsynConn.sslFile = nil;
std::string sslCaFileName = client->getSSLVerification();
if(!sslCaFileName.empty())
{
long len = sslCaFileName.length();
long pos = sslCaFileName.rfind('.', len-1);
httpAsynConn.sslFile = [NSString stringWithUTF8String:sslCaFileName.substr(0, pos).c_str()];
}
[httpAsynConn startRequest:nsrequest];
while( httpAsynConn.finish != true)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
//if http connection return error
if (httpAsynConn.connError != nil)
{
NSString* errorString = [httpAsynConn.connError localizedDescription];
strcpy(errorBuffer, [errorString UTF8String]);
return 0;
}
//if http response got error, just log the error
if (httpAsynConn.responseError != nil)
{
NSString* errorString = [httpAsynConn.responseError localizedDescription];
strcpy(errorBuffer, [errorString UTF8String]);
}
*responseCode = httpAsynConn.responseCode;
//add cookie to cookies vector
if(!cookieFilename.empty())
{
NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:httpAsynConn.responseHeader forURL:url];
for (NSHTTPCookie *cookie in cookies)
{
//NSLog(@"Cookie: %@", cookie);
NSString *domain = cookie.domain;
//BOOL session = cookie.sessionOnly;
NSString *path = cookie.path;
BOOL secure = cookie.isSecure;
NSDate *date = cookie.expiresDate;
NSString *name = cookie.name;
NSString *value = cookie.value;
CookiesInfo cookieInfo;
cookieInfo.domain = [domain cStringUsingEncoding: NSUTF8StringEncoding];
cookieInfo.path = [path cStringUsingEncoding: NSUTF8StringEncoding];
cookieInfo.secure = (secure == YES) ? true : false;
cookieInfo.expires = [[NSString stringWithFormat:@"%ld", (long)[date timeIntervalSince1970]] cStringUsingEncoding: NSUTF8StringEncoding];
cookieInfo.name = [name cStringUsingEncoding: NSUTF8StringEncoding];
cookieInfo.value = [value cStringUsingEncoding: NSUTF8StringEncoding];
cookieInfo.tailmatch = true;
client->getCookie()->updateOrAddCookie(&cookieInfo);
}
}
//handle response header
NSMutableString *header = [NSMutableString string];
[header appendFormat:@"HTTP/1.1 %ld %@\n", (long)httpAsynConn.responseCode, httpAsynConn.statusString];
for (id key in httpAsynConn.responseHeader)
{
[header appendFormat:@"%@: %@\n", key, [httpAsynConn.responseHeader objectForKey:key]];
}
if (header.length > 0)
{
NSRange range = NSMakeRange(header.length-1, 1);
[header deleteCharactersInRange:range];
}
NSData *headerData = [header dataUsingEncoding:NSUTF8StringEncoding];
std::vector<char> *headerBuffer = (std::vector<char>*)headerStream;
const void* headerptr = [headerData bytes];
long headerlen = [headerData length];
headerBuffer->insert(headerBuffer->end(), (char*)headerptr, (char*)headerptr+headerlen);
//handle response data
std::vector<char> *recvBuffer = (std::vector<char>*)stream;
const void* ptr = [httpAsynConn.responseData bytes];
long len = [httpAsynConn.responseData length];
recvBuffer->insert(recvBuffer->end(), (char*)ptr, (char*)ptr+len);
return 1;
}
// HttpClient implementation
HttpClient* HttpClient::getInstance()
{
if (_httpClient == nullptr)
{
_httpClient = new (std::nothrow) HttpClient();
}
return _httpClient;
}
void HttpClient::destroyInstance()
{
if (nullptr == _httpClient)
{
CCLOG("HttpClient singleton is nullptr");
return;
}
CCLOG("HttpClient::destroyInstance begin");
auto thiz = _httpClient;
_httpClient = nullptr;
if(auto sche = thiz->_scheduler.lock())
{
sche->unscheduleAllForTarget(thiz);
}
thiz->_schedulerMutex.lock();
thiz->_scheduler.reset();
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)
{
std::lock_guard<std::mutex> lock(_sslCaFileMutex);
_sslCaFilename = caFile;
}
HttpClient::HttpClient()
: _isInited(false)
, _timeoutForConnect(30)
, _timeoutForRead(60)
, _threadCount(0)
, _cookie(nullptr)
, _requestSentinel(new HttpRequest())
{
CCLOG("In the constructor of HttpClient!");
memset(_responseMessage, 0, sizeof(char) * RESPONSE_BUFFER_SIZE);
_scheduler = Application::getInstance()->getScheduler();
increaseThreadCount();
}
HttpClient::~HttpClient()
{
CC_SAFE_RELEASE(_requestSentinel);
if (!_cookieFilename.empty() && nullptr != _cookie)
{
_cookie->writeFile();
CC_SAFE_DELETE(_cookie);
}
CCLOG("HttpClient destructor");
}
//Lazy create semaphore & mutex & thread
bool HttpClient::lazyInitThreadSemaphore()
{
if (_isInited)
{
return true;
}
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 == lazyInitThreadSemaphore())
{
return;
}
if (!request)
{
return;
}
request->retain();
_requestQueueMutex.lock();
_requestQueue.pushBack(request);
_requestQueueMutex.unlock();
// Notify thread start to work
_sleepCondition.notify_one();
}
void HttpClient::sendImmediate(HttpRequest* request)
{
if(!request)
{
return;
}
request->retain();
// Create a HttpResponse object, the default setting is http access failed
HttpResponse *response = new (std::nothrow) HttpResponse(request);
auto t = std::thread(&HttpClient::networkThreadAlone, this, request, response);
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
HttpResponse* response = nullptr;
_responseQueueMutex.lock();
if (!_responseQueue.empty())
{
response = _responseQueue.at(0);
_responseQueue.erase(0);
}
_responseQueueMutex.unlock();
if (response)
{
HttpRequest *request = response->getHttpRequest();
const ccHttpRequestCallback& callback = request->getResponseCallback();
if (callback != nullptr)
{
callback(this, response);
}
response->release();
// do not release in other thread
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::HEAD:
requestType = @"HEAD";
break;
case HttpRequest::Type::DELETE:
requestType = @"DELETE";
break;
default:
CCASSERT(false, "CCHttpClient: unknown request type, only GET,POST,PUT or DELETE is 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<std::mutex> lock(_timeoutForConnectMutex);
_timeoutForConnect = value;
}
int HttpClient::getTimeoutForConnect()
{
std::lock_guard<std::mutex> lock(_timeoutForConnectMutex);
return _timeoutForConnect;
}
void HttpClient::setTimeoutForRead(int value)
{
std::lock_guard<std::mutex> lock(_timeoutForReadMutex);
_timeoutForRead = value;
}
int HttpClient::getTimeoutForRead()
{
std::lock_guard<std::mutex> lock(_timeoutForReadMutex);
return _timeoutForRead;
}
const std::string& HttpClient::getCookieFilename()
{
std::lock_guard<std::mutex> lock(_cookieFileMutex);
return _cookieFilename;
}
const std::string& HttpClient::getSSLVerification()
{
std::lock_guard<std::mutex> lock(_sslCaFileMutex);
return _sslCaFilename;
}
}
NS_CC_END

View File

@@ -0,0 +1,656 @@
/****************************************************************************
Copyright (c) 2012 greathqy
Copyright (c) 2012 cocos2d-x.org
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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 "network/HttpClient.h"
#include <queue>
#include <errno.h>
#include <curl/curl.h>
#include "platform/CCFileUtils.h"
#include "platform/CCApplication.h"
NS_CC_BEGIN
namespace network {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
typedef int int32_t;
#endif
static HttpClient* _httpClient = nullptr; // pointer to singleton
typedef size_t (*write_callback)(void *ptr, size_t size, size_t nmemb, void *stream);
// Callback function used by libcurl for collect response data
static size_t writeData(void *ptr, size_t size, size_t nmemb, void *stream)
{
std::vector<char> *recvBuffer = (std::vector<char>*)stream;
size_t sizes = size * nmemb;
// add data to the end of recvBuffer
// write data maybe called more than once in a single request
recvBuffer->insert(recvBuffer->end(), (char*)ptr, (char*)ptr+sizes);
return sizes;
}
// Callback function used by libcurl for collect header data
static size_t writeHeaderData(void *ptr, size_t size, size_t nmemb, void *stream)
{
std::vector<char> *recvBuffer = (std::vector<char>*)stream;
size_t sizes = size * nmemb;
// add data to the end of recvBuffer
// write data maybe called more than once in a single request
recvBuffer->insert(recvBuffer->end(), (char*)ptr, (char*)ptr+sizes);
return sizes;
}
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);
// Worker thread
void HttpClient::networkThread()
{
increaseThreadCount();
while (true)
{
HttpRequest *request;
// step 1: send http request if the requestQueue isn't empty
{
std::lock_guard<std::mutex> lock(_requestQueueMutex);
while (_requestQueue.empty())
{
_sleepCondition.wait(_requestQueueMutex);
}
request = _requestQueue.at(0);
_requestQueue.erase(0);
}
if (request == _requestSentinel) {
break;
}
// step 2: libcurl sync access
// Create a HttpResponse object, the default setting is http access failed
HttpResponse *response = new (std::nothrow) HttpResponse(request);
processResponse(response, _responseMessage);
// add response packet into queue
_responseQueueMutex.lock();
_responseQueue.pushBack(response);
_responseQueueMutex.unlock();
_schedulerMutex.lock();
if (auto sche = _scheduler.lock())
{
sche->performFunctionInCocosThread(CC_CALLBACK_0(HttpClient::dispatchResponseCallbacks, this));
}
_schedulerMutex.unlock();
}
// cleanup: if worker thread received quit signal, clean up un-completed request queue
_requestQueueMutex.lock();
_requestQueue.clear();
_requestQueueMutex.unlock();
_responseQueueMutex.lock();
_responseQueue.clear();
_responseQueueMutex.unlock();
decreaseThreadCountAndMayDeleteThis();
}
// Worker thread
void HttpClient::networkThreadAlone(HttpRequest* request, HttpResponse* response)
{
increaseThreadCount();
char responseMessage[RESPONSE_BUFFER_SIZE] = { 0 };
processResponse(response, responseMessage);
_schedulerMutex.lock();
if (auto sche = _scheduler.lock())
{
sche->performFunctionInCocosThread([this, response, request]{
const ccHttpRequestCallback& callback = request->getResponseCallback();
if (callback != nullptr)
{
callback(this, response);
}
response->release();
// do not release in other thread
request->release();
});
}
_schedulerMutex.unlock();
decreaseThreadCountAndMayDeleteThis();
}
//Configure curl's timeout property
static bool configureCURL(HttpClient* client, HttpRequest* request, CURL* handle, char* errorBuffer)
{
if (!handle) {
return false;
}
int32_t code;
code = curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, errorBuffer);
if (code != CURLE_OK) {
return false;
}
code = curl_easy_setopt(handle, CURLOPT_TIMEOUT, request->getTimeout());
if (code != CURLE_OK) {
return false;
}
code = curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, request->getTimeout());
if (code != CURLE_OK) {
return false;
}
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, sslCaFilename.c_str());
}
// FIXED #3224: The subthread of CCHttpClient interrupts main thread if timeout comes.
// Document is here: http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTNOSIGNAL
curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "");
return true;
}
class CURLRaii
{
/// Instance of CURL
CURL *_curl;
/// Keeps custom header data
curl_slist *_headers;
public:
CURLRaii()
: _curl(curl_easy_init())
, _headers(nullptr)
{
}
~CURLRaii()
{
if (_curl)
curl_easy_cleanup(_curl);
/* free the linked list for header data */
if (_headers)
curl_slist_free_all(_headers);
}
template <class T>
bool setOption(CURLoption option, T data)
{
return CURLE_OK == curl_easy_setopt(_curl, option, data);
}
/**
* @brief Inits CURL instance for common usage
* @param request Null not allowed
* @param callback Response write callback
* @param stream Response write stream
*/
bool init(HttpClient* client, HttpRequest* request, write_callback callback, void* stream, write_callback headerCallback, void* headerStream, char* errorBuffer)
{
if (!_curl)
return false;
if (!configureCURL(client, request, _curl, errorBuffer))
return false;
/* get custom header data (if set) */
std::vector<std::string> headers=request->getHeaders();
if(!headers.empty())
{
/* append custom headers one by one */
for (auto& header : headers)
_headers = curl_slist_append(_headers,header.c_str());
/* set custom headers for curl */
if (!setOption(CURLOPT_HTTPHEADER, _headers))
return false;
}
std::string cookieFilename = client->getCookieFilename();
if (!cookieFilename.empty()) {
if (!setOption(CURLOPT_COOKIEFILE, cookieFilename.c_str())) {
return false;
}
if (!setOption(CURLOPT_COOKIEJAR, cookieFilename.c_str())) {
return false;
}
}
return setOption(CURLOPT_URL, request->getUrl())
&& setOption(CURLOPT_WRITEFUNCTION, callback)
&& setOption(CURLOPT_WRITEDATA, stream)
&& setOption(CURLOPT_HEADERFUNCTION, headerCallback)
&& setOption(CURLOPT_HEADERDATA, headerStream);
}
/// @param responseCode Null not allowed
bool perform(long *responseCode)
{
if (CURLE_OK != curl_easy_perform(_curl))
return false;
CURLcode code = curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, responseCode);
if (code != CURLE_OK || !(*responseCode >= 200 && *responseCode < 300)) {
CCLOGERROR("Curl curl_easy_getinfo failed: %s", curl_easy_strerror(code));
return false;
}
// Get some mor data.
return true;
}
};
//Process Get Request
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(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(HttpClient* client, HttpRequest* request, write_callback callback, void* stream, long* responseCode, write_callback headerCallback, void* headerStream, char* errorBuffer)
{
CURLRaii curl;
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())
&& curl.perform(responseCode);
return ok ? 0 : 1;
}
//Process PUT Request
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(client, request, callback, stream, headerCallback, headerStream, errorBuffer)
&& curl.setOption(CURLOPT_CUSTOMREQUEST, "PUT")
&& curl.setOption(CURLOPT_POSTFIELDS, request->getRequestData())
&& curl.setOption(CURLOPT_POSTFIELDSIZE, request->getRequestDataSize())
&& curl.perform(responseCode);
return ok ? 0 : 1;
}
static int processHeadTask(HttpClient* client, HttpRequest* request, write_callback callback, void* stream, long* responseCode, write_callback headerCallback, void* headerStream, char* errorBuffer)
{
CURLRaii curl;
bool ok = curl.init(client, request, callback, stream, headerCallback, headerStream, errorBuffer)
&& curl.setOption(CURLOPT_NOBODY, "HEAD")
&& curl.setOption(CURLOPT_POSTFIELDS, request->getRequestData())
&& curl.setOption(CURLOPT_POSTFIELDSIZE, request->getRequestDataSize())
&& curl.perform(responseCode);
return ok ? 0 : 1;
}
//Process DELETE Request
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(client, request, callback, stream, headerCallback, headerStream, errorBuffer)
&& curl.setOption(CURLOPT_CUSTOMREQUEST, "DELETE")
&& curl.setOption(CURLOPT_FOLLOWLOCATION, true)
&& curl.perform(responseCode);
return ok ? 0 : 1;
}
// HttpClient implementation
HttpClient* HttpClient::getInstance()
{
if (_httpClient == nullptr)
{
_httpClient = new (std::nothrow) HttpClient();
}
return _httpClient;
}
void HttpClient::destroyInstance()
{
if (nullptr == _httpClient)
{
CCLOG("HttpClient singleton is nullptr");
return;
}
CCLOG("HttpClient::destroyInstance begin");
auto thiz = _httpClient;
_httpClient = nullptr;
if(auto sche = thiz->_scheduler.lock())
{
sche->unscheduleAllForTarget(thiz);
}
thiz->_schedulerMutex.lock();
thiz->_scheduler.reset();
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)
{
std::lock_guard<std::mutex> lock(_cookieFileMutex);
if (cookieFile)
{
_cookieFilename = std::string(cookieFile);
}
else
{
_cookieFilename = (FileUtils::getInstance()->getWritablePath() + "cookieFile.txt");
}
}
void HttpClient::setSSLVerification(const std::string& caFile)
{
std::lock_guard<std::mutex> lock(_sslCaFileMutex);
_sslCaFilename = caFile;
}
HttpClient::HttpClient()
: _isInited(false)
, _timeoutForConnect(30)
, _timeoutForRead(60)
, _threadCount(0)
, _cookie(nullptr)
, _requestSentinel(new HttpRequest())
{
CCLOG("In the constructor of HttpClient!");
memset(_responseMessage, 0, RESPONSE_BUFFER_SIZE * sizeof(char));
_scheduler = Application::getInstance()->getScheduler();
increaseThreadCount();
}
HttpClient::~HttpClient()
{
CC_SAFE_RELEASE(_requestSentinel);
CCLOG("HttpClient destructor");
}
//Lazy create semaphore & mutex & thread
bool HttpClient::lazyInitThreadSemaphore()
{
if (_isInited)
{
return true;
}
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 == lazyInitThreadSemaphore())
{
return;
}
if (!request)
{
return;
}
request->retain();
_requestQueueMutex.lock();
_requestQueue.pushBack(request);
_requestQueueMutex.unlock();
// Notify thread start to work
_sleepCondition.notify_one();
}
void HttpClient::sendImmediate(HttpRequest* request)
{
if(!request)
{
return;
}
request->retain();
// Create a HttpResponse object, the default setting is http access failed
HttpResponse *response = new (std::nothrow) HttpResponse(request);
auto t = std::thread(&HttpClient::networkThreadAlone, this, request, response);
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
HttpResponse* response = nullptr;
_responseQueueMutex.lock();
if (!_responseQueue.empty())
{
response = _responseQueue.at(0);
_responseQueue.erase(0);
}
_responseQueueMutex.unlock();
if (response)
{
HttpRequest *request = response->getHttpRequest();
const ccHttpRequestCallback& callback = request->getResponseCallback();
if (callback != nullptr)
{
callback(this, response);
}
response->release();
// do not release in other thread
request->release();
}
}
// 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::HEAD:
retValue = processHeadTask(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(false, "CCHttpClient: unknown request type, only GET, POST, PUT, HEAD or DELETE is 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<std::mutex> lock(_timeoutForConnectMutex);
_timeoutForConnect = value;
}
int HttpClient::getTimeoutForConnect()
{
std::lock_guard<std::mutex> lock(_timeoutForConnectMutex);
return _timeoutForConnect;
}
void HttpClient::setTimeoutForRead(int value)
{
std::lock_guard<std::mutex> lock(_timeoutForReadMutex);
_timeoutForRead = value;
}
int HttpClient::getTimeoutForRead()
{
std::lock_guard<std::mutex> lock(_timeoutForReadMutex);
return _timeoutForRead;
}
const std::string& HttpClient::getCookieFilename()
{
std::lock_guard<std::mutex> lock(_cookieFileMutex);
return _cookieFilename;
}
const std::string& HttpClient::getSSLVerification()
{
std::lock_guard<std::mutex> lock(_sslCaFileMutex);
return _sslCaFilename;
}
}
NS_CC_END

View File

@@ -0,0 +1,220 @@
/****************************************************************************
Copyright (c) 2012 greathqy
Copyright (c) 2012 cocos2d-x.org
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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.
****************************************************************************/
#ifndef __CCHTTPCLIENT_H__
#define __CCHTTPCLIENT_H__
#include <thread>
#include <condition_variable>
#include "base/CCVector.h"
#include "network/HttpRequest.h"
#include "network/HttpResponse.h"
#include "network/HttpCookie.h"
#include "base/CCScheduler.h"
/**
* @addtogroup network
* @{
*/
NS_CC_BEGIN
namespace network {
/** Singleton that handles asynchronous http requests.
*
* Once the request completed, a callback will issued in main thread when it provided during make request.
*
* @lua NA
*/
class CC_DLL HttpClient
{
public:
/**
* The buffer size of _responseMessage
*/
static const int RESPONSE_BUFFER_SIZE = 256;
/**
* Get instance of HttpClient.
*
* @return the instance of HttpClient.
*/
static HttpClient *getInstance();
/**
* Release the instance of HttpClient.
*/
static void destroyInstance();
/**
* Enable cookie support.
*
* @param cookieFile the filepath of cookie file.
*/
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 the ssl CA filename
*
* @return the ssl CA filename
*/
const std::string& getSSLVerification();
/**
* Add a get request to task queue
*
* @param request a HttpRequest object, which includes url, response callback etc.
please make sure request->_requestData is clear before calling "send" here.
*/
void send(HttpRequest* request);
/**
* Immediate send a request
*
* @param request a HttpRequest object, which includes url, response callback etc.
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.
* @deprecated Please use `HttpRequest.setTimeout` instead.
*/
CC_DEPRECATED_ATTRIBUTE void setTimeoutForConnect(int value);
/**
* Get the timeout value for connecting.
*
* @return int the timeout value for connecting.
* @deprecated Please use `HttpRequest.getTimeout` instead.
*/
CC_DEPRECATED_ATTRIBUTE int getTimeoutForConnect();
/**
* Set the timeout value for reading.
*
* @param value the timeout value for reading.
* @deprecated Please use `HttpRequest.setTimeout` instead.
*/
CC_DEPRECATED_ATTRIBUTE void setTimeoutForRead(int value);
/**
* Get the timeout value for reading.
*
* @return int the timeout value for reading.
* @deprecated Please use `HttpRequest.setTimeout` instead.
*/
CC_DEPRECATED_ATTRIBUTE int getTimeoutForRead();
HttpCookie* getCookie() const {return _cookie; }
std::mutex& getCookieFileMutex() {return _cookieFileMutex;}
std::mutex& getSSLCaFileMutex() {return _sslCaFileMutex;}
private:
HttpClient();
virtual ~HttpClient();
bool init();
/**
* Init pthread mutex, semaphore, and create new thread for http requests
* @return bool
*/
bool lazyInitThreadSemaphore();
void networkThread();
void networkThreadAlone(HttpRequest* request, HttpResponse* response);
/** 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;
std::weak_ptr<Scheduler> _scheduler;
std::mutex _schedulerMutex;
Vector<HttpRequest*> _requestQueue;
std::mutex _requestQueueMutex;
Vector<HttpResponse*> _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;
};
} // namespace network
NS_CC_END
// end group
/// @}
#endif //__CCHTTPCLIENT_H__

View File

@@ -0,0 +1,158 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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 "network/HttpCookie.h"
#include "platform/CCFileUtils.h"
#include <sstream>
#include <stdio.h>
void HttpCookie::readFile()
{
std::string inString = cocos2d::FileUtils::getInstance()->getStringFromFile(_cookieFileName);
if(!inString.empty())
{
std::vector<std::string> cookiesVec;
cookiesVec.clear();
std::stringstream stream(inString);
std::string item;
while(std::getline(stream, item, '\n'))
{
cookiesVec.push_back(item);
}
if(cookiesVec.empty())
return;
_cookies.clear();
for(auto& cookie : cookiesVec)
{
if(cookie.length() == 0)
continue;
if(cookie.find("#HttpOnly_") != std::string::npos)
{
cookie = cookie.substr(10);
}
if(cookie.at(0) == '#')
continue;
CookiesInfo co;
std::stringstream streamInfo(cookie);
std::vector<std::string> elems;
std::string elemsItem;
while (std::getline(streamInfo, elemsItem, '\t'))
{
elems.push_back(elemsItem);
}
co.domain = elems[0];
if (co.domain.at(0) == '.')
{
co.domain = co.domain.substr(1);
}
co.tailmatch = strcmp("TRUE", elems[1].c_str())?true: false;
co.path = elems[2];
co.secure = strcmp("TRUE", elems[3].c_str())?true: false;
co.expires = elems[4];
co.name = elems[5];
co.value = elems[6];
_cookies.push_back(co);
}
}
}
const std::vector<CookiesInfo>* HttpCookie::getCookies() const
{
return &_cookies;
}
const CookiesInfo* HttpCookie::getMatchCookie(const std::string& url) const
{
for(auto& cookie : _cookies)
{
if(url.find(cookie.domain) != std::string::npos)
return &cookie;
}
return nullptr;
}
void HttpCookie::updateOrAddCookie(CookiesInfo* cookie)
{
for(auto& _cookie : _cookies)
{
if(cookie->domain == _cookie.domain)
{
_cookie = *cookie;
return;
}
}
_cookies.push_back(*cookie);
}
void HttpCookie::writeFile()
{
FILE *out;
out = fopen(_cookieFileName.c_str(), "w");
fputs("# Netscape HTTP Cookie File\n"
"# http://curl.haxx.se/docs/http-cookies.html\n"
"# This file was generated by cocos2d-x! Edit at your own risk.\n"
"# Test cocos2d-x cookie write.\n\n",
out);
std::string line;
for(auto& cookie : _cookies)
{
line.clear();
line.append(cookie.domain);
line.append(1, '\t');
cookie.tailmatch ? line.append("TRUE") : line.append("FALSE");
line.append(1, '\t');
line.append(cookie.path);
line.append(1, '\t');
cookie.secure ? line.append("TRUE") : line.append("FALSE");
line.append(1, '\t');
line.append(cookie.expires);
line.append(1, '\t');
line.append(cookie.name);
line.append(1, '\t');
line.append(cookie.value);
//line.append(1, '\n');
fprintf(out, "%s\n", line.c_str());
}
fclose(out);
}
void HttpCookie::setCookieFileName(const std::string& filename)
{
_cookieFileName = filename;
}

View File

@@ -0,0 +1,63 @@
/****************************************************************************
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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.
****************************************************************************/
#ifndef HTTP_COOKIE_H
#define HTTP_COOKIE_H
/// @cond DO_NOT_SHOW
#include<string>
#include<vector>
struct CookiesInfo
{
std::string domain;
bool tailmatch;
std::string path;
bool secure;
std::string name;
std::string value;
std::string expires;
};
class HttpCookie
{
public:
void readFile();
void writeFile();
void setCookieFileName(const std::string& fileName);
const std::vector<CookiesInfo>* getCookies()const;
const CookiesInfo* getMatchCookie(const std::string& url) const;
void updateOrAddCookie(CookiesInfo* cookie);
private:
std::string _cookieFileName;
std::vector<CookiesInfo> _cookies;
};
/// @endcond
#endif /* HTTP_COOKIE_H */

View File

@@ -0,0 +1,308 @@
/****************************************************************************
Copyright (c) 2010-2012 cocos2d-x.org
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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.
****************************************************************************/
#ifndef __HTTP_REQUEST_H__
#define __HTTP_REQUEST_H__
#include "base/CCRef.h"
#include "base/ccMacros.h"
#include <string>
#include <vector>
#include <functional>
/**
* @addtogroup network
* @{
*/
NS_CC_BEGIN
namespace network {
class HttpClient;
class HttpResponse;
typedef std::function<void(HttpClient*/* client*/, HttpResponse*/* response*/)> ccHttpRequestCallback;
/**
* Defines the object which users must packed for HttpClient::send(HttpRequest*) method.
* Please refer to tests/test-cpp/Classes/ExtensionTest/NetworkTest/HttpClientTest.cpp as a sample
* @since v2.0.2
*
* @lua NA
*/
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT)
#ifdef DELETE
#undef DELETE
#endif
#endif
class CC_DLL HttpRequest : public Ref
{
public:
/**
* The HttpRequest type enum used in the HttpRequest::setRequestType.
*/
enum class Type
{
GET,
POST,
PUT,
HEAD,
DELETE,
UNKNOWN,
};
/**
* Constructor.
* Because HttpRequest object will be used between UI thread and network thread,
requestObj->autorelease() is forbidden to avoid crashes in AutoreleasePool
new/retain/release still works, which means you need to release it manually
Please refer to HttpRequestTest.cpp to find its usage.
*/
HttpRequest()
: _requestType(Type::UNKNOWN)
, _callback(nullptr)
, _userData(nullptr)
, _timeoutInSeconds(10.0f)
{
}
/** Destructor. */
virtual ~HttpRequest()
{
}
/**
* Override autorelease method to avoid developers to call it.
* If this function was called, it would trigger assert in debug mode
*
* @return Ref* always return nullptr.
*/
Ref* autorelease()
{
CCASSERT(false, "HttpResponse is used between network thread and ui thread \
therefore, autorelease is forbidden here");
return nullptr;
}
// setter/getters for properties
/**
* Set request type of HttpRequest object before being sent,now it support the enum value of HttpRequest::Type.
*
* @param type the request type.
*/
inline void setRequestType(Type type)
{
_requestType = type;
}
/**
* Get the request type of HttpRequest object.
*
* @return HttpRequest::Type.
*/
inline Type getRequestType() const
{
return _requestType;
}
/**
* Set the url address of HttpRequest object.
* The url value could be like these: "http://httpbin.org/ip" or "https://httpbin.org/get"
*
* @param url the string object.
*/
inline void setUrl(const std::string& url)
{
_url = url;
}
/**
* Get the url address of HttpRequest object.
*
* @return const char* the pointer of _url.
*/
inline const char* getUrl() const
{
return _url.c_str();
}
/**
* Set the request data of HttpRequest object.
*
* @param buffer the buffer of request data, it support binary data.
* @param len the size of request data.
*/
inline void setRequestData(const char* buffer, size_t len)
{
_requestData.assign(buffer, buffer + len);
}
/**
* Get the request data pointer of HttpRequest object.
*
* @return char* the request data pointer.
*/
inline char* getRequestData()
{
if(!_requestData.empty())
return _requestData.data();
return nullptr;
}
/**
* Get the size of request data
*
* @return ssize_t the size of request data
*/
inline ssize_t getRequestDataSize() const
{
return _requestData.size();
}
/**
* Set a string tag to identify your request.
* This tag can be found in HttpResponse->getHttpRequest->getTag().
*
* @param tag the string object.
*/
inline void setTag(const std::string& tag)
{
_tag = tag;
}
/**
* Get the string tag to identify the request.
* The best practice is to use it in your MyClass::onMyHttpRequestCompleted(sender, HttpResponse*) callback.
*
* @return const char* the pointer of _tag
*/
inline const char* getTag() const
{
return _tag.c_str();
}
/**
* Set user-customed data of HttpRequest object.
* You can attach a customed data in each request, and get it back in response callback.
* But you need to new/delete the data pointer manully.
*
* @param userData the string pointer
*/
inline void setUserData(void* userData)
{
_userData = userData;
}
/**
* Get the user-customed data pointer which were pre-setted.
* Don't forget to delete it. HttpClient/HttpResponse/HttpRequest will do nothing with this pointer.
*
* @return void* the pointer of user-customed data.
*/
inline void* getUserData() const
{
return _userData;
}
/**
* Set response callback function of HttpRequest object.
* When response come back, we would call _pCallback to process response data.
*
* @param callback the ccHttpRequestCallback function.
*/
inline void setResponseCallback(const ccHttpRequestCallback& callback)
{
_callback = callback;
}
/**
* Get ccHttpRequestCallback callback function.
*
* @return const ccHttpRequestCallback& ccHttpRequestCallback callback function.
*/
inline const ccHttpRequestCallback& getResponseCallback() const
{
return _callback;
}
/**
* Set custom-defined headers.
*
* @param pHeaders the string vector of custom-defined headers.
*/
inline void setHeaders(const std::vector<std::string>& headers)
{
_headers = headers;
}
/**
* Get custom headers.
*
* @return std::vector<std::string> the string vector of custom-defined headers.
*/
inline std::vector<std::string> getHeaders() const
{
return _headers;
}
inline void setTimeout(float timeoutInSeconds)
{
_timeoutInSeconds = timeoutInSeconds;
}
inline float getTimeout() const
{
return _timeoutInSeconds;
}
protected:
// properties
Type _requestType; /// kHttpRequestGet, kHttpRequestPost or other enums
std::string _url; /// target url that this request is sent to
std::vector<char> _requestData; /// used for POST
std::string _tag; /// user defined tag, to identify different requests in response callback
ccHttpRequestCallback _callback; /// C++11 style callbacks
void* _userData; /// You can add your customed data here
std::vector<std::string> _headers; /// custom http headers
float _timeoutInSeconds;
};
}
NS_CC_END
// end group
/// @}
#endif //__HTTP_REQUEST_H__

View File

@@ -0,0 +1,246 @@
/****************************************************************************
Copyright (c) 2010-2012 cocos2d-x.org
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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.
****************************************************************************/
#ifndef __HTTP_RESPONSE__
#define __HTTP_RESPONSE__
#include "network/HttpRequest.h"
/**
* @addtogroup network
* @{
*/
NS_CC_BEGIN
namespace network {
/**
* @brief defines the object which users will receive at onHttpCompleted(sender, HttpResponse) callback.
* Please refer to samples/TestCpp/Classes/ExtensionTest/NetworkTest/HttpClientTest.cpp as a sample.
* @since v2.0.2.
* @lua NA
*/
class CC_DLL HttpResponse : public cocos2d::Ref
{
public:
/**
* Constructor, it's used by HttpClient internal, users don't need to create HttpResponse manually.
* @param request the corresponding HttpRequest which leads to this response.
*/
HttpResponse(HttpRequest* request)
: _pHttpRequest(request)
, _succeed(false)
, _responseDataString("")
{
if (_pHttpRequest)
{
_pHttpRequest->retain();
}
}
/**
* Destructor, it will be called in HttpClient internal.
* Users don't need to destruct HttpResponse object manually.
*/
virtual ~HttpResponse()
{
if (_pHttpRequest)
{
_pHttpRequest->release();
}
}
/**
* Override autorelease method to prevent developers from calling it.
* If this method is called , it would trigger CCASSERT.
* @return cocos2d::Ref* always return nullptr.
*/
cocos2d::Ref* autorelease()
{
CCASSERT(false, "HttpResponse is used between network thread and ui thread \
therefore, autorelease is forbidden here");
return nullptr;
}
// getters, will be called by users
/**
* Get the corresponding HttpRequest object which leads to this response.
* There's no paired setter for it, because it's already set in class constructor
* @return HttpRequest* the corresponding HttpRequest object which leads to this response.
*/
inline HttpRequest* getHttpRequest() const
{
return _pHttpRequest;
}
/**
* To see if the http request is returned successfully.
* Although users can judge if (http response code = 200), we want an easier way.
* If this getter returns false, you can call getResponseCode and getErrorBuffer to find more details.
* @return bool the flag that represent whether the http request return successfully or not.
*/
inline bool isSucceed() const
{
return _succeed;
}
/**
* Get the http response data.
* @return std::vector<char>* the pointer that point to the _responseData.
*/
inline std::vector<char>* getResponseData()
{
return &_responseData;
}
/**
* Get the response headers.
* @return std::vector<char>* the pointer that point to the _responseHeader.
*/
inline std::vector<char>* getResponseHeader()
{
return &_responseHeader;
}
/**
* Get the http response code to judge whether response is successful or not.
* I know that you want to see the _responseCode is 200.
* If _responseCode is not 200, you should check the meaning for _responseCode by the net.
* @return long the value of _responseCode
*/
inline long getResponseCode() const
{
return _responseCode;
}
/**
* Get the error buffer which will tell you more about the reason why http request failed.
* @return const char* the pointer that point to _errorBuffer.
*/
inline const char* getErrorBuffer() const
{
return _errorBuffer.c_str();
}
// setters, will be called by HttpClient
// users should avoid invoking these methods
/**
* Set whether the http request is returned successfully or not,
* This setter is mainly used in HttpClient, users mustn't set it directly
* @param value the flag represent whether the http request is successful or not.
*/
inline void setSucceed(bool value)
{
_succeed = value;
}
/**
* Set the http response data buffer, it is used by HttpClient.
* @param data the pointer point to the response data buffer.
*/
inline void setResponseData(std::vector<char>* data)
{
_responseData = *data;
}
/**
* Set the http response headers buffer, it is used by HttpClient.
* @param data the pointer point to the response headers buffer.
*/
inline void setResponseHeader(std::vector<char>* data)
{
_responseHeader = *data;
}
/**
* Set the http response code.
* @param value the http response code that represent whether the request is successful or not.
*/
inline void setResponseCode(long value)
{
_responseCode = value;
}
/**
* Set the error buffer which will tell you more the reason why http request failed.
* @param value a string pointer that point to the reason.
*/
inline void setErrorBuffer(const char* value)
{
_errorBuffer.clear();
if (value != nullptr)
_errorBuffer.assign(value);
}
/**
* Set the response data by the string pointer and the defined size.
* @param value a string pointer that point to response data buffer.
* @param n the defined size that the response data buffer would be copied.
*/
inline void setResponseDataString(const char* value, size_t n)
{
_responseDataString.clear();
_responseDataString.assign(value, n);
}
/**
* Get the string pointer that point to the response data.
* @return const char* the string pointer that point to the response data.
*/
inline const char* getResponseDataString() const
{
return _responseDataString.c_str();
}
protected:
bool initWithRequest(HttpRequest* request);
// properties
HttpRequest* _pHttpRequest; /// the corresponding HttpRequest pointer who leads to this response
bool _succeed; /// to indicate if the http request is successful simply
std::vector<char> _responseData; /// the returned raw data. You can also dump it as a string
std::vector<char> _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 _responseDataString; // the returned raw data. You can also dump it as a string
};
}
NS_CC_END
// end group
/// @}
#endif //__HTTP_RESPONSE_H__

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,300 @@
/****************************************************************************
Copyright (c) 2015 Chris Hannon http://www.channon.us
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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.
*based on the SocketIO library created by LearnBoost at http://socket.io
*using spec version 1 found at https://github.com/LearnBoost/socket.io-spec
Usage is described below, a full working example can be found in TestCpp under ExtionsTest/NetworkTest/SocketIOTest
creating a new connection to a socket.io server running at localhost:3000
SIOClient *client = SocketIO::connect(*delegate, "ws://localhost:3000");
the connection process will begin and if successful delegate::onOpen will be called
if the connection process results in an error, delegate::onError will be called with the err msg
sending a message to the server
client->send("Hello!");
emitting an event to be handled by the server, argument json formatting is up to you
client->emit("eventname", "[{\"arg\":\"value\"}]");
registering an event callback, target should be a member function in a subclass of SIODelegate
CC_CALLBACK_2 is used to wrap the callback with std::bind and store as an SIOEvent
client->on("eventname", CC_CALLBACK_2(TargetClass::targetfunc, *targetclass_instance));
event target function should match this pattern, *this pointer will be made available
void TargetClass::targetfunc(SIOClient *, const std::string&)
disconnect from the endpoint by calling disconnect(), onClose will be called on the delegate once complete
in the onClose method the pointer should be set to NULL or used to connect to a new endpoint
client->disconnect();
****************************************************************************/
#pragma once
#include <string>
#include <unordered_map>
#include <functional>
#include "base/ccMacros.h"
#include "base/CCMap.h"
/**
* @addtogroup network
* @{
*/
NS_CC_BEGIN
namespace network {
//forward declarations
class SIOClientImpl;
class SIOClient;
/**
* Singleton and wrapper class to provide static creation method as well as registry of all sockets.
*
* @lua NA
*/
class CC_DLL SocketIO
{
public:
/**
* Get instance of SocketIO.
*
* @return SocketIO* the instance of SocketIO.
*/
static SocketIO* getInstance();
static void destroyInstance();
/**
* The delegate class to process socket.io events.
* @lua NA
*/
class SIODelegate
{
public:
/** Destructor of SIODelegate. */
virtual ~SIODelegate() {}
/**
* This is kept for backwards compatibility, connect is now fired as a socket.io event "connect"
*
* This function would be called when the related SIOClient object receive messages that mean it have connected to endpoint successfully.
*
* @param client the connected SIOClient object.
*/
virtual void onConnect(SIOClient* client) { CCLOG("SIODelegate onConnect fired"); };
/**
* This is kept for backwards compatibility, message is now fired as a socket.io event "message"
*
* This function would be called when the related SIOClient object receive message or json message.
*
* @param client the connected SIOClient object.
* @param data the message,it could be json message
*/
virtual void onMessage(SIOClient* client, const std::string& data) { CCLOG("SIODelegate onMessage fired with data: %s", data.c_str()); };
/**
* Pure virtual callback function, this function should be overridden by the subclass.
*
* This function would be called when the related SIOClient object disconnect or receive disconnect signal.
*
* @param client the connected SIOClient object.
*/
virtual void onClose(SIOClient* client) = 0;
/**
* Pure virtual callback function, this function should be overridden by the subclass.
*
* This function would be called when the related SIOClient object receive error signal or didn't connect the endpoint but do some network operation, eg.,send and emit,etc.
*
* @param client the connected SIOClient object.
* @param data the error message
*/
virtual void onError(SIOClient* client, const std::string& data) = 0;
/**
* Fire event to script when the related SIOClient object receive the fire event signal.
*
* @param client the connected SIOClient object.
* @param eventName the event's name.
* @param data the event's data information.
*/
virtual void fireEventToScript(SIOClient* client, const std::string& eventName, const std::string& data) { CCLOG("SIODelegate event '%s' fired with data: %s", eventName.c_str(), data.c_str()); };
};
/**
* Static client creation method, similar to socketio.connect(uri) in JS.
* @param uri the URI of the socket.io server.
* @param delegate the delegate which want to receive events from the socket.io client.
* @return SIOClient* an initialized SIOClient if connected successfully, otherwise nullptr.
*/
static SIOClient* connect(const std::string& uri, SIODelegate& delegate);
/**
* Static client creation method, similar to socketio.connect(uri) in JS.
* @param uri the URI of the socket.io server.
* @param delegate the delegate which want to receive events from the socket.io client.
* @param caFilePath The ca file path for wss connection
* @return SIOClient* an initialized SIOClient if connected successfully, otherwise nullptr.
*/
static SIOClient* connect(const std::string& uri, SIODelegate& delegate, const std::string& caFilePath);
/**
* Static client creation method, similar to socketio.connect(uri) in JS.
* @param delegate the delegate which want to receive events from the socket.io client.
* @param uri the URI of the socket.io server.
* @return SIOClient* an initialized SIOClient if connected successfully, otherwise nullptr.
*/
CC_DEPRECATED_ATTRIBUTE static SIOClient* connect(SIODelegate& delegate, const std::string& uri);
private:
SocketIO();
virtual ~SocketIO();
static SocketIO *_inst;
cocos2d::Map<std::string, SIOClientImpl*> _sockets;
SIOClientImpl* getSocket(const std::string& uri);
void addSocket(const std::string& uri, SIOClientImpl* socket);
void removeSocket(const std::string& uri);
friend class SIOClientImpl;
private:
CC_DISALLOW_COPY_AND_ASSIGN(SocketIO)
};
//c++11 style callbacks entities will be created using CC_CALLBACK (which uses std::bind)
typedef std::function<void(SIOClient*, const std::string&)> SIOEvent;
//c++11 map to callbacks
typedef std::unordered_map<std::string, SIOEvent> EventRegistry;
/**
* A single connection to a socket.io endpoint.
*
* @lua NA
*/
class CC_DLL SIOClient
: public cocos2d::Ref
{
private:
friend class SocketIO; // Only SocketIO class could contruct a SIOClient instance.
std::string _path, _tag;
bool _connected;
SIOClientImpl* _socket;
SocketIO::SIODelegate* _delegate;
EventRegistry _eventRegistry;
uint32_t _instanceId;
void fireEvent(const std::string& eventName, const std::string& data);
void onOpen();
void onConnect();
void socketClosed();
friend class SIOClientImpl;
/**
* Constructor of SIOClient class.
*
* @param host the string that represent the host address.
* @param port the int value represent the port number.
* @param path the string that represent endpoint.
* @param impl the SIOClientImpl object.
* @param delegate the SIODelegate object.
*/
SIOClient(const std::string& path, SIOClientImpl* impl, SocketIO::SIODelegate& delegate);
/**
* Destructor of SIOClient class.
*/
virtual ~SIOClient();
public:
/**
* Get the delegate for the client
* @return the delegate object for the client
*/
SocketIO::SIODelegate* getDelegate() { return _delegate; };
/**
* Disconnect from the endpoint, onClose will be called for the delegate when complete
*/
void disconnect();
/**
* Send a message to the socket.io server.
*
* @param s message.
*/
void send(const std::string& s);
/**
* Emit the eventname and the args to the endpoint that _path point to.
* @param eventname
* @param args
*/
void emit(const std::string& eventname, const std::string& args);
/**
* Used to register a socket.io event callback.
* Event argument should be passed using CC_CALLBACK2(&Base::function, this).
* @param eventName the name of event.
* @param e the callback function.
*/
void on(const std::string& eventName, SIOEvent e);
/**
* Set tag of SIOClient.
* The tag is used to distinguish the various SIOClient objects.
* @param tag string object.
*/
void setTag(const char* tag);
/**
* Get tag of SIOClient.
* @return const char* the pointer point to the _tag.
*/
const char* getTag()
{
return _tag.c_str();
}
/**
* Gets instance id
*/
uint32_t getInstanceId() const;
};
}
NS_CC_END
// end group
/// @}

View File

@@ -0,0 +1,377 @@
/*
* Copyright 2017 Facebook, Inc.
* Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Uri class is based on the original file here https://github.com/facebook/folly/blob/master/folly/Uri.cpp
*/
#include "network/Uri.h"
#include "base/ccMacros.h" // For CCLOGERROR macro
#include <regex>
#include <sstream>
#include <ctype.h>
#include <stdlib.h>
#undef LIKELY
#undef UNLIKELY
#if defined(__GNUC__) && __GNUC__ >= 4
#define LIKELY(x) (__builtin_expect((x), 1))
#define UNLIKELY(x) (__builtin_expect((x), 0))
#else
#define LIKELY(x) (x)
#define UNLIKELY(x) (x)
#endif
namespace {
template<typename T>
std::string toString(T arg)
{
std::stringstream ss;
ss << arg;
return ss.str();
}
std::string submatch(const std::smatch& m, int idx)
{
auto& sub = m[idx];
return std::string(sub.first, sub.second);
}
template <class String>
void toLower(String& s)
{
for (auto& c : s) {
c = char(tolower(c));
}
}
} // namespace
NS_CC_BEGIN
namespace network {
Uri::Uri()
: _isValid(false)
, _isSecure(false)
, _hasAuthority(false)
, _port(0)
{
}
Uri::Uri(const Uri& o)
{
*this = o;
}
Uri::Uri(Uri&& o)
{
*this = std::move(o);
}
Uri& Uri::operator=(const Uri& o)
{
if (this != &o)
{
_isValid = o._isValid;
_isSecure = o._isSecure;
_scheme = o._scheme;
_username = o._username;
_password = o._password;
_host = o._host;
_hostName = o._hostName;
_hasAuthority = o._hasAuthority;
_port = o._port;
_authority = o._authority;
_pathEtc = o._pathEtc;
_path = o._path;
_query = o._query;
_fragment = o._fragment;
_queryParams = o._queryParams;
}
return *this;
}
Uri& Uri::operator=(Uri&& o)
{
if (this != &o)
{
_isValid = o._isValid;
o._isValid = false;
_isSecure = o._isSecure;
o._isSecure = false;
_scheme = std::move(o._scheme);
_username = std::move(o._username);
_password = std::move(o._password);
_host = std::move(o._host);
_hostName = std::move(o._hostName);
_hasAuthority = o._hasAuthority;
o._hasAuthority = false;
_port = o._port;
o._port = 0;
_authority = std::move(o._authority);
_pathEtc = std::move(o._pathEtc);
_path = std::move(o._path);
_query = std::move(o._query);
_fragment = std::move(o._fragment);
_queryParams = std::move(o._queryParams);
}
return *this;
}
bool Uri::operator==(const Uri& o) const
{
return (_isValid == o._isValid
&& _isSecure == o._isSecure
&& _scheme == o._scheme
&& _username == o._username
&& _password == o._password
&& _host == o._host
&& _hostName == o._hostName
&& _hasAuthority == o._hasAuthority
&& _port == o._port
&& _authority == o._authority
&& _pathEtc == o._pathEtc
&& _path == o._path
&& _query == o._query
&& _fragment == o._fragment
&& _queryParams == o._queryParams);
}
Uri Uri::parse(const std::string &str)
{
Uri uri;
if (!uri.doParse(str))
{
uri.clear();
}
return uri;
}
bool Uri::doParse(const std::string& str)
{
static const std::regex uriRegex(
"([a-zA-Z][a-zA-Z0-9+.-]*):" // scheme:
"([^?#]*)" // authority and path
"(?:\\?([^#]*))?" // ?query
"(?:#(.*))?"); // #fragment
static const std::regex authorityAndPathRegex("//([^/]*)(/.*)?");
if (str.empty())
{
CCLOGERROR("%s", "Empty URI is invalid!");
return false;
}
bool hasScheme = true;;
std::string copied(str);
if (copied.find("://") == std::string::npos)
{
hasScheme = false;
copied.insert(0, "abc://"); // Just make regex happy.
}
std::smatch match;
if (UNLIKELY(!std::regex_match(copied.cbegin(), copied.cend(), match, uriRegex))) {
CCLOGERROR("Invalid URI: %s", str.c_str());
return false;
}
if (hasScheme)
{
_scheme = submatch(match, 1);
toLower(_scheme);
if (_scheme == "https" || _scheme == "wss")
{
_isSecure = true;
}
}
std::string authorityAndPath(match[2].first, match[2].second);
std::smatch authorityAndPathMatch;
if (!std::regex_match(authorityAndPath.cbegin(),
authorityAndPath.cend(),
authorityAndPathMatch,
authorityAndPathRegex)) {
// Does not start with //, doesn't have authority
_hasAuthority = false;
_path = authorityAndPath;
} else {
static const std::regex authorityRegex(
"(?:([^@:]*)(?::([^@]*))?@)?" // username, password
"(\\[[^\\]]*\\]|[^\\[:]*)" // host (IP-literal (e.g. '['+IPv6+']',
// dotted-IPv4, or named host)
"(?::(\\d*))?"); // port
auto authority = authorityAndPathMatch[1];
std::smatch authorityMatch;
if (!std::regex_match(authority.first,
authority.second,
authorityMatch,
authorityRegex)) {
std::string invalidAuthority(authority.first, authority.second);
CCLOGERROR("Invalid URI authority: %s", invalidAuthority.c_str());
return false;
}
std::string port(authorityMatch[4].first, authorityMatch[4].second);
if (!port.empty()) {
_port = static_cast<uint16_t>(atoi(port.c_str()));
}
_hasAuthority = true;
_username = submatch(authorityMatch, 1);
_password = submatch(authorityMatch, 2);
_host = submatch(authorityMatch, 3);
_path = submatch(authorityAndPathMatch, 2);
}
_query = submatch(match, 3);
_fragment = submatch(match, 4);
_isValid = true;
// Assign authority part
//
// Port is 5 characters max and we have up to 3 delimiters.
_authority.reserve(getHost().size() + getUserName().size() + getPassword().size() + 8);
if (!getUserName().empty() || !getPassword().empty()) {
_authority.append(getUserName());
if (!getPassword().empty()) {
_authority.push_back(':');
_authority.append(getPassword());
}
_authority.push_back('@');
}
_authority.append(getHost());
if (getPort() != 0) {
_authority.push_back(':');
_authority.append(::toString(getPort()));
}
// Assign path etc part
_pathEtc = _path;
if (!_query.empty())
{
_pathEtc += '?';
_pathEtc += _query;
}
if (!_fragment.empty())
{
_pathEtc += '#';
_pathEtc += _fragment;
}
// Assign host name
if (!_host.empty() && _host[0] == '[') {
// If it starts with '[', then it should end with ']', this is ensured by
// regex
_hostName = _host.substr(1, _host.size() - 2);
} else {
_hostName = _host;
}
return true;
}
void Uri::clear()
{
_isValid = false;
_isSecure = false;
_scheme.clear();
_username.clear();
_password.clear();
_host.clear();
_hostName.clear();
_hasAuthority = false;
_port = 0;
_authority.clear();
_pathEtc.clear();
_path.clear();
_query.clear();
_fragment.clear();
_queryParams.clear();
}
const std::vector<std::pair<std::string, std::string>>& Uri::getQueryParams()
{
if (!_query.empty() && _queryParams.empty()) {
// Parse query string
static const std::regex queryParamRegex(
"(^|&)" /*start of query or start of parameter "&"*/
"([^=&]*)=?" /*parameter name and "=" if value is expected*/
"([^=&]*)" /*parameter value*/
"(?=(&|$))" /*forward reference, next should be end of query or
start of next parameter*/);
std::cregex_iterator paramBeginItr(
_query.data(), _query.data() + _query.size(), queryParamRegex);
std::cregex_iterator paramEndItr;
for (auto itr = paramBeginItr; itr != paramEndItr; itr++) {
if (itr->length(2) == 0) {
// key is empty, ignore it
continue;
}
_queryParams.emplace_back(
std::string((*itr)[2].first, (*itr)[2].second), // parameter name
std::string((*itr)[3].first, (*itr)[3].second) // parameter value
);
}
}
return _queryParams;
}
std::string Uri::toString() const
{
std::stringstream ss;
if (_hasAuthority) {
ss << _scheme << "://";
if (!_password.empty()) {
ss << _username << ":" << _password << "@";
} else if (!_username.empty()) {
ss << _username << "@";
}
ss << _host;
if (_port != 0) {
ss << ":" << _port;
}
} else {
ss << _scheme << ":";
}
ss << _path;
if (!_query.empty()) {
ss << "?" << _query;
}
if (!_fragment.empty()) {
ss << "#" << _fragment;
}
return ss.str();
}
} // namespace network {
NS_CC_END

View File

@@ -0,0 +1,184 @@
/*
* Copyright 2017 Facebook, Inc.
* Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Uri class is based on the original file here https://github.com/facebook/folly/blob/master/folly/Uri.cpp
*/
#pragma once
#include "base/ccMacros.h"
#include <string>
#include <vector>
#include <stdint.h>
/**
* @addtogroup network
* @{
*/
NS_CC_BEGIN
namespace network {
/**
* Class representing a URI.
*
* Consider http://www.facebook.com/foo/bar?key=foo#anchor
*
* The URI is broken down into its parts: scheme ("http"), authority
* (ie. host and port, in most cases: "www.facebook.com"), path
* ("/foo/bar"), query ("key=foo") and fragment ("anchor"). The scheme is
* lower-cased.
*
* If this Uri represents a URL, note that, to prevent ambiguity, the component
* parts are NOT percent-decoded; you should do this yourself with
* uriUnescape() (for the authority and path) and uriUnescape(...,
* UriEscapeMode::QUERY) (for the query, but probably only after splitting at
* '&' to identify the individual parameters).
*/
class CC_DLL Uri
{
public:
/**
* Parse a Uri from a string. Throws std::invalid_argument on parse error.
*/
static Uri parse(const std::string& str);
/** Default constructor */
Uri();
/** Copy constructor */
Uri(const Uri& o);
/** Move constructor */
Uri(Uri&& o);
/** Copy assignment */
Uri& operator=(const Uri& o);
/** Move assignment */
Uri& operator=(Uri&& o);
/** Checks whether two Uri instances contain the same values */
bool operator==(const Uri& o) const;
/** Checks wether it's a valid URI */
bool isValid() const { return _isValid; }
/** Checks whether it's a SSL connection */
bool isSecure() const { return _isSecure; }
/** Gets the scheme name for this URI. */
const std::string& getScheme() const { return _scheme; }
/** Gets the user name with the specified URI. */
const std::string& getUserName() const { return _username; }
/** Gets the password with the specified URI. */
const std::string& getPassword() const { return _password; }
/**
* Get host part of URI. If host is an IPv6 address, square brackets will be
* returned, for example: "[::1]".
*/
const std::string& getHost() const { return _host; }
/**
* Get host part of URI. If host is an IPv6 address, square brackets will not
* be returned, for exmaple "::1"; otherwise it returns the same thing as
* getHost().
*
* getHostName() is what one needs to call if passing the host to any other tool
* or API that connects to that host/port; e.g. getaddrinfo() only understands
* IPv6 host without square brackets
*/
const std::string& getHostName() const { return _hostName; }
/** Gets the port number of the URI. */
uint16_t getPort() const { return _port; }
/** Gets the path part of the URI. */
const std::string& getPath() const { return _path; }
/// Gets the path, query and fragment parts of the URI.
const std::string& getPathEtc() const { return _pathEtc; }
/** Gets the query part of the URI. */
const std::string& getQuery() const { return _query; }
/** Gets the fragment part of the URI */
const std::string& getFragment() const { return _fragment; }
/** Gets the authority part (userName, password, host and port) of the URI.
* @note If the port number is a well-known port
* number for the given scheme (e.g., 80 for http), it
* is not included in the authority.
*/
const std::string& getAuthority() const { return _authority; }
/** Gets a string representation of the URI. */
std::string toString() const;
/**
* Get query parameters as key-value pairs.
* e.g. for URI containing query string: key1=foo&key2=&key3&=bar&=bar=
* In returned list, there are 3 entries:
* "key1" => "foo"
* "key2" => ""
* "key3" => ""
* Parts "=bar" and "=bar=" are ignored, as they are not valid query
* parameters. "=bar" is missing parameter name, while "=bar=" has more than
* one equal signs, we don't know which one is the delimiter for key and
* value.
*
* Note, this method is not thread safe, it might update internal state, but
* only the first call to this method update the state. After the first call
* is finished, subsequent calls to this method are thread safe.
*
* @return query parameter key-value pairs in a vector, each element is a
* pair of which the first element is parameter name and the second
* one is parameter value
*/
const std::vector<std::pair<std::string, std::string>>& getQueryParams();
/** Clears all parts of the URI. */
void clear();
private:
bool doParse(const std::string& str);
bool _isValid;
bool _isSecure;
std::string _scheme;
std::string _username;
std::string _password;
std::string _host;
std::string _hostName;
bool _hasAuthority;
uint16_t _port;
std::string _authority;
std::string _pathEtc;
std::string _path;
std::string _query;
std::string _fragment;
std::vector<std::pair<std::string, std::string>> _queryParams;
};
} // namespace network {
NS_CC_END
// end group
/// @}

View File

@@ -0,0 +1,378 @@
/****************************************************************************
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
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 "network/WebSocket.h"
#include "base/CCData.h"
#import "SocketRocket/SocketRocket.h"
#if !__has_feature(objc_arc)
#error WebSocket must be compiled with ARC enabled
#endif
static std::vector<cocos2d::network::WebSocket*>* __websocketInstances = nullptr;
@interface WebSocketImpl : NSObject<SRWebSocketDelegate>
{
}
@end
//
@implementation WebSocketImpl
{
SRWebSocket* _ws;
cocos2d::network::WebSocket* _ccws;
cocos2d::network::WebSocket::Delegate* _delegate;
std::string _url;
std::string _selectedProtocol;
bool _isDestroyed;
}
-(id) initWithURL:(const std::string&) url protocols:(NSArray<NSString *> *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates ws:(cocos2d::network::WebSocket*) ccws delegate:(const cocos2d::network::WebSocket::Delegate&) delegate
{
if (self = [super init])
{
_ccws = ccws;
_delegate = const_cast<cocos2d::network::WebSocket::Delegate*>(&delegate);
_url = url;
NSURL* nsUrl = [[NSURL alloc] initWithString:[[NSString alloc] initWithUTF8String:_url.c_str()]];
_ws = [[SRWebSocket alloc] initWithURL:nsUrl protocols:protocols allowsUntrustedSSLCertificates:allowsUntrustedSSLCertificates];
_ws.delegate = self;
[_ws open];
_isDestroyed = false;
}
return self;
}
-(void) dealloc
{
// NSLog(@"WebSocketImpl-apple dealloc: %p, SRWebSocket ref: %ld", self, CFGetRetainCount((__bridge CFTypeRef)_ws));
}
-(void) sendString:(NSString*) message
{
[_ws sendString:message error:nil];
}
-(void) sendData:(NSData*) data
{
[_ws sendData:data error:nil];
}
-(void) close
{
_isDestroyed = true;
_ccws->retain();
_delegate->onClose(_ccws);
[_ws close];
_ccws->release();
}
-(void) closeAsync
{
[_ws close];
}
-(cocos2d::network::WebSocket::State) getReadyState
{
cocos2d::network::WebSocket::State ret;
SRReadyState state = _ws.readyState;
switch (state) {
case SR_OPEN:
ret = cocos2d::network::WebSocket::State::OPEN;
break;
case SR_CONNECTING:
ret = cocos2d::network::WebSocket::State::CONNECTING;
break;
case SR_CLOSING:
ret = cocos2d::network::WebSocket::State::CLOSING;
break;
default:
ret = cocos2d::network::WebSocket::State::CLOSED;
break;
}
return ret;
}
-(const std::string&) getUrl
{
return _url;
}
-(const std::string&) getProtocol
{
return _selectedProtocol;
}
-(cocos2d::network::WebSocket::Delegate*) getDelegate
{
return (cocos2d::network::WebSocket::Delegate*)_delegate;
}
// Delegate methods
-(void)webSocketDidOpen:(SRWebSocket *)webSocket;
{
if (!_isDestroyed)
{
// NSLog(@"Websocket Connected");
if (webSocket.protocol != nil)
_selectedProtocol = [webSocket.protocol UTF8String];
_delegate->onOpen(_ccws);
}
else
{
NSLog(@"WebSocketImpl webSocketDidOpen was destroyed!");
}
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error;
{
if (!_isDestroyed)
{
NSLog(@":( Websocket Failed With Error %@", error);
_delegate->onError(_ccws, cocos2d::network::WebSocket::ErrorCode::UNKNOWN);
[self webSocket:webSocket didCloseWithCode:0 reason:@"onerror" wasClean:YES];
}
else
{
NSLog(@"WebSocketImpl didFailWithError was destroyed!");
}
}
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithString:(nonnull NSString *)string
{
if (!_isDestroyed)
{
cocos2d::network::WebSocket::Data data;
data.bytes = (char*) [string cStringUsingEncoding:NSUTF8StringEncoding];
data.len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
data.isBinary = false;
data.issued = 0;
data.ext = nullptr;
_delegate->onMessage(_ccws, data);
}
else
{
NSLog(@"WebSocketImpl didReceiveMessageWithString was destroyed!");
}
}
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithData:(NSData *)nsData
{
if (!_isDestroyed)
{
cocos2d::network::WebSocket::Data data;
data.bytes = (char*)nsData.bytes;
data.len = nsData.length;
data.isBinary = true;
data.issued = 0;
data.ext = nullptr;
_delegate->onMessage(_ccws, data);
}
else
{
NSLog(@"WebSocketImpl didReceiveMessageWithData was destroyed!");
}
}
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean
{
if (!_isDestroyed)
_delegate->onClose(_ccws);
else
NSLog(@"WebSocketImpl didCloseWithCode was destroyed!");
}
@end
NS_CC_BEGIN
namespace network {
void WebSocket::closeAllConnections()
{
if (__websocketInstances != nullptr)
{
ssize_t count = __websocketInstances->size();
for (ssize_t i = count-1; i >=0 ; i--)
{
WebSocket* instance = __websocketInstances->at(i);
instance->close();
}
__websocketInstances->clear();
delete __websocketInstances;
__websocketInstances = nullptr;
}
}
WebSocket::WebSocket()
: _impl(nil)
{
if (__websocketInstances == nullptr)
{
__websocketInstances = new (std::nothrow) std::vector<WebSocket*>();
}
__websocketInstances->push_back(this);
}
WebSocket::~WebSocket()
{
// NSLog(@"In the destructor of WebSocket-apple (%p).", this);
if (__websocketInstances != nullptr)
{
auto iter = std::find(__websocketInstances->begin(), __websocketInstances->end(), this);
if (iter != __websocketInstances->end())
{
__websocketInstances->erase(iter);
}
else
{
NSLog(@"ERROR: WebSocket instance wasn't added to the container which saves websocket instances!");
}
}
}
bool WebSocket::init(const Delegate& delegate,
const std::string& url,
const std::vector<std::string>* protocols/* = nullptr*/,
const std::string& caFilePath/* = ""*/)
{
if (url.empty())
return false;
NSMutableArray* nsProtocols = [[NSMutableArray alloc] init];
if (protocols != nullptr)
{
for (const auto& protocol : *protocols)
{
[nsProtocols addObject:[[NSString alloc] initWithUTF8String:protocol.c_str()]];
}
}
_impl = [[WebSocketImpl alloc] initWithURL: url protocols:nsProtocols allowsUntrustedSSLCertificates:NO ws: this delegate:delegate];
return _impl != nil;
}
void WebSocket::send(const std::string& message)
{
if ([_impl getReadyState] == State::OPEN)
{
NSString* str = [[NSString alloc] initWithBytes:message.data() length:message.length() encoding:NSUTF8StringEncoding];
[_impl sendString:str];
}
else
{
NSLog(@"Couldn't send message since websocket wasn't opened!");
}
}
void WebSocket::send(const unsigned char* binaryMsg, unsigned int len)
{
if ([_impl getReadyState] == State::OPEN)
{
NSData* data = [[NSData alloc] initWithBytes:binaryMsg length:(NSUInteger)len];
[_impl sendData:data];
}
else
{
NSLog(@"Couldn't send message since websocket wasn't opened!");
}
}
void WebSocket::close()
{
if ([_impl getReadyState] == State::CLOSING || [_impl getReadyState] == State::CLOSED)
{
NSLog(@"WebSocket (%p) was closed, no need to close it again!", this);
return;
}
[_impl close];
}
void WebSocket::closeAsync()
{
if ([_impl getReadyState] == State::CLOSING || [_impl getReadyState] == State::CLOSED)
{
NSLog(@"WebSocket (%p) was closed, no need to close it again!", this);
return;
}
[_impl closeAsync];
}
void WebSocket::closeAsync(int code, const std::string &reason)
{
//lws_close_reason() replacement required
closeAsync();
}
std::string WebSocket::getExtensions() const
{
//TODO websocket extensions
return "";
}
size_t WebSocket::getBufferedAmount() const
{
//TODO pending send bytes
return 0;
}
WebSocket::State WebSocket::getReadyState() const
{
return [_impl getReadyState];
}
const std::string& WebSocket::getUrl() const
{
return [_impl getUrl];
}
const std::string& WebSocket::getProtocol() const
{
return [_impl getProtocol];
}
WebSocket::Delegate* WebSocket::getDelegate() const
{
return [_impl getDelegate];
}
} // namespace network {
NS_CC_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,261 @@
/****************************************************************************
Copyright (c) 2010-2012 cocos2d-x.org
Copyright (c) 2013-2016 Chukong Technologies Inc.
Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
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.
****************************************************************************/
#pragma once
#include "base/ccMacros.h"
#include "platform/CCStdC.h"
#include "base/CCRef.h"
#include <string>
#include <vector>
#include <algorithm>
#ifndef OBJC_CLASS
#ifdef __OBJC__
#define OBJC_CLASS(name) @class name
#else
#define OBJC_CLASS(name) class name
#endif
#endif // OBJC_CLASS
OBJC_CLASS(WebSocketImpl);
/**
* @addtogroup network
* @{
*/
NS_CC_BEGIN
namespace network {
class WebSocketFrame;
/**
* WebSocket is wrapper of the libwebsockets-protocol, let the develop could call the websocket easily.
* Please note that all public methods of WebSocket have to be invoked on Cocos Thread.
*/
class CC_DLL WebSocket : public Ref
{
public:
/**
* Close all connections and wait for all websocket threads to exit
* @note This method has to be invoked on Cocos Thread
*/
static void closeAllConnections();
/**
* Constructor of WebSocket.
*
* @js ctor
*/
WebSocket();
private:
/**
* Destructor of WebSocket.
*
* @js NA
* @lua NA
*/
virtual ~WebSocket();
public:
/**
* Data structure for message
*/
struct Data
{
Data():bytes(nullptr), len(0), issued(0), isBinary(false), ext(nullptr){}
char* bytes;
ssize_t len, issued;
bool isBinary;
void* ext;
ssize_t getRemain() { return std::max((ssize_t)0, len - issued); }
};
/**
* ErrorCode enum used to represent the error in the websocket.
*/
enum class ErrorCode
{
TIME_OUT, /** &lt; value 0 */
CONNECTION_FAILURE, /** &lt; value 1 */
UNKNOWN, /** &lt; value 2 */
};
/**
* State enum used to represent the Websocket state.
*/
enum class State
{
CONNECTING, /** &lt; value 0 */
OPEN, /** &lt; value 1 */
CLOSING, /** &lt; value 2 */
CLOSED, /** &lt; value 3 */
};
/**
* The delegate class is used to process websocket events.
*
* The most member function are pure virtual functions,they should be implemented the in subclass.
* @lua NA
*/
class Delegate
{
public:
/** Destructor of Delegate. */
virtual ~Delegate() {}
/**
* This function to be called after the client connection complete a handshake with the remote server.
* This means that the WebSocket connection is ready to send and receive data.
*
* @param ws The WebSocket object connected
*/
virtual void onOpen(WebSocket* ws) = 0;
/**
* This function to be called when data has appeared from the server for the client connection.
*
* @param ws The WebSocket object connected.
* @param data Data object for message.
*/
virtual void onMessage(WebSocket* ws, const Data& data) = 0;
/**
* When the WebSocket object connected wants to close or the protocol won't get used at all and current _readyState is State::CLOSING,this function is to be called.
*
* @param ws The WebSocket object connected.
*/
virtual void onClose(WebSocket* ws) = 0;
/**
* This function is to be called in the following cases:
* 1. client connection is failed.
* 2. the request client connection has been unable to complete a handshake with the remote server.
* 3. the protocol won't get used at all after this callback and current _readyState is State::CONNECTING.
* 4. when a socket descriptor needs to be removed from an external polling array. in is again the struct libwebsocket_pollargs containing the fd member to be removed. If you are using the internal polling loop, you can just ignore it and current _readyState is State::CONNECTING.
*
* @param ws The WebSocket object connected.
* @param error WebSocket::ErrorCode enum,would be ErrorCode::TIME_OUT or ErrorCode::CONNECTION_FAILURE.
*/
virtual void onError(WebSocket* ws, const ErrorCode& error) = 0;
};
/**
* @brief The initialized method for websocket.
* It needs to be invoked right after websocket instance is allocated.
* @param delegate The delegate which want to receive event from websocket.
* @param url The URL of websocket server.
* @param protocols The websocket protocols that agree with websocket server
* @param caFilePath The ca file path for wss connection
* @return true: Success, false: Failure.
* @lua NA
*/
bool init(const Delegate& delegate,
const std::string& url,
const std::vector<std::string>* protocols = nullptr,
const std::string& caFilePath = "");
/**
* @brief Sends string data to websocket server.
*
* @param message string data.
* @lua sendstring
*/
void send(const std::string& message);
/**
* @brief Sends binary data to websocket server.
*
* @param binaryMsg binary string data.
* @param len the size of binary string data.
* @lua sendstring
*/
void send(const unsigned char* binaryMsg, unsigned int len);
/**
* @brief Closes the connection to server synchronously.
* @note It's a synchronous method, it will not return until websocket thread exits.
*/
void close();
/**
* @brief Closes the connection to server asynchronously.
* @note It's an asynchronous method, it just notifies websocket thread to exit and returns directly,
* If using 'closeAsync' to close websocket connection,
* be careful of not using destructed variables in the callback of 'onClose'.
*/
void closeAsync();
/**
* @brief Closes the connection to server asynchronously.
* @note It's an asynchronous method, it just notifies websocket thread to exit and returns directly,
* If using 'closeAsync' to close websocket connection,
* be careful of not using destructed variables in the callback of 'onClose'.
* @param code close reason
* @param reason reason text description
*/
void closeAsync(int code, const std::string &reason);
/**
* @brief Gets current state of connection.
* @return State the state value could be State::CONNECTING, State::OPEN, State::CLOSING or State::CLOSED
*/
State getReadyState() const;
/**
* @brief Gets the URL of websocket connection.
*/
const std::string& getUrl() const;
/**
* @brief Returns the number of bytes of data that have been queued using calls to send() but not yet transmitted to the network.
*/
size_t getBufferedAmount() const;
/**
* @brief Returns the extensions selected by the server.
*/
std::string getExtensions() const;
/**
* @brief Gets the protocol selected by websocket server.
*/
const std::string& getProtocol() const;
Delegate* getDelegate() const;
private:
WebSocketImpl* _impl;
};
} // namespace network {
NS_CC_END
// end group
/// @}

View File

@@ -0,0 +1,858 @@
/****************************************************************************
Copyright (c) 2019 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
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 "base/ccConfig.h"
#if (USE_SOCKET > 0) && (USE_WEBSOCKET_SERVER > 0)
#include <iostream>
#include <cassert>
#include <atomic>
#include "WebSocketServer.h"
#include "platform/CCApplication.h"
#include "base/CCScheduler.h"
#define MAX_MSG_PAYLOAD 2048
#define SEND_BUFF 1024
namespace {
std::atomic_int32_t _aliveServer{0}; //debug info
struct lws_protocols protocols[] = {
{
"", //protocol name
cocos2d::network::WebSocketServer::_websocketServerCallback,
sizeof(int),
MAX_MSG_PAYLOAD
},
{
nullptr, nullptr, 0
}
};
const struct lws_extension exts[] = {
{
"permessage-deflate",
lws_extension_callback_pm_deflate,
"permessage-deflate; client_on_context_takeover; client_max_window_bits"
},
{
"deflate-frame",
lws_extension_callback_pm_deflate,
"deflate-frame"
},
{
nullptr, nullptr, nullptr
}
};
struct AsyncTaskData {
std::mutex mtx;
std::list<std::function<void()> > tasks;
};
// run in server thread loop
void flush_tasks_in_server_loop_cb(uv_async_t* asyn)
{
AsyncTaskData* data = (AsyncTaskData*)asyn->data;
std::lock_guard<std::mutex> guard(data->mtx);
while (!data->tasks.empty())
{
// fetch task, run task
data->tasks.front()();
// drop task
data->tasks.pop_front();
}
}
void init_libuv_async_handle(uv_loop_t* loop, uv_async_t* async)
{
memset(async, 0, sizeof(uv_async_t));
uv_async_init(loop, async, flush_tasks_in_server_loop_cb);
async->data = new AsyncTaskData();
}
// run in game thread, dispatch runnable object into server loop
void schedule_task_into_server_thread_task_queue(uv_async_t* asyn, std::function<void()> func)
{
AsyncTaskData* data = (AsyncTaskData*)asyn->data;
{
std::lock_guard<std::mutex> guard(data->mtx);
data->tasks.emplace_back(func);
}
//notify server thread to invoke `flush_tasks_in_server_loop_cb()`
uv_async_send(asyn);
}
}
namespace cocos2d {
namespace network {
#define RUN_IN_GAMETHREAD(task) \
do { \
cocos2d::Application::getInstance()->getScheduler()->performFunctionInCocosThread([=]() { \
task; \
});\
} while(0)
#define DISPATCH_CALLBACK_IN_GAMETHREAD() do {\
data->setCallback([callback](const std::string& msg) { \
auto wrapper = [callback, msg]() {callback(msg); }; \
RUN_IN_GAMETHREAD(wrapper()); \
}); \
}while(0)
#define RUN_IN_SERVERTHREAD(task) do { \
schedule_task_into_server_thread_task_queue(&_async, [=](){ \
task; \
}); \
} while(0)
//#define LOGE() CCLOG("WSS: %s", __FUNCTION__)
#define LOGE()
DataFrame::DataFrame(const std::string& data) :_isBinary(false)
{
_underlyingData.resize(data.size() + LWS_PRE);
memcpy(getData(), data.c_str(), data.length());
}
DataFrame::DataFrame(const void* data, int len, bool isBinary) :_isBinary(isBinary)
{
_underlyingData.resize(len + LWS_PRE);
memcpy(getData(), data, len);
}
DataFrame::~DataFrame()
{
}
void DataFrame::append(unsigned char* p, int len)
{
_underlyingData.insert(_underlyingData.end(), p, p + len);
}
int DataFrame::slice(unsigned char** p, int len) {
*p = getData() + _consumed;
if (_consumed + len > size()) {
return size() - _consumed;
}
return len;
}
int DataFrame::consume(int len) {
_consumed = len + _consumed > size() ? size() : len + _consumed;
return _consumed;
}
int DataFrame::remain() const {
return size() - _consumed;
}
std::string DataFrame::toString()
{
return std::string((char*)getData(), size());
}
WebSocketServer::WebSocketServer()
{
_aliveServer.fetch_add(1);
}
WebSocketServer::~WebSocketServer()
{
_aliveServer.fetch_sub(1);
destroyContext();
}
bool WebSocketServer::close(std::function<void(const std::string & errorMsg)> callback)
{
_onclose_cb = callback;
if(_ctx)
lws_libuv_stop(_ctx);
return true;
}
void WebSocketServer::closeAsync(std::function<void(const std::string & errorMsg)> callback)
{
RUN_IN_SERVERTHREAD(this->close(callback));
}
void WebSocketServer::listen(std::shared_ptr<WebSocketServer> server, int port, const std::string& host, std::function<void(const std::string & errorMsg)> callback)
{
auto tryLock = server->_serverLock.try_lock();
if (!tryLock) {
CCLOG("websocketserver is already running!");
if (callback) {
RUN_IN_GAMETHREAD(callback("Error: Server is already running!"));
}
return;
}
server->_serverState = ServerThreadState::RUNNING;
//lws_set_log_level(-1, nullptr);
if (server->_ctx) {
server->destroyContext();
if (callback) {
RUN_IN_GAMETHREAD(callback("Error: lws_context already created!"));
}
RUN_IN_GAMETHREAD(if (server->_onerror) server->_onerror("websocket listen error!"));
server->_serverState = ServerThreadState::ST_ERROR;
server->_serverLock.unlock();
return;
}
server->_host = host;
struct lws_context_creation_info info;
memset(&info, 0, sizeof(info));
info.port = port;
info.iface = server->_host.empty() ? nullptr : server->_host.c_str();
info.protocols = protocols;
info.gid = -1;
info.uid = -1;
info.extensions = exts;
info.options = LWS_SERVER_OPTION_VALIDATE_UTF8
|LWS_SERVER_OPTION_LIBUV
|LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME
;
info.timeout_secs = 60; //
info.max_http_header_pool = 1;
info.user = server.get();
server->_ctx = lws_create_context(&info);
if (!server->_ctx) {
if (callback) {
RUN_IN_GAMETHREAD(callback("Error: Failed to create lws_context!"));
}
RUN_IN_GAMETHREAD(if (server->_onerror)server->_onerror("websocket listen error!"));
server->_serverState = ServerThreadState::ST_ERROR;
server->_serverLock.unlock();
return;
}
uv_loop_t* loop = nullptr;
if (lws_uv_initloop(server->_ctx, loop, 0)) {
if (callback) {
RUN_IN_GAMETHREAD(callback("Error: Failed to create libuv loop!"));
}
RUN_IN_GAMETHREAD(if (server->_onerror)server->_onerror("websocket listen error, failed to create libuv loop!"));
server->_serverState = ServerThreadState::ST_ERROR;
server->_serverLock.unlock();
server->destroyContext();
return;
}
loop = lws_uv_getloop(server->_ctx, 0);
init_libuv_async_handle(loop, &server->_async);
RUN_IN_GAMETHREAD(if(server->_onlistening)server->_onlistening(""));
RUN_IN_GAMETHREAD(if(server->_onbegin)server->_onbegin());
RUN_IN_GAMETHREAD(if(callback)callback(""));
lws_libuv_run(server->_ctx, 0);
uv_close((uv_handle_t*)&server->_async, nullptr);
RUN_IN_GAMETHREAD(if(server->_onclose)server->_onclose(""));
RUN_IN_GAMETHREAD(if(server->_onclose_cb)server->_onclose_cb(""));
RUN_IN_GAMETHREAD(if(server->_onend)server->_onend());
server->_serverState = ServerThreadState::STOPPED;
server->destroyContext();
server->_serverLock.unlock();
return;
}
void WebSocketServer::listenAsync(std::shared_ptr<WebSocketServer>& server,int port, const std::string& host, std::function<void(const std::string & errorMsg)> callback)
{
std::thread([=]() {
WebSocketServer::listen(server, port, host, callback);
}).detach();
}
std::vector<std::shared_ptr<WebSocketServerConnection>> WebSocketServer::getConnections() const
{
std::lock_guard<std::mutex> guard(_connsMtx);
std::vector<std::shared_ptr<WebSocketServerConnection> > ret;
for (auto itr : _conns) {
ret.emplace_back(itr.second);
}
return ret;
}
void WebSocketServer::onCreateClient(struct lws* wsi)
{
LOGE();
std::shared_ptr<WebSocketServerConnection> conn = std::make_shared<WebSocketServerConnection>(wsi);
//char ip[221] = { 0 };
//char addr[221] = { 0 };
//lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), ip, 220, addr, 220);
//lws_get_peer_simple(wsi, ip, 220);
{
std::lock_guard<std::mutex> guard(_connsMtx);
_conns.emplace(wsi, conn);
}
RUN_IN_GAMETHREAD(if(_onconnection) _onconnection(conn));
conn->onConnected();
}
void WebSocketServer::onDestroyClient(struct lws* wsi)
{
LOGE();
std::shared_ptr<WebSocketServerConnection> conn = findConnection(wsi);
if( conn ){
conn->onDestroyClient();
}
std::lock_guard<std::mutex> guard(_connsMtx);
_conns.erase(wsi);
}
void WebSocketServer::onCloseClient(struct lws* wsi)
{
LOGE();
std::shared_ptr<WebSocketServerConnection> conn = findConnection(wsi);
if (conn) {
conn->onClientCloseInit();
}
}
void WebSocketServer::onCloseClientInit(struct lws* wsi, void* in, int len)
{
LOGE();
int16_t code;
char* msg = nullptr;
std::shared_ptr<WebSocketServerConnection> conn = findConnection(wsi);
if (conn && len > 2) {
code = ntohs(*(int16_t*)in);
msg = (char*)in + sizeof(code);
std::string cp(msg, len - sizeof(code));
conn->onClientCloseInit(code, cp);
}
}
void WebSocketServer::onClientReceive(struct lws* wsi, void* in, int len)
{
LOGE();
std::shared_ptr<WebSocketServerConnection> conn = findConnection(wsi);
if (conn)
{
conn->onDataReceive(in, len);
}
}
int WebSocketServer::onServerWritable(struct lws* wsi)
{
LOGE();
std::shared_ptr<WebSocketServerConnection> conn = findConnection(wsi);
if (conn)
{
return conn->onDrainData();
}
return 0;
}
void WebSocketServer::onClientHTTP(struct lws* wsi)
{
LOGE();
std::shared_ptr<WebSocketServerConnection> conn = findConnection(wsi);
if(conn)
{
conn->onHTTP();
}
}
std::shared_ptr<WebSocketServerConnection> WebSocketServer::findConnection(struct lws *wsi) {
std::shared_ptr<WebSocketServerConnection> conn;
{
std::lock_guard<std::mutex> guard(_connsMtx);
auto itr = _conns.find(wsi);
if (itr != _conns.end()) {
conn = itr->second;
}
}
return conn;
}
void WebSocketServer::destroyContext()
{
if (_ctx) {
lws_context_destroy(_ctx);
lws_context_destroy2(_ctx);
_ctx = nullptr;
}
if (_async.data) {
delete (AsyncTaskData*)_async.data;
_async.data = nullptr;
}
}
WebSocketServerConnection::WebSocketServerConnection(struct lws* wsi) : _wsi(wsi)
{
LOGE();
uv_loop_t* loop = lws_uv_getloop(lws_get_context(wsi), 0);
init_libuv_async_handle(loop, &_async);
}
WebSocketServerConnection::~WebSocketServerConnection()
{
LOGE();
if (_async.data) {
delete (AsyncTaskData*)_async.data;
}
}
bool WebSocketServerConnection::send(std::shared_ptr<DataFrame> data)
{
_sendQueue.emplace_back(data);
onDrainData();
return true;
}
void WebSocketServerConnection::sendTextAsync(const std::string& text, std::function<void(const std::string&)> callback)
{
LOGE();
std::shared_ptr<DataFrame> data = std::make_shared<DataFrame>(text);
if (callback) {
DISPATCH_CALLBACK_IN_GAMETHREAD();
}
RUN_IN_SERVERTHREAD(this->send(data));
}
void WebSocketServerConnection::sendBinaryAsync(const void* in, size_t len, std::function<void(const std::string&)> callback)
{
LOGE();
std::shared_ptr<DataFrame> data = std::make_shared<DataFrame>(in, len);
if (callback) {
DISPATCH_CALLBACK_IN_GAMETHREAD();
}
RUN_IN_SERVERTHREAD(this->send(data));
}
bool WebSocketServerConnection::close(int code, std::string message)
{
LOGE();
if (!_wsi) return false;
_readyState = ReadyState::CLOSING;
_closeReason = message;
_closeCode = code;
onClientCloseInit();
//trigger callback to return -1 which indicates connection closed
lws_callback_on_writable(_wsi);
return true;
}
void WebSocketServerConnection::closeAsync(int code, std::string message)
{
LOGE();
RUN_IN_SERVERTHREAD(this->close(code, message));
}
void WebSocketServerConnection::onConnected()
{
_readyState = ReadyState::OPEN;
RUN_IN_GAMETHREAD(if(_onconnect)_onconnect());
}
void WebSocketServerConnection::onDataReceive(void* in, int len)
{
LOGE();
bool isFinal = (bool)lws_is_final_fragment(_wsi);
bool isBinary = (bool)lws_frame_is_binary(_wsi);
if (!_prevPkg) {
_prevPkg = std::make_shared<DataFrame>(in, len, isBinary);
}
else {
_prevPkg->append((unsigned char*)in, len);
}
if (isFinal) {
//trigger event
std::shared_ptr<DataFrame> fullpkg = _prevPkg;
if (isBinary)
{
RUN_IN_GAMETHREAD(if(_onbinary)_onbinary(fullpkg));
}
if (!isBinary)
{
RUN_IN_GAMETHREAD(if(_ontext)_ontext(fullpkg));
}
RUN_IN_GAMETHREAD(if(_ondata)_ondata(fullpkg));
_prevPkg.reset();
}
}
int WebSocketServerConnection::onDrainData()
{
LOGE();
if (!_wsi) return -1;
if (_closed) return -1;
if (_readyState == ReadyState::CLOSING) {
return -1;
}
if (_readyState != ReadyState::OPEN) return 0;
unsigned char* p = nullptr;
int send_len = 0;
int finish_len = 0;
int flags = 0;
std::vector<char> buff(SEND_BUFF + LWS_PRE);
if (!_sendQueue.empty())
{
std::shared_ptr<DataFrame> frag = _sendQueue.front();
send_len = frag->slice(&p, SEND_BUFF);
if(frag->isFront())
{
if (frag->isBinary())
{
flags |= LWS_WRITE_BINARY;
}
if (frag->isString())
{
flags |= LWS_WRITE_TEXT;
}
}
if (frag->remain() != send_len)
{
// remain bytes > 0
// not FIN
flags |= LWS_WRITE_NO_FIN;
}
if (!frag->isFront()) {
flags |= LWS_WRITE_CONTINUATION;
}
finish_len = lws_write(_wsi, p, send_len, (lws_write_protocol)flags);
if (finish_len == 0)
{
frag->onFinish("Connection Closed");
return -1;
}
else if (finish_len < 0)
{
frag->onFinish("Send Error!");
return -1;
}
else
{
frag->consume(finish_len);
}
if (frag->remain() == 0) {
frag->onFinish("");
_sendQueue.pop_front();
}
lws_callback_on_writable(_wsi);
}
return 0;
}
void WebSocketServerConnection::onHTTP()
{
if (!_wsi) return;
_headers.clear();
int n = 0, len;
std::vector<char> buf(256);
const char* c;
do {
lws_token_indexes idx = static_cast<lws_token_indexes>(n);
c = (const char*)lws_token_to_string(idx);
if (!c) {
n++;
break;
}
len = lws_hdr_total_length(_wsi, idx);
if (!len) {
n++;
continue;
}
else if (len + 1 > buf.size())
{
buf.resize(len + 1);
}
lws_hdr_copy(_wsi, buf.data(), buf.size(), idx);
buf[len] = '\0';
_headers.emplace(std::string(c), std::string(buf.data()));
n++;
} while (c);
}
void WebSocketServerConnection::onClientCloseInit(int code, const std::string& msg)
{
_closeCode = code;
_closeReason = msg;
}
void WebSocketServerConnection::onClientCloseInit()
{
if (_closed) return;
lws_close_reason(_wsi, (lws_close_status)_closeCode, (unsigned char*)_closeReason.c_str(), _closeReason.length());
_closed = true;
}
void WebSocketServerConnection::onDestroyClient()
{
_readyState = ReadyState::CLOSED;
//on wsi destroied
if (_wsi)
{
RUN_IN_GAMETHREAD(if(_onclose)_onclose(_closeCode, _closeReason));
RUN_IN_GAMETHREAD(if(_onend) _onend());
uv_close((uv_handle_t*)&_async, nullptr);
}
}
std::vector<std::string> WebSocketServerConnection::getProtocols() {
std::vector<std::string> ret;
if (_wsi) {
//TODO cause abort
//const struct lws_protocols* protos = lws_get_protocol(_wsi);
//while (protos && protos->name != nullptr)
//{
// ret.emplace_back(protos->name);
// protos++;
//}
}
return ret;
}
std::map<std::string, std::string> WebSocketServerConnection::getHeaders()
{
if (!_wsi) return {};
return _headers;
}
int WebSocketServer::_websocketServerCallback(struct lws* wsi, enum lws_callback_reasons reason,
void* user, void* in, size_t len)
{
int ret = 0;
WebSocketServer* server = nullptr;
lws_context* ctx = nullptr;
if (wsi)
ctx = lws_get_context(wsi);
if (ctx)
server = static_cast<WebSocketServer*>(lws_context_user(ctx));
if (!server) {
return 0;
}
switch (reason)
{
case LWS_CALLBACK_ESTABLISHED:
break;
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
break;
case LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH:
break;
case LWS_CALLBACK_CLIENT_ESTABLISHED:
break;
case LWS_CALLBACK_CLOSED:
break;
case LWS_CALLBACK_CLOSED_HTTP:
break;
case LWS_CALLBACK_RECEIVE:
server->onClientReceive(wsi, in, len);
break;
case LWS_CALLBACK_RECEIVE_PONG:
break;
case LWS_CALLBACK_CLIENT_RECEIVE:
server->onClientReceive(wsi, in, len);
break;
case LWS_CALLBACK_CLIENT_RECEIVE_PONG:
break;
case LWS_CALLBACK_CLIENT_WRITEABLE:
//ret = server->onClientWritable(wsi);
break;
case LWS_CALLBACK_SERVER_WRITEABLE:
ret = server->onServerWritable(wsi);
break;
case LWS_CALLBACK_HTTP:
break;
case LWS_CALLBACK_HTTP_BODY:
break;
case LWS_CALLBACK_HTTP_BODY_COMPLETION:
break;
case LWS_CALLBACK_HTTP_FILE_COMPLETION:
break;
case LWS_CALLBACK_HTTP_WRITEABLE:
break;
case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
break;
case LWS_CALLBACK_FILTER_HTTP_CONNECTION:
break;
case LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED:
break;
case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
break;
case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS:
break;
case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS:
break;
case LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION:
break;
case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
break;
case LWS_CALLBACK_CONFIRM_EXTENSION_OKAY:
break;
case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED:
break;
case LWS_CALLBACK_PROTOCOL_INIT:
break;
case LWS_CALLBACK_PROTOCOL_DESTROY:
break;
case LWS_CALLBACK_WSI_CREATE:
server->onCreateClient(wsi);
break;
case LWS_CALLBACK_WSI_DESTROY:
server->onDestroyClient(wsi);
break;
case LWS_CALLBACK_GET_THREAD_ID:
break;
case LWS_CALLBACK_ADD_POLL_FD:
break;
case LWS_CALLBACK_DEL_POLL_FD:
break;
case LWS_CALLBACK_CHANGE_MODE_POLL_FD:
break;
case LWS_CALLBACK_LOCK_POLL:
break;
case LWS_CALLBACK_UNLOCK_POLL:
break;
case LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY:
break;
case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE:
server->onCloseClientInit(wsi, in, len);
break;
case LWS_CALLBACK_WS_EXT_DEFAULTS:
break;
case LWS_CALLBACK_CGI:
break;
case LWS_CALLBACK_CGI_TERMINATED:
break;
case LWS_CALLBACK_CGI_STDIN_DATA:
break;
case LWS_CALLBACK_CGI_STDIN_COMPLETED:
break;
case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
break;
case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
server->onCloseClient(wsi);
break;
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
break;
case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
break;
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
break;
case LWS_CALLBACK_HTTP_BIND_PROTOCOL:
break;
case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
break;
case LWS_CALLBACK_CHECK_ACCESS_RIGHTS:
break;
case LWS_CALLBACK_PROCESS_HTML:
break;
case LWS_CALLBACK_ADD_HEADERS:
server->onClientHTTP(wsi);
break;
case LWS_CALLBACK_SESSION_INFO:
break;
case LWS_CALLBACK_GS_EVENT:
break;
case LWS_CALLBACK_HTTP_PMO:
break;
case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE:
break;
case LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION:
break;
case LWS_CALLBACK_RAW_RX:
break;
case LWS_CALLBACK_RAW_CLOSE:
break;
case LWS_CALLBACK_RAW_WRITEABLE:
break;
case LWS_CALLBACK_RAW_ADOPT:
break;
case LWS_CALLBACK_RAW_ADOPT_FILE:
break;
case LWS_CALLBACK_RAW_RX_FILE:
break;
case LWS_CALLBACK_RAW_WRITEABLE_FILE:
break;
case LWS_CALLBACK_RAW_CLOSE_FILE:
break;
case LWS_CALLBACK_SSL_INFO:
break;
case LWS_CALLBACK_CHILD_WRITE_VIA_PARENT:
break;
case LWS_CALLBACK_CHILD_CLOSING:
break;
case LWS_CALLBACK_CGI_PROCESS_ATTACH:
break;
case LWS_CALLBACK_USER:
break;
default:
break;
}
return ret;
}
} // namespace network
} // namespace cocos2d
#endif

View File

@@ -0,0 +1,320 @@
/****************************************************************************
Copyright (c) 2019 Xiamen Yaji Software Co., Ltd.
http://www.cocos.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
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.
****************************************************************************/
#pragma once
#include "base/ccConfig.h"
#if (USE_SOCKET > 0) && (USE_WEBSOCKET_SERVER > 0)
#include "uv.h"
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
#include <memory>
#include <map>
#include <list>
#include <unordered_map>
#include <thread>
#include <mutex>
#include <atomic>
#include "platform/CCPlatformDefine.h"
#include "websockets/libwebsockets.h"
namespace cocos2d {
namespace network {
class WebSocketServer;
class WebSocketServerConnection;
/**
* receive/send data buffer with reserved bytes
*/
class DataFrame {
public:
DataFrame(const std::string& data);
DataFrame(const void* data, int len, bool isBinary = true);
virtual ~DataFrame();
void append(unsigned char* p, int len);
int slice(unsigned char** p, int len);
int consume(int len);
int remain() const;
inline bool isBinary() const { return _isBinary; }
inline bool isString() const { return !_isBinary; }
inline bool isFront() const { return _consumed == 0; }
void setCallback(std::function<void(const std::string&)> callback)
{
_callback = callback;
}
void onFinish(const std::string& message) {
if (_callback) {
_callback(message);
}
}
inline int size() const { return _underlyingData.size() - LWS_PRE; }
std::string toString();
unsigned char* getData() { return _underlyingData.data() + LWS_PRE; }
private:
std::vector<unsigned char> _underlyingData;
int _consumed = 0;
bool _isBinary = false;
std::function<void(const std::string&) > _callback;
};
class CC_DLL WebSocketServerConnection {
public:
WebSocketServerConnection(struct lws* wsi);
virtual ~WebSocketServerConnection();
enum ReadyState {
CONNECTING = 1,
OPEN = 2,
CLOSING = 3,
CLOSED = 4
};
void sendTextAsync(const std::string&, std::function<void(const std::string&)> callback);
void sendBinaryAsync(const void*, size_t len, std::function<void(const std::string&)> callback);
void closeAsync(int code, std::string reasson);
/** stream is not implemented*/
//bool beginBinary();
/** should implement in JS */
// bool send();
// bool sendPing(std::string)
//int getSocket();
//std::shared_ptr<WebSocketServer>& getServer();
//std::string& getPath();
inline int getReadyState() const {
return (int)_readyState;
}
std::map<std::string, std::string> getHeaders();
std::vector<std::string> getProtocols();
inline void setOnClose(std::function<void(int, const std::string&)> cb)
{
_onclose = cb;
}
inline void setOnError(std::function<void(const std::string&)> cb)
{
_onerror = cb;
}
inline void setOnText(std::function<void(std::shared_ptr<DataFrame>)> cb)
{
_ontext = cb;
}
inline void setOnBinary(std::function<void(std::shared_ptr<DataFrame>)> cb)
{
_onbinary = cb;
}
inline void setOnData(std::function<void(std::shared_ptr<DataFrame>)> cb)
{
_ondata = cb;
}
inline void setOnConnect(std::function<void()> cb)
{
_onconnect = cb;
}
inline void setOnEnd(std::function<void()> cb)
{
_onend = cb;
}
void onClientCloseInit();
inline void setData(void* d) { _data = d; }
inline void* getData() const { return _data; }
private:
bool send(std::shared_ptr<DataFrame> data);
bool close(int code, std::string reasson);
inline void scheduleSend() {
if (_wsi)
lws_callback_on_writable(_wsi);
}
void onConnected();
void onDataReceive(void* in, int len);
int onDrainData();
void onHTTP();
void onClientCloseInit(int code, const std::string& msg);
void onDestroyClient();
struct lws* _wsi = nullptr;
std::map<std::string, std::string> _headers;
std::list<std::shared_ptr<DataFrame>> _sendQueue;
std::shared_ptr<DataFrame> _prevPkg;
bool _closed = false;
std::string _closeReason = "close connection";
int _closeCode = 1000;
std::atomic<ReadyState> _readyState{ReadyState::CLOSED};
// Attention: do not reference **this** in callbacks
std::function<void(int, const std::string&)> _onclose;
std::function<void(const std::string&)> _onerror;
std::function<void(std::shared_ptr<DataFrame>)> _ontext;
std::function<void(std::shared_ptr<DataFrame>)> _onbinary;
std::function<void(std::shared_ptr<DataFrame>)> _ondata;
std::function<void()> _onconnect;
std::function<void()> _onend;
uv_async_t _async = {0};
void* _data = nullptr;
friend class WebSocketServer;
};
class CC_DLL WebSocketServer {
public:
WebSocketServer();
virtual ~WebSocketServer();
static void listenAsync(std::shared_ptr<WebSocketServer>& server, int port, const std::string& host, std::function<void(const std::string & errorMsg)> callback);
void closeAsync(std::function<void(const std::string & errorMsg)> callback = nullptr);
std::vector<std::shared_ptr<WebSocketServerConnection>> getConnections() const;
void setOnListening(std::function<void(const std::string&)> cb)
{
_onlistening = cb;
}
void setOnError(std::function<void(const std::string&)> cb)
{
_onerror = cb;
}
void setOnClose(std::function<void(const std::string&)> cb)
{
_onclose = cb;
}
void setOnConnection(std::function<void(std::shared_ptr<WebSocketServerConnection>)> cb)
{
_onconnection = cb;
}
inline void setOnEnd(std::function<void()> cb)
{
_onend = cb;
}
inline void setOnBegin(std::function<void()> cb)
{
_onbegin = cb;
}
inline void setData(void* d) { _data = d; }
inline void* getData() const { return _data; }
protected:
static void listen(std::shared_ptr<WebSocketServer> server, int port, const std::string& host, std::function<void(const std::string & errorMsg)> callback);
bool close(std::function<void(const std::string & errorMsg)> callback = nullptr);
void onCreateClient(struct lws* wsi);
void onDestroyClient(struct lws* wsi);
void onCloseClient(struct lws* wsi);
void onCloseClientInit(struct lws* wsi, void* in, int len);
void onClientReceive(struct lws* wsi, void* in, int len);
int onServerWritable(struct lws* wsi);
void onClientHTTP(struct lws* wsi);
private:
std::shared_ptr<WebSocketServerConnection> findConnection(struct lws *wsi);
void destroyContext();
std::string _host;
lws_context* _ctx = nullptr;
uv_async_t _async = {0};
mutable std::mutex _connsMtx;
std::unordered_map<struct lws*, std::shared_ptr<WebSocketServerConnection> > _conns;
// Attention: do not reference **this** in callbacks
std::function<void(const std::string&)> _onlistening;
std::function<void(const std::string&)> _onerror;
std::function<void(const std::string&)> _onclose;
std::function<void(const std::string&)> _onclose_cb;
std::function<void()> _onend;
std::function<void()> _onbegin;
std::function<void(std::shared_ptr<WebSocketServerConnection>)> _onconnection;
enum class ServerThreadState{
NOT_BOOTED,
ST_ERROR,
RUNNING,
STOPPED,
};
std::atomic<ServerThreadState> _serverState {ServerThreadState::NOT_BOOTED};
std::mutex _serverLock;
void* _data = nullptr;
public:
static int _websocketServerCallback(struct lws* wsi, enum lws_callback_reasons reason,
void* user, void* in, size_t len);
};
}
}
#endif //#if (USE_SOCKET > 0) && (USE_WEBSOCKET_SERVER > 0)