2019-03-31 01:37:06 +08:00
|
|
|
// Copyright (c) 2016-2018 Easy2D - Nomango
|
|
|
|
|
//
|
|
|
|
|
// 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.
|
|
|
|
|
|
2019-04-05 16:06:32 +08:00
|
|
|
#include "../easy2d-network.h"
|
2019-03-31 01:37:06 +08:00
|
|
|
#include <thread>
|
2019-03-31 23:09:49 +08:00
|
|
|
#include <codecvt>
|
2019-03-31 01:37:06 +08:00
|
|
|
|
2019-04-05 16:06:32 +08:00
|
|
|
// CURL
|
|
|
|
|
#include "../third-party/curl/curl.h"
|
|
|
|
|
|
2019-03-31 01:37:06 +08:00
|
|
|
#pragma comment(lib, "libcurl.lib")
|
|
|
|
|
|
|
|
|
|
namespace
|
|
|
|
|
{
|
|
|
|
|
using namespace easy2d;
|
|
|
|
|
using namespace easy2d::network;
|
|
|
|
|
|
|
|
|
|
size_t write_data(void* buffer, size_t size, size_t nmemb, void* userp)
|
|
|
|
|
{
|
|
|
|
|
std::string* recv_buffer = (std::string*)userp;
|
|
|
|
|
size_t total = size * nmemb;
|
|
|
|
|
|
|
|
|
|
// add data to the end of recv_buffer
|
|
|
|
|
// write data maybe called more than once in a single request
|
|
|
|
|
recv_buffer->append((char*)buffer, total);
|
|
|
|
|
|
|
|
|
|
return total;
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-31 23:09:49 +08:00
|
|
|
std::string convert_to_utf8(String const& str)
|
|
|
|
|
{
|
|
|
|
|
std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
|
|
|
|
|
std::string result;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
result = utf8_conv.to_bytes(str.c_str());
|
|
|
|
|
}
|
|
|
|
|
catch (std::range_error&)
|
|
|
|
|
{
|
|
|
|
|
// bad conversion
|
|
|
|
|
result = str.to_string();
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String convert_from_utf8(std::string const& str)
|
|
|
|
|
{
|
|
|
|
|
std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8_conv;
|
|
|
|
|
String result;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
result = utf8_conv.from_bytes(str);
|
|
|
|
|
}
|
|
|
|
|
catch (std::range_error&)
|
|
|
|
|
{
|
|
|
|
|
// bad conversion
|
|
|
|
|
result = str;
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-31 01:37:06 +08:00
|
|
|
class Curl
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
Curl()
|
|
|
|
|
: curl_(curl_easy_init())
|
|
|
|
|
, curl_headers_(nullptr)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~Curl()
|
|
|
|
|
{
|
|
|
|
|
if (curl_)
|
|
|
|
|
{
|
|
|
|
|
curl_easy_cleanup(curl_);
|
|
|
|
|
curl_ = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (curl_headers_)
|
|
|
|
|
{
|
|
|
|
|
curl_slist_free_all(curl_headers_);
|
|
|
|
|
curl_headers_ = nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Init(HttpClient* client, Array<std::string> const& headers, std::string const& url, std::string* response_data, std::string* response_header, char* error_buffer)
|
|
|
|
|
{
|
|
|
|
|
if (!SetOption(CURLOPT_ERRORBUFFER, error_buffer))
|
|
|
|
|
return false;
|
|
|
|
|
if (!SetOption(CURLOPT_TIMEOUT, client->GetTimeoutForRead()))
|
|
|
|
|
return false;
|
|
|
|
|
if (!SetOption(CURLOPT_CONNECTTIMEOUT, client->GetTimeoutForConnect()))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const auto ssl_ca_file = client->GetSSLVerification().to_string();
|
|
|
|
|
if (ssl_ca_file.empty()) {
|
|
|
|
|
if (!SetOption(CURLOPT_SSL_VERIFYPEER, 0L))
|
|
|
|
|
return false;
|
|
|
|
|
if (!SetOption(CURLOPT_SSL_VERIFYHOST, 0L))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
if (!SetOption(CURLOPT_SSL_VERIFYPEER, 1L))
|
|
|
|
|
return false;
|
|
|
|
|
if (!SetOption(CURLOPT_SSL_VERIFYHOST, 2L))
|
|
|
|
|
return false;
|
|
|
|
|
if (!SetOption(CURLOPT_CAINFO, ssl_ca_file.c_str()))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!SetOption(CURLOPT_NOSIGNAL, 1L))
|
|
|
|
|
return false;
|
|
|
|
|
if (!SetOption(CURLOPT_ACCEPT_ENCODING, ""))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// set request headers
|
|
|
|
|
if (!headers.empty())
|
|
|
|
|
{
|
|
|
|
|
for (const auto& header : headers)
|
|
|
|
|
{
|
|
|
|
|
curl_headers_ = curl_slist_append(curl_headers_, header.c_str());
|
|
|
|
|
}
|
|
|
|
|
if (!SetOption(CURLOPT_HTTPHEADER, curl_headers_))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return SetOption(CURLOPT_URL, url.c_str())
|
|
|
|
|
&& SetOption(CURLOPT_WRITEFUNCTION, write_data)
|
|
|
|
|
&& SetOption(CURLOPT_WRITEDATA, response_data)
|
|
|
|
|
&& SetOption(CURLOPT_HEADERFUNCTION, write_data)
|
|
|
|
|
&& SetOption(CURLOPT_HEADERDATA, response_header);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Perform(long* response_code)
|
|
|
|
|
{
|
|
|
|
|
if (CURLE_OK != curl_easy_perform(curl_))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
CURLcode code = curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, response_code);
|
2019-03-31 13:09:31 +08:00
|
|
|
return code == CURLE_OK && (*response_code >= 200 && *response_code < 300);
|
2019-03-31 01:37:06 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename ..._Args>
|
|
|
|
|
bool SetOption(CURLoption option, _Args&&... args)
|
|
|
|
|
{
|
|
|
|
|
return CURLE_OK == curl_easy_setopt(curl_, option, std::forward<_Args>(args)...);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public:
|
2019-03-31 13:09:31 +08:00
|
|
|
static inline bool GetRequest(
|
|
|
|
|
HttpClient* client,
|
|
|
|
|
Array<std::string> const& headers,
|
|
|
|
|
std::string const& url,
|
|
|
|
|
long* response_code,
|
|
|
|
|
std::string* response_data,
|
|
|
|
|
std::string* response_header,
|
|
|
|
|
char* error_buffer)
|
2019-03-31 01:37:06 +08:00
|
|
|
{
|
|
|
|
|
Curl curl;
|
2019-03-31 13:09:31 +08:00
|
|
|
return curl.Init(client, headers, url, response_data, response_header, error_buffer)
|
2019-03-31 01:37:06 +08:00
|
|
|
&& curl.SetOption(CURLOPT_FOLLOWLOCATION, true)
|
|
|
|
|
&& curl.Perform(response_code);
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-31 13:09:31 +08:00
|
|
|
static inline bool PostRequest(
|
|
|
|
|
HttpClient* client,
|
|
|
|
|
Array<std::string> const& headers,
|
|
|
|
|
std::string const& url,
|
|
|
|
|
std::string const& request_data,
|
|
|
|
|
long* response_code,
|
|
|
|
|
std::string* response_data,
|
|
|
|
|
std::string* response_header,
|
|
|
|
|
char* error_buffer)
|
2019-03-31 01:37:06 +08:00
|
|
|
{
|
|
|
|
|
Curl curl;
|
2019-03-31 13:09:31 +08:00
|
|
|
return curl.Init(client, headers, url, response_data, response_header, error_buffer)
|
2019-03-31 01:37:06 +08:00
|
|
|
&& curl.SetOption(CURLOPT_POST, 1)
|
|
|
|
|
&& curl.SetOption(CURLOPT_POSTFIELDS, request_data.c_str())
|
|
|
|
|
&& curl.SetOption(CURLOPT_POSTFIELDSIZE, request_data.size())
|
|
|
|
|
&& curl.Perform(response_code);
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-31 13:09:31 +08:00
|
|
|
static inline bool PutRequest(
|
|
|
|
|
HttpClient* client,
|
|
|
|
|
Array<std::string> const& headers,
|
|
|
|
|
std::string const& url,
|
|
|
|
|
std::string const& request_data,
|
|
|
|
|
long* response_code,
|
|
|
|
|
std::string* response_data,
|
|
|
|
|
std::string* response_header,
|
|
|
|
|
char* error_buffer)
|
2019-03-31 01:37:06 +08:00
|
|
|
{
|
|
|
|
|
Curl curl;
|
2019-03-31 13:09:31 +08:00
|
|
|
return curl.Init(client, headers, url, response_data, response_header, error_buffer)
|
2019-03-31 01:37:06 +08:00
|
|
|
&& curl.SetOption(CURLOPT_CUSTOMREQUEST, "PUT")
|
|
|
|
|
&& curl.SetOption(CURLOPT_POSTFIELDS, request_data.c_str())
|
|
|
|
|
&& curl.SetOption(CURLOPT_POSTFIELDSIZE, request_data.size())
|
|
|
|
|
&& curl.Perform(response_code);
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-31 13:09:31 +08:00
|
|
|
static inline bool DeleteRequest(
|
|
|
|
|
HttpClient* client,
|
|
|
|
|
Array<std::string> const& headers,
|
|
|
|
|
std::string const& url,
|
|
|
|
|
long* response_code,
|
|
|
|
|
std::string* response_data,
|
|
|
|
|
std::string* response_header,
|
|
|
|
|
char* error_buffer)
|
2019-03-31 01:37:06 +08:00
|
|
|
{
|
|
|
|
|
Curl curl;
|
2019-03-31 13:09:31 +08:00
|
|
|
return curl.Init(client, headers, url, response_data, response_header, error_buffer)
|
2019-03-31 01:37:06 +08:00
|
|
|
&& curl.SetOption(CURLOPT_CUSTOMREQUEST, "DELETE")
|
|
|
|
|
&& curl.SetOption(CURLOPT_FOLLOWLOCATION, true)
|
|
|
|
|
&& curl.Perform(response_code);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
CURL* curl_;
|
|
|
|
|
curl_slist* curl_headers_;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace easy2d
|
|
|
|
|
{
|
|
|
|
|
namespace network
|
|
|
|
|
{
|
|
|
|
|
HttpClient::HttpClient()
|
|
|
|
|
: app_(nullptr)
|
|
|
|
|
, timeout_for_connect_(30000 /* 30 seconds */)
|
|
|
|
|
, timeout_for_read_(60000 /* 60 seconds */)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HttpClient::SetupComponent(Application * app)
|
|
|
|
|
{
|
|
|
|
|
app_ = app;
|
|
|
|
|
|
|
|
|
|
::curl_global_init(CURL_GLOBAL_ALL);
|
|
|
|
|
|
|
|
|
|
std::thread thread(Closure(this, &HttpClient::NetworkThread));
|
|
|
|
|
thread.detach();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HttpClient::DestroyComponent()
|
|
|
|
|
{
|
|
|
|
|
::curl_global_cleanup();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HttpClient::Send(HttpRequestPtr const & request)
|
|
|
|
|
{
|
|
|
|
|
if (!request)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
request_mutex_.lock();
|
|
|
|
|
request_queue_.push(request);
|
|
|
|
|
request_mutex_.unlock();
|
|
|
|
|
|
|
|
|
|
sleep_condition_.notify_one();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HttpClient::NetworkThread()
|
|
|
|
|
{
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
HttpRequestPtr request;
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard<std::mutex> lock(request_mutex_);
|
|
|
|
|
while (request_queue_.empty())
|
|
|
|
|
{
|
|
|
|
|
sleep_condition_.wait(request_mutex_);
|
|
|
|
|
}
|
|
|
|
|
request = request_queue_.front();
|
|
|
|
|
request_queue_.pop();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HttpResponsePtr response = new (std::nothrow) HttpResponse(request);
|
|
|
|
|
Perform(request, response);
|
|
|
|
|
|
|
|
|
|
response_mutex_.lock();
|
|
|
|
|
response_queue_.push(response);
|
|
|
|
|
response_mutex_.unlock();
|
|
|
|
|
|
|
|
|
|
if (app_)
|
|
|
|
|
{
|
|
|
|
|
app_->PreformFunctionInMainThread(Closure(this, &HttpClient::DispatchResponseCallback));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HttpClient::Perform(HttpRequestPtr const & request, HttpResponsePtr const & response)
|
|
|
|
|
{
|
|
|
|
|
bool ok = false;
|
|
|
|
|
long response_code = 0;
|
2019-03-31 13:09:31 +08:00
|
|
|
char error_message[256] = { 0 };
|
|
|
|
|
std::string response_header;
|
|
|
|
|
std::string response_data;
|
|
|
|
|
|
2019-03-31 23:09:49 +08:00
|
|
|
|
|
|
|
|
std::string url = convert_to_utf8(request->GetUrl());
|
|
|
|
|
std::string data = convert_to_utf8(request->GetData());
|
|
|
|
|
|
2019-03-31 13:09:31 +08:00
|
|
|
Array<std::string> headers;
|
|
|
|
|
headers.reserve(request->GetHeaders().size());
|
2019-03-31 23:09:49 +08:00
|
|
|
for (const auto& pair : request->GetHeaders())
|
2019-03-31 13:09:31 +08:00
|
|
|
{
|
2019-03-31 23:09:49 +08:00
|
|
|
headers.push_back(pair.first.to_string() + ":" + pair.second.to_string());
|
2019-03-31 13:09:31 +08:00
|
|
|
}
|
2019-03-31 01:37:06 +08:00
|
|
|
|
|
|
|
|
switch (request->GetType())
|
|
|
|
|
{
|
|
|
|
|
case HttpRequest::Type::Get:
|
2019-03-31 13:09:31 +08:00
|
|
|
ok = Curl::GetRequest(this, headers, url, &response_code, &response_data, &response_header, error_message);
|
2019-03-31 01:37:06 +08:00
|
|
|
break;
|
|
|
|
|
case HttpRequest::Type::Post:
|
2019-03-31 13:09:31 +08:00
|
|
|
ok = Curl::PostRequest(this, headers, url, data, &response_code, &response_data, &response_header, error_message);
|
2019-03-31 01:37:06 +08:00
|
|
|
break;
|
|
|
|
|
case HttpRequest::Type::Put:
|
2019-03-31 13:09:31 +08:00
|
|
|
ok = Curl::PutRequest(this, headers, url, data, &response_code, &response_data, &response_header, error_message);
|
2019-03-31 01:37:06 +08:00
|
|
|
break;
|
|
|
|
|
case HttpRequest::Type::Delete:
|
2019-03-31 13:09:31 +08:00
|
|
|
ok = Curl::DeleteRequest(this, headers, url, &response_code, &response_data, &response_header, error_message);
|
2019-03-31 01:37:06 +08:00
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
E2D_ERROR_LOG(L"HttpClient: unknown request type, only GET, POST, PUT or DELETE is supported");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response->SetResponseCode(response_code);
|
2019-03-31 13:09:31 +08:00
|
|
|
response->SetHeader(response_header);
|
2019-03-31 23:09:49 +08:00
|
|
|
response->SetData(convert_from_utf8(response_data));
|
2019-03-31 01:37:06 +08:00
|
|
|
if (!ok)
|
|
|
|
|
{
|
|
|
|
|
response->SetSucceed(false);
|
|
|
|
|
response->SetError(error_message);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
response->SetSucceed(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void HttpClient::DispatchResponseCallback()
|
|
|
|
|
{
|
|
|
|
|
HttpResponsePtr response;
|
|
|
|
|
|
|
|
|
|
response_mutex_.lock();
|
|
|
|
|
if (!response_queue_.empty())
|
|
|
|
|
{
|
|
|
|
|
response = response_queue_.front();
|
|
|
|
|
response_queue_.pop();
|
|
|
|
|
}
|
|
|
|
|
response_mutex_.unlock();
|
|
|
|
|
|
|
|
|
|
if (response)
|
|
|
|
|
{
|
|
|
|
|
HttpRequestPtr request = response->GetRequest();
|
|
|
|
|
const auto& callback = request->GetResponseCallback();
|
|
|
|
|
|
|
|
|
|
if (callback)
|
|
|
|
|
{
|
|
|
|
|
callback(request, response);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|