• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "hilog/log.h"
17 #include "http_client.h"
18 #include "net_conn_client.h"
19 
20 #include "base/network/download_manager.h"
21 
22 #define ACE_FORCE_EXPORT __attribute__((visibility("default")))
23 
24 #define ACE_CURL_EASY_SET_OPTION(handle, opt, data)            \
25     do {                                                       \
26         CURLcode result = curl_easy_setopt(handle, opt, data); \
27         if (result != CURLE_OK) {                              \
28             return false;                                      \
29         }                                                      \
30     } while (0)
31 
32 namespace OHOS::Ace {
33 namespace {
34 constexpr int32_t MAXIMUM_WAITING_PERIOD = 2800;
35 
36 #define PRINT_LOG(level, fmt, ...)                                                                               \
37     HILOG_IMPL(LOG_CORE, LOG_##level, 0xD00393A, "DownloadManager", "[%{public}d]" fmt, __LINE__, ##__VA_ARGS__) \
38 
39 #define LOGE(fmt, ...) PRINT_LOG(ERROR, fmt, ##__VA_ARGS__)
40 #define LOGW(fmt, ...) PRINT_LOG(WARN, fmt, ##__VA_ARGS__)
41 #define LOGI(fmt, ...) PRINT_LOG(INFO, fmt, ##__VA_ARGS__)
42 #define LOGD(fmt, ...) PRINT_LOG(DEBUG, fmt, ##__VA_ARGS__)
43 } // namespace
44 
45 // For sync download tasks, this period may cause image not able to be loaded.
46 // System detects appFreeze after 3s, which has higher priority
47 using NetStackRequest = NetStack::HttpClient::HttpClientRequest;
48 using NetStackResponse = NetStack::HttpClient::HttpClientResponse;
49 using NetStackError = NetStack::HttpClient::HttpClientError;
50 using NetStackTask = NetStack::HttpClient::HttpClientTask;
51 using NetStackTaskStatus = NetStack::HttpClient::TaskStatus;
52 
53 class ACE_FORCE_EXPORT DownloadManagerImpl : public DownloadManager {
54 public:
55     DownloadManagerImpl() = default;
~DownloadManagerImpl()56     ~DownloadManagerImpl()
57     {
58         if (isCurl_) {
59             curl_global_cleanup();
60         }
61     }
62 
Download(const std::string & url,std::vector<uint8_t> & dataOut)63     bool Download(const std::string& url, std::vector<uint8_t>& dataOut) override
64     {
65         // when calling, it is necessary to set it to true and call curl clean up method
66         // during download manager ohos object destruction
67         isCurl_ = true;
68         if (!Initialize()) {
69             return false;
70         }
71 
72         std::unique_ptr<CURL, decltype(&curl_easy_cleanup)> handle(curl_easy_init(), &curl_easy_cleanup);
73         if (!handle) {
74             return false;
75         }
76 
77         dataOut.clear();
78         std::string errorStr;
79         errorStr.reserve(CURL_ERROR_SIZE);
80 
81         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_URL, url.c_str());
82         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_WRITEFUNCTION, OnWritingMemory);
83         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_WRITEDATA, &dataOut);
84         // Some servers don't like requests that are made without a user-agent field, so we provide one
85         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_USERAGENT, "libcurl-agent/1.0");
86         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_URL, url.c_str());
87 #if !defined(PREVIEW)
88         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_CAINFO, "/etc/ssl/certs/cacert.pem");
89 #endif
90         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_VERBOSE, 1L);
91         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_ERRORBUFFER, errorStr.data());
92 
93         ProxyInfo proxy;
94         if (GetProxy(proxy)) {
95             ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_PROXY, proxy.host.c_str());
96             ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_PROXYPORT, proxy.port);
97             if (!proxy.exclusions.empty()) {
98                 ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_NOPROXY, proxy.exclusions.c_str());
99             }
100             ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
101             ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_HTTPPROXYTUNNEL, 1L);
102         }
103 
104 #if defined(IOS_PLATFORM) || defined(ANDROID_PLATFORM) || defined(PREVIEW)
105         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_SSL_VERIFYPEER, 0L);
106         ACE_CURL_EASY_SET_OPTION(handle.get(), CURLOPT_SSL_VERIFYHOST, 0L);
107 #endif
108 
109         CURLcode result = curl_easy_perform(handle.get());
110         if (result != CURLE_OK) {
111             LOGE("Failed to download, url: [%{private}s], [%{public}s]", url.c_str(), curl_easy_strerror(result));
112             if (!errorStr.empty()) {
113                 LOGE("Failed to download reason: [%{public}s]", errorStr.c_str());
114             }
115             dataOut.clear();
116             return false;
117         }
118         dataOut.shrink_to_fit();
119         return true;
120     }
121 
Download(const std::string & url,const std::shared_ptr<DownloadResult> & downloadResult)122     bool Download(const std::string& url, const std::shared_ptr<DownloadResult>& downloadResult) override
123     {
124         NetStackRequest httpReq;
125         httpReq.SetHeader("Accept", "image/webp,*/*");
126         httpReq.SetURL(url);
127         auto& session = NetStack::HttpClient::HttpSession::GetInstance();
128         auto task = session.CreateTask(httpReq);
129         if (!task) {
130             return false;
131         }
132         std::shared_ptr<DownloadCondition> downloadCondition = std::make_shared<DownloadCondition>();
133         task->OnSuccess(
134             [downloadCondition, downloadResult](const NetStackRequest& request, const NetStackResponse& response) {
135                 {
136                     std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
137                     downloadResult->downloadSuccess = true;
138                     downloadResult->dataOut = std::move(response.GetResult());
139                 }
140                 downloadCondition->cv.notify_all();
141             });
142         task->OnCancel(
143             [downloadCondition, downloadResult](const NetStackRequest& request, const NetStackResponse& response) {
144                 {
145                     std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
146                     downloadResult->errorMsg.append("Http task of url ");
147                     downloadResult->errorMsg.append(request.GetURL());
148                     downloadResult->errorMsg.append(" cancelled by netStack");
149                     downloadResult->downloadSuccess = false;
150                 }
151                 downloadCondition->cv.notify_all();
152             });
153         task->OnFail([downloadCondition, downloadResult](
154                          const NetStackRequest& request, const NetStackResponse& response, const NetStackError& error) {
155             {
156                 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
157                 downloadResult->errorMsg.append("Http task of url ");
158                 downloadResult->errorMsg.append(request.GetURL());
159                 downloadResult->errorMsg.append(" failed, response code ");
160                 auto responseCode = response.GetResponseCode();
161                 downloadResult->errorMsg.append(std::to_string(responseCode));
162                 downloadResult->errorMsg.append(", msg from netStack: ");
163                 downloadResult->errorMsg.append(error.GetErrorMessage());
164                 downloadResult->downloadSuccess = false;
165             }
166             downloadCondition->cv.notify_all();
167         });
168         auto result = task->Start();
169         return HandleDownloadResult(result, downloadCondition, downloadResult);
170     }
171 
DownloadAsync(DownloadCallback && downloadCallback,const std::string & url,int32_t instanceId,int32_t nodeId)172     bool DownloadAsync(
173         DownloadCallback&& downloadCallback, const std::string& url, int32_t instanceId, int32_t nodeId) override
174     {
175         NetStackRequest httpReq;
176         httpReq.SetHeader("Accept", "image/webp,*/*");
177         httpReq.SetURL(url);
178         auto& session = NetStack::HttpClient::HttpSession::GetInstance();
179         auto task = session.CreateTask(httpReq);
180         if (!task) {
181             return false;
182         }
183         task->OnSuccess(
184             [successCallback = downloadCallback.successCallback, failCallback = downloadCallback.failCallback,
185                 instanceId](const NetStackRequest& request, const NetStackResponse& response) {
186                 if (response.GetResponseCode() != NetStack::HttpClient::ResponseCode::OK) {
187                     std::string errorMsg = "Http task of url " + request.GetURL() + " failed, response code " +
188                                            std::to_string(response.GetResponseCode());
189                     failCallback(errorMsg, true, instanceId);
190                     return;
191                 }
192                 LOGI("Async http task of url [%{private}s] success, the responseCode = %d", request.GetURL().c_str(),
193                     response.GetResponseCode());
194                 successCallback(std::move(response.GetResult()), true, instanceId);
195             });
196         task->OnCancel([cancelCallback = downloadCallback.cancelCallback, instanceId](
197                            const NetStackRequest& request, const NetStackResponse& response) {
198             LOGI("Async Http task of url [%{private}s] cancelled by netStack", request.GetURL().c_str());
199             std::string errorMsg = "Http task of url " + request.GetURL() + " cancelled by netStack";
200             cancelCallback(errorMsg, true, instanceId);
201         });
202         task->OnFail([failCallback = downloadCallback.failCallback, instanceId](
203                          const NetStackRequest& request, const NetStackResponse& response, const NetStackError& error) {
204             LOGI("Async http task of url [%{private}s] failed, response code %{public}d, msg from netStack: "
205                  "[%{public}s]",
206                 request.GetURL().c_str(), response.GetResponseCode(), error.GetErrorMessage().c_str());
207             std::string errorMsg = "Http task of url " + request.GetURL() + " failed, response code " +
208                                    std::to_string(response.GetResponseCode()) +
209                                    ", msg from netStack: " + error.GetErrorMessage();
210             failCallback(errorMsg, true, instanceId);
211         });
212         if (downloadCallback.onProgressCallback) {
213             task->OnProgress([onProgressCallback = downloadCallback.onProgressCallback, instanceId](
214                                  const NetStackRequest& request, u_long dlTotal, u_long dlNow, u_long ulTotal,
215                                  u_long ulNow) { onProgressCallback(dlTotal, dlNow, true, instanceId); });
216         }
217         AddDownloadTask(url, task, nodeId);
218         auto result = task->Start();
219         LOGI("download src [%{private}s] [%{public}s]", url.c_str(),
220             result ? " successfully" : " failed to download, please check netStack log");
221         return result;
222     }
223 
DownloadSync(DownloadCallback && downloadCallback,const std::string & url,int32_t instanceId,int32_t nodeId)224     bool DownloadSync(
225         DownloadCallback&& downloadCallback, const std::string& url, int32_t instanceId, int32_t nodeId) override
226     {
227         LOGI("DownloadSync task of [%{private}s] start", url.c_str());
228         NetStackRequest httpReq;
229         httpReq.SetHeader("Accept", "image/webp,*/*");
230         httpReq.SetURL(url);
231         auto& session = NetStack::HttpClient::HttpSession::GetInstance();
232         auto task = session.CreateTask(httpReq);
233         std::shared_ptr<DownloadCondition> downloadCondition = std::make_shared<DownloadCondition>();
234         if (!task) {
235             return false;
236         }
237         task->OnSuccess([downloadCondition](const NetStackRequest& request, const NetStackResponse& response) {
238             LOGI("Sync http task of url [%{private}s] success, the responseCode = %d", request.GetURL().c_str(),
239                 response.GetResponseCode());
240             {
241                 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
242                 downloadCondition->downloadSuccess = true;
243                 downloadCondition->dataOut = std::move(response.GetResult());
244             }
245             downloadCondition->cv.notify_all();
246         });
247         task->OnCancel([downloadCondition](const NetStackRequest& request, const NetStackResponse& response) {
248             LOGI("Sync Http task of url [%{private}s] cancelled", request.GetURL().c_str());
249             {
250                 std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
251                 downloadCondition->errorMsg.append("Http task of url ");
252                 downloadCondition->errorMsg.append(request.GetURL());
253                 downloadCondition->errorMsg.append(" cancelled by netStack");
254                 downloadCondition->downloadSuccess = false;
255             }
256             downloadCondition->cv.notify_all();
257         });
258         task->OnFail([downloadCondition](const NetStackRequest& request, const NetStackResponse& response,
259                         const NetStackError& error) { OnFail(downloadCondition, request, response, error); });
260         if (downloadCallback.onProgressCallback) {
261             task->OnProgress([onProgressCallback = downloadCallback.onProgressCallback, instanceId](
262                                 const NetStackRequest& request, u_long dlTotal, u_long dlNow, u_long ulTotal,
263                                 u_long ulNow) { onProgressCallback(dlTotal, dlNow, false, instanceId); });
264         }
265         AddDownloadTask(url, task, nodeId);
266         auto result = task->Start();
267         return HandleDownloadResult(result, std::move(downloadCallback), downloadCondition, instanceId, url);
268     }
269 
OnFail(std::shared_ptr<DownloadCondition> downloadCondition,const NetStackRequest & request,const NetStackResponse & response,const NetStackError & error)270     static void OnFail(std::shared_ptr<DownloadCondition> downloadCondition, const NetStackRequest& request,
271         const NetStackResponse& response, const NetStackError& error)
272     {
273         LOGI(
274             "Sync Http task of url [%{private}s] failed, response code %{public}d, msg from netStack: [%{public}s]",
275             request.GetURL().c_str(), response.GetResponseCode(), error.GetErrorMessage().c_str());
276         {
277             std::unique_lock<std::mutex> taskLock(downloadCondition->downloadMutex);
278             downloadCondition->errorMsg.append("Http task of url ");
279             downloadCondition->errorMsg.append(request.GetURL());
280             downloadCondition->errorMsg.append(" failed, response code ");
281             auto responseCode = response.GetResponseCode();
282             downloadCondition->errorMsg.append(std::to_string(responseCode));
283             downloadCondition->errorMsg.append(", msg from netStack: ");
284             downloadCondition->errorMsg.append(error.GetErrorMessage());
285             downloadCondition->downloadSuccess = false;
286         }
287         downloadCondition->cv.notify_all();
288     }
289 
RemoveDownloadTask(const std::string & url,int32_t nodeId)290     bool RemoveDownloadTask(const std::string& url, int32_t nodeId) override
291     {
292         std::scoped_lock lock(httpTaskMutex_);
293         auto urlKey = url + std::to_string(nodeId);
294         auto iter = httpTaskMap_.find(urlKey);
295         if (iter != httpTaskMap_.end()) {
296             auto task = iter->second;
297             if (task->GetStatus() == NetStackTaskStatus::RUNNING) {
298                 LOGI("AceImage RemoveDownloadTask, url:%{private}s", url.c_str());
299                 task->Cancel();
300             }
301             httpTaskMap_.erase(urlKey);
302             return true;
303         }
304         return false;
305     }
306 
307 private:
308     struct ProxyInfo {
309         std::string host;
310         int32_t port = 0;
311         std::string exclusions;
312     };
313     std::mutex httpTaskMutex_;
314     std::unordered_map<std::string, std::shared_ptr<NetStackTask>> httpTaskMap_;
315 
HandleDownloadResult(bool result,DownloadCallback && downloadCallback,const std::shared_ptr<DownloadCondition> & downloadCondition,int32_t instanceId,const std::string & url)316     bool HandleDownloadResult(bool result, DownloadCallback&& downloadCallback,
317         const std::shared_ptr<DownloadCondition>& downloadCondition, int32_t instanceId, const std::string& url)
318     {
319         if (!result) {
320             return result;
321         }
322         {
323             std::unique_lock<std::mutex> downloadLock(downloadCondition->downloadMutex);
324             // condition_variable is waiting for any of the success, cancel or failed to respond in sync mode
325             downloadCondition->cv.wait_for(
326                 downloadLock, std::chrono::milliseconds(MAXIMUM_WAITING_PERIOD), [downloadCondition] {
327                     return downloadCondition ? downloadCondition->downloadSuccess.has_value() : false;
328                 });
329         }
330         if (!downloadCondition->downloadSuccess.has_value()) {
331             LOGI("Sync Task of netstack with url [%{private}s] maximum waiting period exceed", url.c_str());
332         }
333         if (downloadCondition->downloadSuccess.value_or(false)) {
334             downloadCallback.successCallback(std::move(downloadCondition->dataOut), false, instanceId);
335         } else {
336             downloadCallback.failCallback(downloadCondition->errorMsg, false, instanceId);
337         }
338         return true;
339     }
340 
HandleDownloadResult(bool result,const std::shared_ptr<DownloadCondition> & downloadCondition,const std::shared_ptr<DownloadResult> & downloadResult)341     bool HandleDownloadResult(bool result, const std::shared_ptr<DownloadCondition>& downloadCondition,
342         const std::shared_ptr<DownloadResult>& downloadResult)
343     {
344         if (!result) {
345             return result;
346         }
347         {
348             std::unique_lock<std::mutex> downloadLock(downloadCondition->downloadMutex);
349             // condition_variable is waiting for any of the success, cancel or failed to respond in sync mode
350             downloadCondition->cv.wait_for(
351                 downloadLock, std::chrono::milliseconds(MAXIMUM_WAITING_PERIOD), [downloadCondition, downloadResult] {
352                     return downloadCondition ? downloadResult->downloadSuccess.has_value() : false;
353                 });
354         }
355         return true;
356     }
357 
AddDownloadTask(const std::string & url,const std::shared_ptr<NetStackTask> & task,int32_t nodeId)358     void AddDownloadTask(const std::string& url, const std::shared_ptr<NetStackTask>& task, int32_t nodeId)
359     {
360         std::scoped_lock lock(httpTaskMutex_);
361         httpTaskMap_.emplace(url + std::to_string(nodeId), task);
362     }
363 
Initialize()364     bool Initialize()
365     {
366         if (initialized_) {
367             return true;
368         }
369 
370         std::lock_guard<std::mutex> lock(mutex_);
371         if (initialized_) {
372             return true;
373         }
374         if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
375             LOGE("Failed to initialize 'curl'");
376             return false;
377         }
378         initialized_ = true;
379         return true;
380     }
381 
OnWritingMemory(void * data,size_t size,size_t memBytes,void * userData)382     static size_t OnWritingMemory(void* data, size_t size, size_t memBytes, void* userData)
383     {
384         // size is always 1, for more details see https://curl.haxx.se/libcurl/c/CURLOPT_WRITEFUNCTION.html
385         auto& dataOut = *static_cast<std::vector<uint8_t>*>(userData);
386         auto chunkData = static_cast<uint8_t*>(data);
387         dataOut.insert(dataOut.end(), chunkData, chunkData + memBytes);
388         return memBytes;
389     }
390 
GetProxy(ProxyInfo & proxy)391     static bool GetProxy(ProxyInfo& proxy)
392     {
393         NetManagerStandard::HttpProxy httpProxy;
394         NetManagerStandard::NetConnClient::GetInstance().GetDefaultHttpProxy(httpProxy);
395         proxy.host = httpProxy.GetHost();
396         proxy.port = httpProxy.GetPort();
397 
398         auto exclusionList = httpProxy.GetExclusionList();
399         for (auto&& ex : exclusionList) {
400             proxy.exclusions.append(ex);
401             if (ex != exclusionList.back()) {
402                 proxy.exclusions.append(",");
403             }
404         }
405         return true;
406     }
407 
408     std::mutex mutex_;
409     bool initialized_ = false;
410     bool isCurl_ = false;
411 };
412 
OHOS_ACE_CreateDownloadManager()413 extern "C" ACE_FORCE_EXPORT void* OHOS_ACE_CreateDownloadManager()
414 {
415     return new DownloadManagerImpl();
416 }
417 } // namespace OHOS::Ace
418