* Copyright (c) 2021 Huawei Device 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.
*/
#include "adapter/preview/osal/fetch_manager.h"
#include <memory>
#include <mutex>
#include "curl/curl.h"
#include "adapter/preview/osal/http_constant.h"
#include "base/log/log.h"
#include "base/utils/singleton.h"
#define ACE_CURL_EASY_SET_OPTION(handle, opt, data) \
do { \
CURLcode result = curl_easy_setopt(handle, opt, data); \
if (result != CURLE_OK) { \
LOGW("Failed to set option: %{public}s, %{public}s", #opt, curl_easy_strerror(result)); \
return false; \
} \
} while (0)
namespace OHOS::Ace {
class FetchManagerImpl final : public FetchManager, public Singleton<FetchManagerImpl> {
DECLARE_SINGLETON(FetchManagerImpl);
ACE_DISALLOW_MOVE(FetchManagerImpl);
public:
bool Fetch(const RequestData requestData, const int32_t callbackId, ResponseData& responseData) override
{
if (!Initialize()) {
return false;
}
std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> handle(curl_easy_init(), &curl_easy_cleanup);
if (!handle) {
return false;
}
struct curl_slist* header = nullptr;
if (!requestData.GetHeader().empty()) {
for (auto&& [key, value] : requestData.GetHeader()) {
header = curl_slist_append(header, (key + ":" + value).c_str());
}
ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_HTTPHEADER, header);
}
ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_TIMEOUT_MS, HttpConstant::TIME_OUT);
ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_CONNECTTIMEOUT_MS, HttpConstant::TIME_OUT);
ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_BUFFERSIZE, HttpConstant::BUFFER_SIZE);
std::string responseBody;
ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_WRITEFUNCTION, OnWritingMemoryBody);
ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_WRITEDATA, &responseBody);
std::string responseHeader;
ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_HEADERFUNCTION, OnWritingMemoryHeader);
ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_HEADERDATA, &responseHeader);
ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_USERAGENT, "libcurl-agent/1.0");
std::string method = requestData.GetMethod();
if (method.empty()) {
method = "GET";
}
if (method == HttpConstant::HTTP_METHOD_HEAD || method == HttpConstant::HTTP_METHOD_OPTIONS ||
method == HttpConstant::HTTP_METHOD_DELETE || method == HttpConstant::HTTP_METHOD_TRACE ||
method == HttpConstant::HTTP_METHOD_GET) {
SetOptionForGet(requestData, handle.get());
} else if (method == HttpConstant::HTTP_METHOD_POST || method == HttpConstant::HTTP_METHOD_PUT) {
SetOptionForPost(requestData, handle.get());
} else {
responseData.SetCode(HttpConstant::ERROR);
responseData.SetData(responseBody);
return true;
}
CURLcode result = curl_easy_perform(handle.get());
if (result != CURLE_OK) {
LOGW("Failed to fetch, url: %{private}s, %{public}s", requestData.GetUrl().c_str(),
curl_easy_strerror(result));
responseData.SetCode(HttpConstant::ERROR);
responseData.SetData(responseBody);
return true;
}
char* ct = nullptr;
curl_easy_getinfo(handle.get(), CURLINFO_CONTENT_TYPE, &ct);
int32_t responseCode = HttpConstant::ERROR;
curl_easy_getinfo(handle.get(), CURLINFO_RESPONSE_CODE, &responseCode);
responseData.SetCode(responseCode);
responseData.SetData(responseBody);
responseData.SetHeaders(responseHeader);
if (header != nullptr) {
curl_slist_free_all(header);
}
return true;
}
bool SetOptionForGet(const RequestData requestData, CURL* curl) const
{
std::string url = requestData.GetUrl();
if (requestData.GetData() != "") {
std::size_t index = url.find(HttpConstant::URL_PARAM_SEPARATOR);
if (index != std::string::npos) {
std::string param = url.substr(index + 1);
std::string encodeIn = param + HttpConstant::URL_PARAM_DELIMITER + requestData.GetData();
char* encodeOut = curl_easy_escape(curl, encodeIn.c_str(), 0);
if (encodeOut != nullptr) {
url = url.substr(0, index + 1) + encodeOut;
curl_free(encodeOut);
}
} else {
char* encodeOut = curl_easy_escape(curl, requestData.GetData().c_str(), 0);
if (encodeOut != nullptr) {
url = url + HttpConstant::URL_PARAM_SEPARATOR + encodeOut;
curl_free(encodeOut);
}
}
}
ACE_CURL_EASY_SET_OPTION(curl, CURLOPT_URL, url.c_str());
return true;
}
bool SetOptionForPost(const RequestData requestData, CURL* curl) const
{
std::string url = requestData.GetUrl();
ACE_CURL_EASY_SET_OPTION(curl, CURLOPT_URL, url.c_str());
ACE_CURL_EASY_SET_OPTION(curl, CURLOPT_POSTFIELDS, requestData.GetData().c_str());
return true;
}
private:
static size_t OnWritingMemoryBody(const void* data, size_t size, size_t memBytes, void* userData)
{
((std::string*)userData)->append((char*)data, 0, size * memBytes);
return size * memBytes;
}
static size_t OnWritingMemoryHeader(const void* data, size_t size, size_t memBytes, void* userData)
{
((std::string*)userData)->append((char*)data, 0, size * memBytes);
return size * memBytes;
}
bool Initialize()
{
if (initialized_) {
return true;
}
std::lock_guard<std::mutex> lock(mutex_);
if (initialized_) {
return true;
}
if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
LOGE("Failed to initialize 'curl'");
return false;
}
initialized_ = true;
return true;
}
std::mutex mutex_;
bool initialized_ = false;
};
FetchManagerImpl::FetchManagerImpl() = default;
FetchManagerImpl::~FetchManagerImpl()
{
curl_global_cleanup();
}
SINGLETON_INSTANCE_IMPL(FetchManagerImpl);
FetchManager& FetchManager::GetInstance()
{
return Singleton<FetchManagerImpl>::GetInstance();
}
}