9#include <framework/database/Downloader.h> 
   10#include <framework/core/RandomNumbers.h> 
   11#include <framework/gearbox/Unit.h> 
   12#include <framework/logging/Logger.h> 
   13#include <framework/utilities/Utils.h> 
   14#include <framework/utilities/EnvironmentVariables.h> 
   19#include <boost/algorithm/string.hpp> 
   24namespace Belle2::Conditions {
 
   48    size_t write_function(
void* buffer, 
size_t size, 
size_t nmemb, 
void* userp)
 
   52        std::ostream& stream = *
static_cast<std::ostream*
>(userp);
 
   53        stream.write(
static_cast<const char*
>(buffer), size * nmemb);
 
   54      } 
catch (std::ios_base::failure& e) {
 
   55        B2ERROR(
"Writing error while downloading: " << e.code().message() << 
'(' << e.code().value() << 
')');
 
   71    int progress_callback(
void* clientp, curl_off_t dltotal, curl_off_t dlnow,
 
   72                          __attribute((unused)) curl_off_t ultotal, __attribute((unused)) curl_off_t ulnow)
 
   75      if (dlnow == 0) 
return 0;
 
   80      if (status.lasttime != 0 && (time - status.lasttime) / 
Unit::ms < 200) {
 
   83      status.lasttime = time;
 
   85        B2DEBUG(39, 
"curl:= " << dlnow << 
" / " << dltotal << 
" bytes transferred");
 
   87        B2DEBUG(39, 
"curl:= " << dlnow << 
" bytes transferred");
 
  102    int debug_callback([[maybe_unused]] CURL* handle, curl_infotype type, 
char* data, 
size_t size,
 
  103                       [[maybe_unused]] 
void* userptr)
 
  105      std::string prefix = 
"curl:";
 
  111      if (type == CURLINFO_TEXT) { prefix += 
"*"; level = 38; }
 
  112      else if (type == CURLINFO_HEADER_OUT) prefix += 
">";
 
  113      else if (type == CURLINFO_HEADER_IN) prefix += 
"<";
 
  116      std::string message(data, size);
 
  117      boost::trim(message);
 
  119      if (!message.empty()) B2DEBUG(level, prefix << 
" " <<  message);
 
  124    std::string getUserAgent()
 
  144    char* escaped = curl_easy_escape(
m_session->curl, text.c_str(), text.size());
 
  146      throw std::runtime_error(
"Could not escape string");
 
  148    std::string escapedStr{escaped};
 
 
  156    return boost::trim_right_copy_if(base, boost::is_any_of(
"/")) + 
"/" +
 
  157           boost::trim_left_copy_if(rest, boost::is_any_of(
"/"));
 
 
  166      curl_global_init(CURL_GLOBAL_ALL);
 
  170    m_session = std::make_unique<CurlSession>();
 
  173      B2FATAL(
"Cannot initialize libcurl");
 
  175    m_session->headers = curl_slist_append(
nullptr, 
"Accept: application/json");
 
  177    curl_easy_setopt(
m_session->curl, CURLOPT_TCP_KEEPALIVE, 1L);
 
  179    curl_easy_setopt(
m_session->curl, CURLOPT_LOW_SPEED_LIMIT, 10 * 1024); 
 
  181    curl_easy_setopt(
m_session->curl, CURLOPT_WRITEFUNCTION, write_function);
 
  182    curl_easy_setopt(
m_session->curl, CURLOPT_VERBOSE, 1);
 
  183    curl_easy_setopt(
m_session->curl, CURLOPT_NOPROGRESS, 0);
 
  184    curl_easy_setopt(
m_session->curl, CURLOPT_DEBUGFUNCTION, debug_callback);
 
  185    curl_easy_setopt(
m_session->curl, CURLOPT_XFERINFOFUNCTION, progress_callback);
 
  187    curl_easy_setopt(
m_session->curl, CURLOPT_FAILONERROR, 
true);
 
  190    curl_easy_setopt(
m_session->curl, CURLOPT_ACCEPT_ENCODING, 
"");
 
  194      curl_easy_setopt(
m_session->curl, CURLOPT_PROXY, proxy.c_str());
 
  196    curl_easy_setopt(
m_session->curl, CURLOPT_AUTOREFERER, 1L);
 
  197    curl_easy_setopt(
m_session->curl, CURLOPT_FOLLOWLOCATION, 1L);
 
  198    curl_easy_setopt(
m_session->curl, CURLOPT_MAXREDIRS, 10L);
 
  199    curl_easy_setopt(
m_session->curl, CURLOPT_TCP_FASTOPEN, 0L);
 
  200    curl_easy_setopt(
m_session->curl, CURLOPT_SSL_VERIFYPEER, 0L);
 
  201    curl_easy_setopt(
m_session->curl, CURLOPT_SSL_VERIFYHOST, 0L);
 
  202    curl_easy_setopt(
m_session->curl, CURLOPT_SSL_VERIFYSTATUS, 0L);
 
  203    curl_easy_setopt(
m_session->curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_WHATEVER);
 
  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();
 
 
  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) {
 
  295            long responseCode{0};
 
  296            curl_easy_getinfo(
m_session->curl, CURLINFO_RESPONSE_CODE, &responseCode);
 
  297            if (responseCode >= 300 and responseCode != 404) {
 
  308              m_rndDistribution->param(std::uniform_real_distribution<double>::param_type(1.0, maxDelay));
 
  309              double seconds = (*m_rndDistribution)(*m_rnd);
 
  310              B2WARNING(
"Could not download url, retrying ..." 
  312                        << 
LogVar(
"try", retry) << 
LogVar(
"waiting time", seconds));
 
  313              std::this_thread::sleep_for(std::chrono::milliseconds((
int)(seconds * 1e3)));
 
  318            if (responseCode == 404 and silentOnMissing) 
return false;
 
  321        throw std::runtime_error(error);
 
  326    B2DEBUG(37, 
"Download finished successfully." << 
LogVar(
"url", url));
 
 
  335      m_rnd->seed(downloaderSeed);
 
 
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
std::unique_ptr< std::uniform_real_distribution< double > > m_rndDistribution
A uniform real distribution for extracting random numbers.
bool startSession()
Start a new curl session if none is active at the moment.
void initializeRandomGeneratorSeed()
Initialize the seed of the internal random number generator.
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 >=300.
unsigned int m_connectionTimeout
Timeout to wait for connections in seconds.
Downloader()=default
Create a new payload downloader.
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)
bool m_rndIsInitialized
Flag for keeping track if the internal random generator is correctly initialized or not.
std::unique_ptr< std::mt19937 > m_rnd
This is a special exception in basf2 where an instance of gRandom is NOT used: since this class inter...
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 std::string getSeed()
Get the random number generator seed.
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