mirror of
https://github.com/smallmain/cocos-enhance-kit.git
synced 2025-10-09 11:05:24 +00:00
初始化
This commit is contained in:
319
cocos2d-x/cocos/network/CCDownloader-android.cpp
Normal file
319
cocos2d-x/cocos/network/CCDownloader-android.cpp
Normal 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" {
|
56
cocos2d-x/cocos/network/CCDownloader-android.h
Normal file
56
cocos2d-x/cocos/network/CCDownloader-android.h
Normal 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
|
||||
|
880
cocos2d-x/cocos/network/CCDownloader-curl.cpp
Normal file
880
cocos2d-x/cocos/network/CCDownloader-curl.cpp
Normal 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
|
||||
|
64
cocos2d-x/cocos/network/CCDownloader-curl.h
Normal file
64
cocos2d-x/cocos/network/CCDownloader-curl.h
Normal 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
|
||||
|
198
cocos2d-x/cocos/network/CCDownloader.cpp
Normal file
198
cocos2d-x/cocos/network/CCDownloader.cpp
Normal 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
|
||||
|
120
cocos2d-x/cocos/network/CCDownloader.h
Normal file
120
cocos2d-x/cocos/network/CCDownloader.h
Normal 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
|
||||
|
47
cocos2d-x/cocos/network/CCDownloaderImpl-apple.h
Normal file
47
cocos2d-x/cocos/network/CCDownloaderImpl-apple.h
Normal 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
|
||||
|
714
cocos2d-x/cocos/network/CCDownloaderImpl-apple.mm
Normal file
714
cocos2d-x/cocos/network/CCDownloaderImpl-apple.mm
Normal 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
|
||||
|
76
cocos2d-x/cocos/network/CCIDownloaderImpl.h
Normal file
76
cocos2d-x/cocos/network/CCIDownloaderImpl.h
Normal 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
|
||||
|
72
cocos2d-x/cocos/network/HttpAsynConnection-apple.h
Normal file
72
cocos2d-x/cocos/network/HttpAsynConnection-apple.h
Normal 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__
|
||||
|
230
cocos2d-x/cocos/network/HttpAsynConnection-apple.m
Executable file
230
cocos2d-x/cocos/network/HttpAsynConnection-apple.m
Executable 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)
|
1125
cocos2d-x/cocos/network/HttpClient-android.cpp
Normal file
1125
cocos2d-x/cocos/network/HttpClient-android.cpp
Normal file
File diff suppressed because it is too large
Load Diff
593
cocos2d-x/cocos/network/HttpClient-apple.mm
Normal file
593
cocos2d-x/cocos/network/HttpClient-apple.mm
Normal 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
|
656
cocos2d-x/cocos/network/HttpClient.cpp
Normal file
656
cocos2d-x/cocos/network/HttpClient.cpp
Normal 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
|
||||
|
||||
|
220
cocos2d-x/cocos/network/HttpClient.h
Normal file
220
cocos2d-x/cocos/network/HttpClient.h
Normal 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__
|
||||
|
158
cocos2d-x/cocos/network/HttpCookie.cpp
Normal file
158
cocos2d-x/cocos/network/HttpCookie.cpp
Normal 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;
|
||||
}
|
||||
|
63
cocos2d-x/cocos/network/HttpCookie.h
Normal file
63
cocos2d-x/cocos/network/HttpCookie.h
Normal 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 */
|
||||
|
308
cocos2d-x/cocos/network/HttpRequest.h
Normal file
308
cocos2d-x/cocos/network/HttpRequest.h
Normal 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__
|
||||
|
246
cocos2d-x/cocos/network/HttpResponse.h
Normal file
246
cocos2d-x/cocos/network/HttpResponse.h
Normal 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__
|
||||
|
1232
cocos2d-x/cocos/network/SocketIO.cpp
Normal file
1232
cocos2d-x/cocos/network/SocketIO.cpp
Normal file
File diff suppressed because it is too large
Load Diff
300
cocos2d-x/cocos/network/SocketIO.h
Normal file
300
cocos2d-x/cocos/network/SocketIO.h
Normal 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
|
||||
/// @}
|
377
cocos2d-x/cocos/network/Uri.cpp
Normal file
377
cocos2d-x/cocos/network/Uri.cpp
Normal 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
|
184
cocos2d-x/cocos/network/Uri.h
Normal file
184
cocos2d-x/cocos/network/Uri.h
Normal 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
|
||||
/// @}
|
378
cocos2d-x/cocos/network/WebSocket-apple.mm
Normal file
378
cocos2d-x/cocos/network/WebSocket-apple.mm
Normal 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
|
1555
cocos2d-x/cocos/network/WebSocket-libwebsockets.cpp
Normal file
1555
cocos2d-x/cocos/network/WebSocket-libwebsockets.cpp
Normal file
File diff suppressed because it is too large
Load Diff
261
cocos2d-x/cocos/network/WebSocket.h
Normal file
261
cocos2d-x/cocos/network/WebSocket.h
Normal 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, /** < value 0 */
|
||||
CONNECTION_FAILURE, /** < value 1 */
|
||||
UNKNOWN, /** < value 2 */
|
||||
};
|
||||
|
||||
/**
|
||||
* State enum used to represent the Websocket state.
|
||||
*/
|
||||
enum class State
|
||||
{
|
||||
CONNECTING, /** < value 0 */
|
||||
OPEN, /** < value 1 */
|
||||
CLOSING, /** < value 2 */
|
||||
CLOSED, /** < 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
|
||||
/// @}
|
||||
|
858
cocos2d-x/cocos/network/WebSocketServer.cpp
Normal file
858
cocos2d-x/cocos/network/WebSocketServer.cpp
Normal 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
|
320
cocos2d-x/cocos/network/WebSocketServer.h
Normal file
320
cocos2d-x/cocos/network/WebSocketServer.h
Normal 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)
|
Reference in New Issue
Block a user