9 #include <framework/database/Downloader.h>
10 #include <framework/logging/Logger.h>
11 #include <framework/gearbox/Unit.h>
12 #include <framework/utilities/Utils.h>
13 #include <framework/utilities/EnvironmentVariables.h>
15 #include <curl/curl.h>
19 #include <boost/algorithm/string.hpp>
25 namespace Belle2::Conditions {
49 size_t write_function(
void* buffer,
size_t size,
size_t nmemb,
void* userp)
53 std::ostream& stream = *
static_cast<std::ostream*
>(userp);
54 stream.write(
static_cast<const char*
>(buffer), size * nmemb);
55 }
catch (std::ios_base::failure& e) {
56 B2ERROR(
"Writing error while downloading: " << e.code().message() <<
'(' << e.code().value() <<
')');
72 int progress_callback(
void* clientp, curl_off_t dltotal, curl_off_t dlnow,
73 __attribute((unused)) curl_off_t ultotal, __attribute((unused)) curl_off_t ulnow)
76 if (dlnow == 0)
return 0;
78 CurlSession& status = *
static_cast<CurlSession*
>(clientp);
81 if (status.lasttime != 0 && (time - status.lasttime) /
Unit::ms < 200) {
84 status.lasttime = time;
86 B2DEBUG(39,
"curl:= " << dlnow <<
" / " << dltotal <<
" bytes transferred");
88 B2DEBUG(39,
"curl:= " << dlnow <<
" bytes transferred");
103 int debug_callback([[maybe_unused]] CURL* handle, curl_infotype type,
char* data,
size_t size,
104 [[maybe_unused]]
void* userptr)
106 std::string prefix =
"curl:";
112 if (type == CURLINFO_TEXT) { prefix +=
"*"; level = 38; }
113 else if (type == CURLINFO_HEADER_OUT) prefix +=
">";
114 else if (type == CURLINFO_HEADER_IN) prefix +=
"<";
117 std::string message(data, size);
118 boost::trim(message);
120 if (!message.empty()) B2DEBUG(level, prefix <<
" " << message);
125 std::string getUserAgent()
145 char* escaped = curl_easy_escape(
m_session->curl, text.c_str(), text.size());
147 throw std::runtime_error(
"Could not escape string");
149 std::string escapedStr{escaped};
157 return boost::trim_right_copy_if(base, boost::is_any_of(
"/")) +
"/" +
158 boost::trim_left_copy_if(rest, boost::is_any_of(
"/"));
167 curl_global_init(CURL_GLOBAL_ALL);
171 m_session = std::make_unique<CurlSession>();
174 B2FATAL(
"Cannot initialize libcurl");
176 m_session->headers = curl_slist_append(
nullptr,
"Accept: application/json");
178 curl_easy_setopt(
m_session->curl, CURLOPT_TCP_KEEPALIVE, 1L);
180 curl_easy_setopt(
m_session->curl, CURLOPT_LOW_SPEED_LIMIT, 10 * 1024);
182 curl_easy_setopt(
m_session->curl, CURLOPT_WRITEFUNCTION, write_function);
183 curl_easy_setopt(
m_session->curl, CURLOPT_VERBOSE, 1);
184 curl_easy_setopt(
m_session->curl, CURLOPT_NOPROGRESS, 0);
185 curl_easy_setopt(
m_session->curl, CURLOPT_DEBUGFUNCTION, debug_callback);
186 curl_easy_setopt(
m_session->curl, CURLOPT_XFERINFOFUNCTION, progress_callback);
188 curl_easy_setopt(
m_session->curl, CURLOPT_FAILONERROR,
true);
191 curl_easy_setopt(
m_session->curl, CURLOPT_ACCEPT_ENCODING,
"");
195 curl_easy_setopt(
m_session->curl, CURLOPT_PROXY, proxy.c_str());
197 curl_easy_setopt(
m_session->curl, CURLOPT_AUTOREFERER, 1L);
198 curl_easy_setopt(
m_session->curl, CURLOPT_FOLLOWLOCATION, 1L);
199 curl_easy_setopt(
m_session->curl, CURLOPT_MAXREDIRS, 10L);
200 curl_easy_setopt(
m_session->curl, CURLOPT_TCP_FASTOPEN, 0L);
201 curl_easy_setopt(
m_session->curl, CURLOPT_SSL_VERIFYPEER, 0L);
202 curl_easy_setopt(
m_session->curl, CURLOPT_SSL_VERIFYHOST, 0L);
203 curl_easy_setopt(
m_session->curl, CURLOPT_SSL_VERIFYSTATUS, 0L);
205 curl_easy_setopt(
m_session->curl, CURLOPT_DNS_CACHE_TIMEOUT, 0L);
208 curl_easy_setopt(
m_session->curl, CURLOPT_DNS_SHUFFLE_ADDRESSES, 1L);
209 auto version = getUserAgent();
210 curl_easy_setopt(
m_session->curl, CURLOPT_USERAGENT, version.c_str());
228 input.seekg(0, std::ios::beg);
232 while (input.good()) {
233 input.read(buffer, 4096);
234 if (input.gcount() == 0)
break;
235 md5.Update((
unsigned char*)buffer, input.gcount());
239 return md5.AsString();
262 B2DEBUG(37,
"Download started ..." <<
LogVar(
"url", url));
264 for (
unsigned int retry{1};; ++retry) {
267 buffer.seekp(0, std::ios::beg);
268 if (!buffer.good()) {
269 throw std::runtime_error(
"cannot write to stream");
272 auto oldExceptionMask = buffer.exceptions();
273 buffer.exceptions(std::ios::failbit | std::ios::badbit);
275 CURLcode res{CURLE_FAILED_INIT};
277 curl_easy_setopt(
m_session->curl, CURLOPT_URL, url.c_str());
278 curl_easy_setopt(
m_session->curl, CURLOPT_WRITEDATA, &buffer);
280 res = curl_easy_perform(
m_session->curl);
282 buffer.exceptions(oldExceptionMask);
285 if (res != CURLE_OK) {
287 const std::string error = len ?
m_session->errbuf : curl_easy_strerror(res);
288 if (
m_maxRetries > 0 && res == CURLE_HTTP_RETURNED_ERROR) {
292 long responseCode{0};
293 curl_easy_getinfo(
m_session->curl, CURLINFO_RESPONSE_CODE, &responseCode);
294 if (responseCode >= 500) {
299 double seconds = gRandom->Uniform(1., maxDelay);
300 B2WARNING(
"Could not download url, retrying ..."
302 <<
LogVar(
"try", retry) <<
LogVar(
"waiting time", seconds));
303 std::this_thread::sleep_for(std::chrono::milliseconds((
int)(seconds * 1e3)));
306 if (responseCode == 404 and silentOnMissing)
return false;
309 throw std::runtime_error(error);
314 B2DEBUG(37,
"Download finished successfully." <<
LogVar(
"url", url));
Simple class to encapsulate libcurl as used by the ConditionsDatabase.
void finishSession()
Finish an existing curl session if any is active at the moment.
static bool s_globalInit
flag to indicate whether curl has been initialized already
bool startSession()
Start a new curl session if none is active at the moment.
bool download(const std::string &url, std::ostream &stream, bool silentOnMissing=false)
get an url and save the content to stream This function raises exceptions when there are any problems
unsigned int m_maxRetries
Number of retries to perform when downloading fails with HTTP response code >=500.
unsigned int m_connectionTimeout
Timeout to wait for connections in seconds.
void setStalledTimeout(unsigned int timeout)
Set the timeout to wait for stalled connections (<10KB/s), 0 disables timeout.
std::unique_ptr< CurlSession > m_session
curl session handle
unsigned int m_stalledTimeout
Timeout to wait for stalled connections (<10KB/s)
std::string joinWithSlash(const std::string &base, const std::string &second)
Join two strings and make sure that there is exactly one '/' between them.
static std::string calculateChecksum(std::istream &input)
calculate the digest/checksum on a given string.
void setConnectionTimeout(unsigned int timeout)
Set the timeout to wait for connections in seconds, 0 means built in curl default.
std::string escapeString(const std::string &text)
Escape a string to make it safe to be used in web requests.
ScopeGuard ensureSession()
Make sure there's an active session and return a ScopeGuard object that closes the session on destruc...
unsigned int m_backoffFactor
Backoff factor for retries in seconds.
static Downloader & getDefaultInstance()
Return the default instance.
static const double ms
[millisecond]
Class to store variables with their name which were sent to the logging service.
static std::string get(const std::string &name, const std::string &fallback="")
Get the value of an environment variable or the given fallback value if the variable is not set.
static bool isSet(const std::string &name)
Check if a value is set in the database.
double getClock()
Return current value of the real-time clock.
struct encapsulating all the state information needed by curl
CURL * curl
curl session information
double lasttime
last time we printed the status (in ns)
curl_slist * headers
headers to send with every request
char errbuf[CURL_ERROR_SIZE]
error buffer in case some error happens during downloading