11 #include <framework/database/Downloader.h>
12 #include <framework/logging/Logger.h>
13 #include <framework/gearbox/Unit.h>
14 #include <framework/utilities/Utils.h>
15 #include <framework/utilities/EnvironmentVariables.h>
17 #include <curl/curl.h>
21 #include <boost/algorithm/string.hpp>
27 namespace Belle2::Conditions {
35 char errbuf[CURL_ERROR_SIZE];
51 size_t write_function(
void* buffer,
size_t size,
size_t nmemb,
void* userp)
55 std::ostream& stream = *
static_cast<std::ostream*
>(userp);
56 stream.write(
static_cast<const char*
>(buffer), size * nmemb);
57 }
catch (std::ios_base::failure& e) {
58 B2ERROR(
"Writing error while downloading: " << e.code().message() <<
'(' << e.code().value() <<
')');
74 int progress_callback(
void* clientp, curl_off_t dltotal, curl_off_t dlnow,
75 __attribute((unused)) curl_off_t ultotal, __attribute((unused)) curl_off_t ulnow)
78 if (dlnow == 0)
return 0;
80 CurlSession& status = *
static_cast<CurlSession*
>(clientp);
83 if (status.lasttime != 0 && (time - status.lasttime) /
Unit::ms < 200) {
86 status.lasttime = time;
88 B2DEBUG(39,
"curl:= " << dlnow <<
" / " << dltotal <<
" bytes transferred");
90 B2DEBUG(39,
"curl:= " << dlnow <<
" bytes transferred");
105 int debug_callback([[maybe_unused]] CURL* handle, curl_infotype type,
char* data,
size_t size,
106 [[maybe_unused]]
void* userptr)
108 std::string prefix =
"curl:";
114 if (type == CURLINFO_TEXT) { prefix +=
"*"; level = 38; }
115 else if (type == CURLINFO_HEADER_OUT) prefix +=
">";
116 else if (type == CURLINFO_HEADER_IN) prefix +=
"<";
119 std::string message(data, size);
120 boost::trim(message);
122 if (!message.empty()) B2DEBUG(level, prefix <<
" " << message);
127 std::string getUserAgent()
147 char* escaped = curl_easy_escape(
m_session->curl, text.c_str(), text.size());
149 throw std::runtime_error(
"Could not escape string");
151 std::string escapedStr{escaped};
159 return boost::trim_right_copy_if(base, boost::is_any_of(
"/")) +
"/" +
160 boost::trim_left_copy_if(rest, boost::is_any_of(
"/"));
169 curl_global_init(CURL_GLOBAL_ALL);
173 m_session = std::make_unique<CurlSession>();
176 B2FATAL(
"Cannot initialize libcurl");
178 m_session->headers = curl_slist_append(
nullptr,
"Accept: application/json");
180 curl_easy_setopt(
m_session->curl, CURLOPT_TCP_KEEPALIVE, 1L);
182 curl_easy_setopt(
m_session->curl, CURLOPT_LOW_SPEED_LIMIT, 10 * 1024);
184 curl_easy_setopt(
m_session->curl, CURLOPT_WRITEFUNCTION, write_function);
185 curl_easy_setopt(
m_session->curl, CURLOPT_VERBOSE, 1);
186 curl_easy_setopt(
m_session->curl, CURLOPT_NOPROGRESS, 0);
187 curl_easy_setopt(
m_session->curl, CURLOPT_DEBUGFUNCTION, debug_callback);
188 curl_easy_setopt(
m_session->curl, CURLOPT_XFERINFOFUNCTION, progress_callback);
190 curl_easy_setopt(
m_session->curl, CURLOPT_FAILONERROR,
true);
193 curl_easy_setopt(
m_session->curl, CURLOPT_ACCEPT_ENCODING,
"");
197 curl_easy_setopt(
m_session->curl, CURLOPT_PROXY, proxy.c_str());
199 curl_easy_setopt(
m_session->curl, CURLOPT_AUTOREFERER, 1L);
200 curl_easy_setopt(
m_session->curl, CURLOPT_FOLLOWLOCATION, 1L);
201 curl_easy_setopt(
m_session->curl, CURLOPT_MAXREDIRS, 10L);
202 curl_easy_setopt(
m_session->curl, CURLOPT_TCP_FASTOPEN, 0L);
203 curl_easy_setopt(
m_session->curl, CURLOPT_SSL_VERIFYPEER, 0L);
204 curl_easy_setopt(
m_session->curl, CURLOPT_SSL_VERIFYHOST, 0L);
205 curl_easy_setopt(
m_session->curl, CURLOPT_SSL_VERIFYSTATUS, 0L);
207 curl_easy_setopt(
m_session->curl, CURLOPT_DNS_CACHE_TIMEOUT, 0L);
210 curl_easy_setopt(
m_session->curl, CURLOPT_DNS_SHUFFLE_ADDRESSES, 1L);
211 auto version = getUserAgent();
212 curl_easy_setopt(
m_session->curl, CURLOPT_USERAGENT, version.c_str());
230 input.seekg(0, std::ios::beg);
234 while (input.good()) {
235 input.read(buffer, 4096);
236 if (input.gcount() == 0)
break;
237 md5.Update((
unsigned char*)buffer, input.gcount());
241 return md5.AsString();
264 B2DEBUG(37,
"Download started ..." <<
LogVar(
"url", url));
266 for (
unsigned int retry{1};; ++retry) {
269 buffer.seekp(0, std::ios::beg);
270 if (!buffer.good()) {
271 throw std::runtime_error(
"cannot write to stream");
274 auto oldExceptionMask = buffer.exceptions();
275 buffer.exceptions(std::ios::failbit | std::ios::badbit);
277 CURLcode res{CURLE_FAILED_INIT};
279 curl_easy_setopt(
m_session->curl, CURLOPT_URL, url.c_str());
280 curl_easy_setopt(
m_session->curl, CURLOPT_WRITEDATA, &buffer);
282 res = curl_easy_perform(
m_session->curl);
284 buffer.exceptions(oldExceptionMask);
287 if (res != CURLE_OK) {
289 const std::string error = len ?
m_session->errbuf : curl_easy_strerror(res);
290 if (
m_maxRetries > 0 && res == CURLE_HTTP_RETURNED_ERROR) {
294 long responseCode{0};
295 curl_easy_getinfo(
m_session->curl, CURLINFO_RESPONSE_CODE, &responseCode);
296 if (responseCode >= 500) {
301 double seconds = gRandom->Uniform(1., maxDelay);
302 B2WARNING(
"Could not download url, retrying ..."
304 <<
LogVar(
"try", retry) <<
LogVar(
"waiting time", seconds));
305 std::this_thread::sleep_for(std::chrono::milliseconds((
int)(seconds * 1e3)));
308 if (responseCode == 404 and silentOnMissing)
return false;
311 throw std::runtime_error(error);
316 B2DEBUG(37,
"Download finished successfully." <<
LogVar(
"url", url));