2019-04-11 14:40:54 +08:00
|
|
|
// Copyright (c) 2016-2018 Kiwano - Nomango
|
2020-01-21 10:09:55 +08:00
|
|
|
//
|
2019-03-31 01:37:06 +08:00
|
|
|
// 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:
|
2020-01-21 10:09:55 +08:00
|
|
|
//
|
2019-03-31 01:37:06 +08:00
|
|
|
// The above copyright notice and this permission notice shall be included in
|
|
|
|
|
// all copies or substantial portions of the Software.
|
2020-01-21 10:09:55 +08:00
|
|
|
//
|
2019-03-31 01:37:06 +08:00
|
|
|
// 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.
|
|
|
|
|
|
2020-02-20 13:58:17 +08:00
|
|
|
#include <chrono>
|
2020-01-21 11:05:28 +08:00
|
|
|
#include <thread>
|
2020-05-24 11:26:21 +08:00
|
|
|
#include <kiwano/utils/Logger.h>
|
2019-09-30 10:59:04 +08:00
|
|
|
#include <kiwano/platform/Application.h>
|
2020-01-21 11:05:28 +08:00
|
|
|
#include <kiwano-network/HttpRequest.h>
|
|
|
|
|
#include <kiwano-network/HttpResponse.hpp>
|
2020-02-14 17:56:50 +08:00
|
|
|
#include <kiwano-network/HttpModule.h>
|
2020-04-15 10:12:42 +08:00
|
|
|
#include <curl/curl.h> // CURL
|
2019-04-05 16:06:32 +08:00
|
|
|
|
2019-03-31 01:37:06 +08:00
|
|
|
namespace
|
|
|
|
|
{
|
2020-01-21 10:09:55 +08:00
|
|
|
using namespace kiwano;
|
|
|
|
|
using namespace kiwano::network;
|
|
|
|
|
|
|
|
|
|
uint32_t write_data(void* buffer, uint32_t size, uint32_t nmemb, void* userp)
|
|
|
|
|
{
|
2020-02-10 13:47:00 +08:00
|
|
|
String* recv_buffer = (String*)userp;
|
|
|
|
|
uint32_t total = size * nmemb;
|
2020-01-21 10:09:55 +08:00
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-19 12:09:50 +08:00
|
|
|
bool Init(HttpModule* client, const Vector<String>& headers, const String& url, String* response_data,
|
2020-02-10 13:47:00 +08:00
|
|
|
String* response_header, char* error_buffer)
|
2020-01-21 10:09:55 +08:00
|
|
|
{
|
|
|
|
|
if (!SetOption(CURLOPT_ERRORBUFFER, error_buffer))
|
|
|
|
|
return false;
|
|
|
|
|
if (!SetOption(CURLOPT_TIMEOUT, client->GetTimeoutForRead()))
|
|
|
|
|
return false;
|
|
|
|
|
if (!SetOption(CURLOPT_CONNECTTIMEOUT, client->GetTimeoutForConnect()))
|
|
|
|
|
return false;
|
|
|
|
|
|
2020-02-10 13:47:00 +08:00
|
|
|
const String& ssl_ca_file = client->GetSSLVerification();
|
2020-01-21 10:09:55 +08:00
|
|
|
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);
|
|
|
|
|
return code == CURLE_OK && (*response_code >= 200 && *response_code < 300);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename... _Args>
|
|
|
|
|
bool SetOption(CURLoption option, _Args&&... args)
|
|
|
|
|
{
|
|
|
|
|
return CURLE_OK == curl_easy_setopt(curl_, option, std::forward<_Args>(args)...);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public:
|
2020-02-19 12:09:50 +08:00
|
|
|
static inline bool GetRequest(HttpModule* client, const Vector<String>& headers, const String& url,
|
2020-02-10 13:47:00 +08:00
|
|
|
long* response_code, String* response_data, String* response_header,
|
2020-01-21 10:09:55 +08:00
|
|
|
char* error_buffer)
|
|
|
|
|
{
|
|
|
|
|
Curl curl;
|
|
|
|
|
return curl.Init(client, headers, url, response_data, response_header, error_buffer)
|
|
|
|
|
&& curl.SetOption(CURLOPT_FOLLOWLOCATION, true) && curl.Perform(response_code);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-19 12:09:50 +08:00
|
|
|
static inline bool PostRequest(HttpModule* client, const Vector<String>& headers, const String& url,
|
|
|
|
|
const String& request_data, long* response_code, String* response_data,
|
2020-02-10 13:47:00 +08:00
|
|
|
String* response_header, char* error_buffer)
|
2020-01-21 10:09:55 +08:00
|
|
|
{
|
|
|
|
|
Curl curl;
|
|
|
|
|
return curl.Init(client, headers, url, response_data, response_header, error_buffer)
|
|
|
|
|
&& curl.SetOption(CURLOPT_POST, 1) && curl.SetOption(CURLOPT_POSTFIELDS, request_data.c_str())
|
|
|
|
|
&& curl.SetOption(CURLOPT_POSTFIELDSIZE, request_data.size()) && curl.Perform(response_code);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-19 12:09:50 +08:00
|
|
|
static inline bool PutRequest(HttpModule* client, const Vector<String>& headers, const String& url,
|
|
|
|
|
const String& request_data, long* response_code, String* response_data,
|
2020-02-10 13:47:00 +08:00
|
|
|
String* response_header, char* error_buffer)
|
2020-01-21 10:09:55 +08:00
|
|
|
{
|
|
|
|
|
Curl curl;
|
|
|
|
|
return curl.Init(client, headers, url, response_data, response_header, error_buffer)
|
|
|
|
|
&& curl.SetOption(CURLOPT_CUSTOMREQUEST, "PUT")
|
|
|
|
|
&& curl.SetOption(CURLOPT_POSTFIELDS, request_data.c_str())
|
|
|
|
|
&& curl.SetOption(CURLOPT_POSTFIELDSIZE, request_data.size()) && curl.Perform(response_code);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-19 12:09:50 +08:00
|
|
|
static inline bool DeleteRequest(HttpModule* client, const Vector<String>& headers, const String& url,
|
2020-02-10 13:47:00 +08:00
|
|
|
long* response_code, String* response_data, String* response_header,
|
2020-01-21 10:09:55 +08:00
|
|
|
char* error_buffer)
|
|
|
|
|
{
|
|
|
|
|
Curl curl;
|
|
|
|
|
return curl.Init(client, headers, url, response_data, response_header, error_buffer)
|
|
|
|
|
&& curl.SetOption(CURLOPT_CUSTOMREQUEST, "DELETE") && curl.SetOption(CURLOPT_FOLLOWLOCATION, true)
|
|
|
|
|
&& curl.Perform(response_code);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
CURL* curl_;
|
|
|
|
|
curl_slist* curl_headers_;
|
|
|
|
|
};
|
|
|
|
|
} // namespace
|
|
|
|
|
|
2019-04-11 14:40:54 +08:00
|
|
|
namespace kiwano
|
2019-03-31 01:37:06 +08:00
|
|
|
{
|
2020-01-21 10:09:55 +08:00
|
|
|
namespace network
|
|
|
|
|
{
|
2020-02-14 17:56:50 +08:00
|
|
|
HttpModule::HttpModule()
|
2020-01-21 10:09:55 +08:00
|
|
|
: timeout_for_connect_(30000 /* 30 seconds */)
|
|
|
|
|
, timeout_for_read_(60000 /* 60 seconds */)
|
2020-02-20 13:58:17 +08:00
|
|
|
, quit_flag_(false)
|
2020-01-21 10:09:55 +08:00
|
|
|
{
|
2019-03-31 01:37:06 +08:00
|
|
|
}
|
2020-01-21 10:09:55 +08:00
|
|
|
|
2020-02-14 17:56:50 +08:00
|
|
|
void HttpModule::SetupModule()
|
2020-01-21 10:09:55 +08:00
|
|
|
{
|
|
|
|
|
::curl_global_init(CURL_GLOBAL_ALL);
|
|
|
|
|
|
2020-02-14 17:56:50 +08:00
|
|
|
std::thread thread(Closure(this, &HttpModule::NetworkThread));
|
2020-01-21 10:09:55 +08:00
|
|
|
thread.detach();
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-14 17:56:50 +08:00
|
|
|
void HttpModule::DestroyModule()
|
2020-01-21 10:09:55 +08:00
|
|
|
{
|
2020-02-20 13:58:17 +08:00
|
|
|
// Set quit flag
|
|
|
|
|
quit_flag_ = true;
|
|
|
|
|
|
|
|
|
|
// Send a fake request
|
|
|
|
|
{
|
|
|
|
|
std::unique_lock<std::mutex> lock(request_mutex_);
|
|
|
|
|
request_queue_.push(nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Notify work thread
|
|
|
|
|
sleep_cond_.notify_one();
|
|
|
|
|
|
|
|
|
|
// Wait for work thread destroyed
|
|
|
|
|
{
|
|
|
|
|
std::unique_lock<std::mutex> lock(quit_mutex_);
|
|
|
|
|
quit_cond_.wait_for(lock, std::chrono::seconds(1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clear curl resources
|
2020-01-21 10:09:55 +08:00
|
|
|
::curl_global_cleanup();
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-14 17:56:50 +08:00
|
|
|
void HttpModule::Send(HttpRequestPtr request)
|
2020-01-21 10:09:55 +08:00
|
|
|
{
|
|
|
|
|
if (!request)
|
|
|
|
|
return;
|
|
|
|
|
|
2020-02-20 13:58:17 +08:00
|
|
|
{
|
|
|
|
|
std::unique_lock<std::mutex> lock(request_mutex_);
|
|
|
|
|
request_queue_.push(request);
|
|
|
|
|
}
|
|
|
|
|
sleep_cond_.notify_one();
|
2020-01-21 10:09:55 +08:00
|
|
|
}
|
|
|
|
|
|
2020-02-14 17:56:50 +08:00
|
|
|
void HttpModule::NetworkThread()
|
2020-01-21 10:09:55 +08:00
|
|
|
{
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
HttpRequestPtr request;
|
|
|
|
|
{
|
2020-02-20 13:58:17 +08:00
|
|
|
std::unique_lock<std::mutex> lock(request_mutex_);
|
|
|
|
|
sleep_cond_.wait(lock, [&]() { return !request_queue_.empty(); });
|
|
|
|
|
|
2020-01-21 10:09:55 +08:00
|
|
|
request = request_queue_.front();
|
|
|
|
|
request_queue_.pop();
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-20 13:58:17 +08:00
|
|
|
if (quit_flag_)
|
|
|
|
|
{
|
|
|
|
|
quit_cond_.notify_one();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-21 10:09:55 +08:00
|
|
|
HttpResponsePtr response = new (std::nothrow) HttpResponse(request);
|
|
|
|
|
Perform(request, response);
|
|
|
|
|
|
|
|
|
|
response_mutex_.lock();
|
|
|
|
|
response_queue_.push(response);
|
|
|
|
|
response_mutex_.unlock();
|
|
|
|
|
|
2020-02-14 17:56:50 +08:00
|
|
|
Application::GetInstance().PreformInMainThread(Closure(this, &HttpModule::DispatchResponseCallback));
|
2020-01-21 10:09:55 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-14 17:56:50 +08:00
|
|
|
void HttpModule::Perform(HttpRequestPtr request, HttpResponsePtr response)
|
2020-01-21 10:09:55 +08:00
|
|
|
{
|
2020-02-10 13:47:00 +08:00
|
|
|
bool ok = false;
|
|
|
|
|
long response_code = 0;
|
|
|
|
|
char error_message[256] = { 0 };
|
|
|
|
|
String response_header;
|
|
|
|
|
String response_data;
|
|
|
|
|
String url = request->GetUrl();
|
|
|
|
|
String data = request->GetData();
|
|
|
|
|
|
|
|
|
|
Vector<String> headers;
|
2020-01-21 10:09:55 +08:00
|
|
|
headers.reserve(request->GetHeaders().size());
|
|
|
|
|
for (const auto& pair : request->GetHeaders())
|
|
|
|
|
{
|
2020-02-10 13:47:00 +08:00
|
|
|
headers.push_back(pair.first + ":" + pair.second);
|
2020-01-21 10:09:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (request->GetType())
|
|
|
|
|
{
|
2020-02-06 16:54:47 +08:00
|
|
|
case HttpType::Get:
|
2020-01-21 10:09:55 +08:00
|
|
|
ok = Curl::GetRequest(this, headers, url, &response_code, &response_data, &response_header, error_message);
|
|
|
|
|
break;
|
2020-02-06 16:54:47 +08:00
|
|
|
case HttpType::Post:
|
2020-01-21 10:09:55 +08:00
|
|
|
ok = Curl::PostRequest(this, headers, url, data, &response_code, &response_data, &response_header,
|
|
|
|
|
error_message);
|
|
|
|
|
break;
|
2020-02-06 16:54:47 +08:00
|
|
|
case HttpType::Put:
|
2020-01-21 10:09:55 +08:00
|
|
|
ok =
|
|
|
|
|
Curl::PutRequest(this, headers, url, data, &response_code, &response_data, &response_header, error_message);
|
|
|
|
|
break;
|
2020-02-06 16:54:47 +08:00
|
|
|
case HttpType::Delete:
|
2020-01-21 10:09:55 +08:00
|
|
|
ok = Curl::DeleteRequest(this, headers, url, &response_code, &response_data, &response_header, error_message);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
2020-07-17 12:23:18 +08:00
|
|
|
KGE_ERRORF("HttpModule: unknown request type, only GET, POST, PUT or DELETE is supported");
|
2020-01-21 10:09:55 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response->SetResponseCode(response_code);
|
2020-02-10 13:47:00 +08:00
|
|
|
response->SetHeader(response_header);
|
|
|
|
|
response->SetData(response_data);
|
2020-01-21 10:09:55 +08:00
|
|
|
if (!ok)
|
|
|
|
|
{
|
|
|
|
|
response->SetSucceed(false);
|
2020-02-10 13:47:00 +08:00
|
|
|
response->SetError(error_message);
|
2020-01-21 10:09:55 +08:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
response->SetSucceed(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-14 17:56:50 +08:00
|
|
|
void HttpModule::DispatchResponseCallback()
|
2020-01-21 10:09:55 +08:00
|
|
|
{
|
|
|
|
|
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)
|
|
|
|
|
{
|
2020-02-15 17:32:32 +08:00
|
|
|
callback(request.Get(), response.Get());
|
2020-01-21 10:09:55 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace network
|
|
|
|
|
} // namespace kiwano
|