LCOV - code coverage report
Current view: top level - datadog - curl.cpp (source / functions) Hit Total Coverage
Test: filtered.info Lines: 290 309 93.9 %
Date: 2024-01-03 20:30:12 Functions: 59 66 89.4 %

          Line data    Source code
       1             : #include "curl.h"
       2             : 
       3             : #include <algorithm>
       4             : #include <cctype>
       5             : #include <chrono>
       6             : #include <condition_variable>
       7             : #include <cstddef>
       8             : #include <iterator>
       9             : #include <list>
      10             : #include <memory>
      11             : #include <mutex>
      12             : #include <system_error>
      13             : #include <unordered_map>
      14             : #include <unordered_set>
      15             : 
      16             : #include "clock.h"
      17             : #include "dict_reader.h"
      18             : #include "dict_writer.h"
      19             : #include "http_client.h"
      20             : #include "json.hpp"
      21             : #include "logger.h"
      22             : #include "parse_util.h"
      23             : #include "string_view.h"
      24             : 
      25             : namespace datadog {
      26             : namespace tracing {
      27             : namespace {
      28             : 
      29             : // `libcurl` is the default implementation: it calls `curl_*` functions under
      30             : // the hood.
      31             : CurlLibrary libcurl;
      32             : 
      33             : }  // namespace
      34             : 
      35          61 : CURL *CurlLibrary::easy_init() { return curl_easy_init(); }
      36             : 
      37          61 : void CurlLibrary::easy_cleanup(CURL *handle) { curl_easy_cleanup(handle); }
      38             : 
      39          97 : CURLcode CurlLibrary::easy_getinfo_private(CURL *curl, char **user_data) {
      40          97 :   return curl_easy_getinfo(curl, CURLINFO_PRIVATE, user_data);
      41             : }
      42             : 
      43           0 : CURLcode CurlLibrary::easy_getinfo_response_code(CURL *curl, long *code) {
      44           0 :   return curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, code);
      45             : }
      46             : 
      47          49 : CURLcode CurlLibrary::easy_setopt_errorbuffer(CURL *handle, char *buffer) {
      48          49 :   return curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, buffer);
      49             : }
      50             : 
      51          44 : CURLcode CurlLibrary::easy_setopt_headerdata(CURL *handle, void *data) {
      52          44 :   return curl_easy_setopt(handle, CURLOPT_HEADERDATA, data);
      53             : }
      54             : 
      55          44 : CURLcode CurlLibrary::easy_setopt_headerfunction(CURL *handle,
      56             :                                                  HeaderCallback on_header) {
      57          44 :   return curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, on_header);
      58             : }
      59             : 
      60          49 : CURLcode CurlLibrary::easy_setopt_httpheader(CURL *handle,
      61             :                                              curl_slist *headers) {
      62          49 :   return curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
      63             : }
      64             : 
      65          49 : CURLcode CurlLibrary::easy_setopt_post(CURL *handle, long post) {
      66          49 :   return curl_easy_setopt(handle, CURLOPT_POST, post);
      67             : }
      68             : 
      69          49 : CURLcode CurlLibrary::easy_setopt_postfields(CURL *handle, const char *data) {
      70          49 :   return curl_easy_setopt(handle, CURLOPT_POSTFIELDS, data);
      71             : }
      72             : 
      73          49 : CURLcode CurlLibrary::easy_setopt_postfieldsize(CURL *handle, long size) {
      74          49 :   return curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE, size);
      75             : }
      76             : 
      77          49 : CURLcode CurlLibrary::easy_setopt_private(CURL *handle, void *pointer) {
      78          49 :   return curl_easy_setopt(handle, CURLOPT_PRIVATE, pointer);
      79             : }
      80             : 
      81           0 : CURLcode CurlLibrary::easy_setopt_unix_socket_path(CURL *handle,
      82             :                                                    const char *path) {
      83           0 :   return curl_easy_setopt(handle, CURLOPT_UNIX_SOCKET_PATH, path);
      84             : }
      85             : 
      86          49 : CURLcode CurlLibrary::easy_setopt_url(CURL *handle, const char *url) {
      87          49 :   return curl_easy_setopt(handle, CURLOPT_URL, url);
      88             : }
      89             : 
      90          44 : CURLcode CurlLibrary::easy_setopt_writedata(CURL *handle, void *data) {
      91          44 :   return curl_easy_setopt(handle, CURLOPT_WRITEDATA, data);
      92             : }
      93             : 
      94          44 : CURLcode CurlLibrary::easy_setopt_writefunction(CURL *handle,
      95             :                                                 WriteCallback on_write) {
      96          44 :   return curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, on_write);
      97             : }
      98             : 
      99          43 : CURLcode CurlLibrary::easy_setopt_timeout_ms(CURL *handle, long timeout_ms) {
     100          43 :   return curl_easy_setopt(handle, CURLOPT_TIMEOUT_MS, timeout_ms);
     101             : }
     102             : 
     103          56 : const char *CurlLibrary::easy_strerror(CURLcode error) {
     104          56 :   return curl_easy_strerror(error);
     105             : }
     106             : 
     107         228 : void CurlLibrary::global_cleanup() { curl_global_cleanup(); }
     108             : 
     109         229 : CURLcode CurlLibrary::global_init(long flags) {
     110         229 :   return curl_global_init(flags);
     111             : }
     112             : 
     113          43 : CURLMcode CurlLibrary::multi_add_handle(CURLM *multi_handle,
     114             :                                         CURL *easy_handle) {
     115          43 :   return curl_multi_add_handle(multi_handle, easy_handle);
     116             : }
     117             : 
     118         228 : CURLMcode CurlLibrary::multi_cleanup(CURLM *multi_handle) {
     119         228 :   return curl_multi_cleanup(multi_handle);
     120             : }
     121             : 
     122         309 : CURLMsg *CurlLibrary::multi_info_read(CURLM *multi_handle, int *msgs_in_queue) {
     123         309 :   return curl_multi_info_read(multi_handle, msgs_in_queue);
     124             : }
     125             : 
     126         228 : CURLM *CurlLibrary::multi_init() { return curl_multi_init(); }
     127             : 
     128         266 : CURLMcode CurlLibrary::multi_perform(CURLM *multi_handle,
     129             :                                      int *running_handles) {
     130         266 :   return curl_multi_perform(multi_handle, running_handles);
     131             : }
     132             : 
     133         276 : CURLMcode CurlLibrary::multi_poll(CURLM *multi_handle, curl_waitfd extra_fds[],
     134             :                                   unsigned extra_nfds, int timeout_ms,
     135             :                                   int *numfds) {
     136         276 :   return curl_multi_poll(multi_handle, extra_fds, extra_nfds, timeout_ms,
     137         276 :                          numfds);
     138             : }
     139             : 
     140          43 : CURLMcode CurlLibrary::multi_remove_handle(CURLM *multi_handle,
     141             :                                            CURL *easy_handle) {
     142          43 :   return curl_multi_remove_handle(multi_handle, easy_handle);
     143             : }
     144             : 
     145           0 : const char *CurlLibrary::multi_strerror(CURLMcode error) {
     146           0 :   return curl_multi_strerror(error);
     147             : }
     148             : 
     149         276 : CURLMcode CurlLibrary::multi_wakeup(CURLM *multi_handle) {
     150         276 :   return curl_multi_wakeup(multi_handle);
     151             : }
     152             : 
     153          76 : curl_slist *CurlLibrary::slist_append(curl_slist *list, const char *string) {
     154          76 :   return curl_slist_append(list, string);
     155             : }
     156             : 
     157         124 : void CurlLibrary::slist_free_all(curl_slist *list) {
     158         124 :   curl_slist_free_all(list);
     159         124 : }
     160             : 
     161             : using ErrorHandler = HTTPClient::ErrorHandler;
     162             : using HeadersSetter = HTTPClient::HeadersSetter;
     163             : using ResponseHandler = HTTPClient::ResponseHandler;
     164             : using URL = HTTPClient::URL;
     165             : 
     166             : class CurlImpl {
     167             :   std::mutex mutex_;
     168             :   CurlLibrary &curl_;
     169             :   const std::shared_ptr<Logger> logger_;
     170             :   Clock clock_;
     171             :   CURLM *multi_handle_;
     172             :   std::unordered_set<CURL *> request_handles_;
     173             :   std::list<CURL *> new_handles_;
     174             :   bool shutting_down_;
     175             :   int num_active_handles_;
     176             :   std::condition_variable no_requests_;
     177             :   std::thread event_loop_;
     178             : 
     179             :   struct Request {
     180             :     CurlLibrary *curl = nullptr;
     181             :     curl_slist *request_headers = nullptr;
     182             :     std::string request_body;
     183             :     ResponseHandler on_response;
     184             :     ErrorHandler on_error;
     185             :     char error_buffer[CURL_ERROR_SIZE] = "";
     186             :     std::unordered_map<std::string, std::string> response_headers_lower;
     187             :     std::string response_body;
     188             :     std::chrono::steady_clock::time_point deadline;
     189             : 
     190             :     ~Request();
     191             :   };
     192             : 
     193             :   class HeaderWriter : public DictWriter {
     194             :     curl_slist *list_ = nullptr;
     195             :     std::string buffer_;
     196             :     CurlLibrary &curl_;
     197             : 
     198             :    public:
     199             :     explicit HeaderWriter(CurlLibrary &curl);
     200             :     ~HeaderWriter();
     201             :     curl_slist *release();
     202             :     void set(StringView key, StringView value) override;
     203             :   };
     204             : 
     205             :   class HeaderReader : public DictReader {
     206             :     std::unordered_map<std::string, std::string> *response_headers_lower_;
     207             :     mutable std::string buffer_;
     208             : 
     209             :    public:
     210             :     explicit HeaderReader(
     211             :         std::unordered_map<std::string, std::string> *response_headers_lower);
     212             :     Optional<StringView> lookup(StringView key) const override;
     213             :     void visit(const std::function<void(StringView key, StringView value)>
     214             :                    &visitor) const override;
     215             :   };
     216             : 
     217             :   void run();
     218             :   void handle_message(const CURLMsg &, std::unique_lock<std::mutex> &);
     219             :   CURLcode log_on_error(CURLcode result);
     220             :   CURLMcode log_on_error(CURLMcode result);
     221             : 
     222             :   static std::size_t on_read_header(char *data, std::size_t, std::size_t length,
     223             :                                     void *user_data);
     224             :   static std::size_t on_read_body(char *data, std::size_t, std::size_t length,
     225             :                                   void *user_data);
     226             :   static bool is_non_whitespace(unsigned char);
     227             :   static char to_lower(unsigned char);
     228             :   static StringView trim(StringView);
     229             : 
     230             :  public:
     231             :   explicit CurlImpl(const std::shared_ptr<Logger> &, const Clock &,
     232             :                     CurlLibrary &, const Curl::ThreadGenerator &);
     233             :   ~CurlImpl();
     234             : 
     235             :   Expected<void> post(const URL &url, HeadersSetter set_headers,
     236             :                       std::string body, ResponseHandler on_response,
     237             :                       ErrorHandler on_error,
     238             :                       std::chrono::steady_clock::time_point deadline);
     239             : 
     240             :   void drain(std::chrono::steady_clock::time_point deadline);
     241             : };
     242             : 
     243             : namespace {
     244             : 
     245         616 : void throw_on_error(CURLcode result) {
     246         616 :   if (result != CURLE_OK) {
     247          12 :     throw result;
     248             :   }
     249         604 : }
     250             : 
     251             : }  // namespace
     252             : 
     253         209 : Curl::Curl(const std::shared_ptr<Logger> &logger, const Clock &clock)
     254         209 :     : Curl(logger, clock, libcurl) {}
     255             : 
     256         228 : Curl::Curl(const std::shared_ptr<Logger> &logger, const Clock &clock,
     257         228 :            CurlLibrary &curl)
     258             :     : Curl(logger, clock, curl,
     259         455 :            [](auto &&func) { return std::thread(std::move(func)); }) {}
     260             : 
     261         229 : Curl::Curl(const std::shared_ptr<Logger> &logger, const Clock &clock,
     262         229 :            CurlLibrary &curl, const Curl::ThreadGenerator &make_thread)
     263         229 :     : impl_(new CurlImpl{logger, clock, curl, make_thread}) {}
     264             : 
     265         229 : Curl::~Curl() { delete impl_; }
     266             : 
     267          64 : Expected<void> Curl::post(const URL &url, HeadersSetter set_headers,
     268             :                           std::string body, ResponseHandler on_response,
     269             :                           ErrorHandler on_error,
     270             :                           std::chrono::steady_clock::time_point deadline) {
     271          64 :   return impl_->post(url, set_headers, body, on_response, on_error, deadline);
     272             : }
     273             : 
     274          23 : void Curl::drain(std::chrono::steady_clock::time_point deadline) {
     275          23 :   impl_->drain(deadline);
     276          23 : }
     277             : 
     278           8 : nlohmann::json Curl::config_json() const {
     279          40 :   return nlohmann::json::object({{"type", "datadog::tracing::Curl"}});
     280             : }
     281             : 
     282         229 : CurlImpl::CurlImpl(const std::shared_ptr<Logger> &logger, const Clock &clock,
     283         229 :                    CurlLibrary &curl, const Curl::ThreadGenerator &make_thread)
     284         229 :     : curl_(curl),
     285         229 :       logger_(logger),
     286         229 :       clock_(clock),
     287         229 :       shutting_down_(false),
     288         458 :       num_active_handles_(0) {
     289         229 :   curl_.global_init(CURL_GLOBAL_ALL);
     290         229 :   multi_handle_ = curl_.multi_init();
     291         229 :   if (multi_handle_ == nullptr) {
     292           1 :     logger_->log_error(Error{
     293             :         Error::CURL_HTTP_CLIENT_SETUP_FAILED,
     294             :         "Unable to initialize a curl multi-handle for sending requests."});
     295           1 :     return;
     296             :   }
     297             : 
     298             :   try {
     299         456 :     event_loop_ = make_thread([this]() { run(); });
     300           1 :   } catch (const std::system_error &error) {
     301           2 :     logger_->log_error(
     302           2 :         Error{Error::CURL_HTTP_CLIENT_SETUP_FAILED, error.what()});
     303             : 
     304             :     // Usually the worker thread would do this, but since the thread failed to
     305             :     // start, do it here.
     306           1 :     (void)curl_.multi_cleanup(multi_handle_);
     307           1 :     curl_.global_cleanup();
     308             : 
     309             :     // Mark this object as not working.
     310           1 :     multi_handle_ = nullptr;
     311           1 :   }
     312           0 : }
     313             : 
     314         458 : CurlImpl::~CurlImpl() {
     315         229 :   if (multi_handle_ == nullptr) {
     316             :     // We're not running; nothing to shut down.
     317           2 :     return;
     318             :   }
     319             : 
     320             :   {
     321         227 :     std::lock_guard<std::mutex> lock(mutex_);
     322         227 :     shutting_down_ = true;
     323         227 :   }
     324         227 :   log_on_error(curl_.multi_wakeup(multi_handle_));
     325         227 :   event_loop_.join();
     326             : 
     327         227 :   log_on_error(curl_.multi_cleanup(multi_handle_));
     328         227 :   curl_.global_cleanup();
     329         239 : }
     330             : 
     331          64 : Expected<void> CurlImpl::post(
     332             :     const HTTPClient::URL &url, HeadersSetter set_headers, std::string body,
     333             :     ResponseHandler on_response, ErrorHandler on_error,
     334             :     std::chrono::steady_clock::time_point deadline) try {
     335          64 :   if (multi_handle_ == nullptr) {
     336           4 :     return Error{Error::CURL_HTTP_CLIENT_NOT_RUNNING,
     337             :                  "Unable to send request via libcurl because the HTTP client "
     338           2 :                  "failed to start."};
     339             :   }
     340             : 
     341          62 :   HeaderWriter writer{curl_};
     342          62 :   set_headers(writer);
     343           0 :   auto cleanup_list = [&](auto list) { curl_.slist_free_all(list); };
     344             :   std::unique_ptr<curl_slist, decltype(cleanup_list)> headers{
     345          62 :       writer.release(), std::move(cleanup_list)};
     346             : 
     347          62 :   auto request = std::make_unique<Request>();
     348             : 
     349          62 :   request->curl = &curl_;
     350          62 :   request->request_headers = headers.get();
     351          62 :   request->request_body = std::move(body);
     352          62 :   request->on_response = std::move(on_response);
     353          62 :   request->on_error = std::move(on_error);
     354          62 :   request->deadline = std::move(deadline);
     355             : 
     356          12 :   auto cleanup_handle = [&](auto handle) { curl_.easy_cleanup(handle); };
     357             :   std::unique_ptr<CURL, decltype(cleanup_handle)> handle{
     358          62 :       curl_.easy_init(), std::move(cleanup_handle)};
     359             : 
     360          62 :   if (!handle) {
     361           2 :     return Error{Error::CURL_REQUEST_SETUP_FAILED,
     362           1 :                  "unable to initialize a curl handle for request sending"};
     363             :   }
     364             : 
     365          61 :   throw_on_error(
     366          61 :       curl_.easy_setopt_httpheader(handle.get(), request->request_headers));
     367          60 :   throw_on_error(curl_.easy_setopt_private(handle.get(), request.get()));
     368          59 :   throw_on_error(
     369          59 :       curl_.easy_setopt_errorbuffer(handle.get(), request->error_buffer));
     370          58 :   throw_on_error(curl_.easy_setopt_post(handle.get(), 1));
     371          57 :   throw_on_error(curl_.easy_setopt_postfieldsize(handle.get(),
     372          57 :                                                  request->request_body.size()));
     373          56 :   throw_on_error(
     374          56 :       curl_.easy_setopt_postfields(handle.get(), request->request_body.data()));
     375          55 :   throw_on_error(
     376          55 :       curl_.easy_setopt_headerfunction(handle.get(), &on_read_header));
     377          54 :   throw_on_error(curl_.easy_setopt_headerdata(handle.get(), request.get()));
     378          53 :   throw_on_error(curl_.easy_setopt_writefunction(handle.get(), &on_read_body));
     379          52 :   throw_on_error(curl_.easy_setopt_writedata(handle.get(), request.get()));
     380         101 :   if (url.scheme == "unix" || url.scheme == "http+unix" ||
     381          50 :       url.scheme == "https+unix") {
     382           1 :     throw_on_error(curl_.easy_setopt_unix_socket_path(handle.get(),
     383             :                                                       url.authority.c_str()));
     384             :     // The authority section of the URL is ignored when a unix domain socket is
     385             :     // to be used.
     386           0 :     throw_on_error(curl_.easy_setopt_url(
     387           0 :         handle.get(), ("http://localhost" + url.path).c_str()));
     388             :   } else {
     389          50 :     throw_on_error(curl_.easy_setopt_url(
     390         103 :         handle.get(), (url.scheme + "://" + url.authority + url.path).c_str()));
     391             :   }
     392             : 
     393          49 :   std::list<CURL *> node;
     394          49 :   node.push_back(handle.get());
     395             :   {
     396          49 :     std::lock_guard<std::mutex> lock(mutex_);
     397          49 :     new_handles_.splice(new_handles_.end(), node);
     398             : 
     399          49 :     (void)headers.release();
     400          49 :     (void)handle.release();
     401          49 :     (void)request.release();
     402          49 :   }
     403             : 
     404          49 :   log_on_error(curl_.multi_wakeup(multi_handle_));
     405             : 
     406          49 :   return nullopt;
     407         110 : } catch (CURLcode error) {
     408          12 :   return Error{Error::CURL_REQUEST_SETUP_FAILED, curl_.easy_strerror(error)};
     409          12 : }
     410             : 
     411          23 : void CurlImpl::drain(std::chrono::steady_clock::time_point deadline) {
     412          23 :   std::unique_lock<std::mutex> lock(mutex_);
     413          23 :   no_requests_.wait_until(lock, deadline, [this]() {
     414          46 :     return num_active_handles_ == 0 && new_handles_.empty();
     415             :   });
     416          23 : }
     417             : 
     418          16 : std::size_t CurlImpl::on_read_header(char *data, std::size_t,
     419             :                                      std::size_t length, void *user_data) {
     420          16 :   const auto request = static_cast<Request *>(user_data);
     421             :   // The idea is:
     422             :   //
     423             :   //         "    Foo-Bar  :   thingy, thingy, thing   \r\n"
     424             :   //    -> {"foo-bar", "thingy, thingy, thing"}
     425             :   //
     426             :   // There isn't always a colon.  Inputs without a colon can be ignored:
     427             :   //
     428             :   // > For an HTTP transfer, the status line and the blank line preceding the
     429             :   // > response body are both included as headers and passed to this
     430             :   // > function.
     431             :   //
     432             :   // https://curl.se/libcurl/c/CURLOPT_HEADERFUNCTION.html
     433             :   //
     434             : 
     435          16 :   const char *const begin = data;
     436          16 :   const char *const end = begin + length;
     437          16 :   const char *const colon = std::find(begin, end, ':');
     438          16 :   if (colon == end) {
     439           4 :     return length;
     440             :   }
     441             : 
     442          12 :   const auto key = strip(range(begin, colon));
     443          12 :   const auto value = strip(range(colon + 1, end));
     444             : 
     445          12 :   std::string key_lower;
     446          12 :   key_lower.reserve(key.size());
     447          12 :   std::transform(key.begin(), key.end(), std::back_inserter(key_lower),
     448             :                  &to_lower);
     449             : 
     450          12 :   request->response_headers_lower.emplace(std::move(key_lower), value);
     451          12 :   return length;
     452          12 : }
     453             : 
     454           0 : bool CurlImpl::is_non_whitespace(unsigned char ch) { return !std::isspace(ch); }
     455             : 
     456         121 : char CurlImpl::to_lower(unsigned char ch) { return std::tolower(ch); }
     457             : 
     458           8 : std::size_t CurlImpl::on_read_body(char *data, std::size_t, std::size_t length,
     459             :                                    void *user_data) {
     460           8 :   const auto request = static_cast<Request *>(user_data);
     461           8 :   request->response_body.append(data, length);
     462           8 :   return length;
     463             : }
     464             : 
     465         148 : CURLcode CurlImpl::log_on_error(CURLcode result) {
     466         148 :   if (result != CURLE_OK) {
     467           0 :     logger_->log_error(
     468           0 :         Error{Error::CURL_HTTP_CLIENT_ERROR, curl_.easy_strerror(result)});
     469             :   }
     470         148 :   return result;
     471             : }
     472             : 
     473        1151 : CURLMcode CurlImpl::log_on_error(CURLMcode result) {
     474        1151 :   if (result != CURLM_OK) {
     475           0 :     logger_->log_error(
     476           0 :         Error{Error::CURL_HTTP_CLIENT_ERROR, curl_.multi_strerror(result)});
     477             :   }
     478        1151 :   return result;
     479             : }
     480             : 
     481         227 : void CurlImpl::run() {
     482             :   int num_messages_remaining;
     483             :   CURLMsg *message;
     484         227 :   const int max_wait_milliseconds = 10000;
     485         227 :   std::unique_lock<std::mutex> lock(mutex_);
     486             : 
     487             :   for (;;) {
     488         276 :     log_on_error(curl_.multi_perform(multi_handle_, &num_active_handles_));
     489         276 :     if (num_active_handles_ == 0) {
     490         272 :       no_requests_.notify_all();
     491             :     }
     492             : 
     493             :     // If a request is done or errored out, curl will enqueue a "message" for
     494             :     // us to handle.  Handle any pending messages.
     495         323 :     while ((message = curl_.multi_info_read(multi_handle_,
     496             :                                             &num_messages_remaining))) {
     497          47 :       handle_message(*message, lock);
     498             :     }
     499         276 :     lock.unlock();
     500         276 :     log_on_error(curl_.multi_poll(multi_handle_, nullptr, 0,
     501             :                                   max_wait_milliseconds, nullptr));
     502         276 :     lock.lock();
     503             : 
     504             :     // New requests might have been added while we were sleeping.
     505         325 :     for (; !new_handles_.empty(); new_handles_.pop_front()) {
     506          49 :       CURL *handle = new_handles_.front();
     507             :       char *user_data;
     508          49 :       if (log_on_error(curl_.easy_getinfo_private(handle, &user_data)) !=
     509             :           CURLE_OK) {
     510           0 :         curl_.easy_cleanup(handle);
     511           1 :         continue;
     512             :       }
     513             : 
     514          49 :       auto *request = reinterpret_cast<Request *>(user_data);
     515          49 :       const auto timeout = request->deadline - clock_().tick;
     516          49 :       if (timeout <= std::chrono::steady_clock::time_point::duration::zero()) {
     517           1 :         std::string message;
     518             :         message +=
     519             :             "Request deadline exceeded before request was even added to "
     520             :             "libcurl "
     521           1 :             "event loop. Deadline was ";
     522           2 :         message += std::to_string(
     523           1 :             -std::chrono::duration_cast<std::chrono::nanoseconds>(timeout)
     524           2 :                  .count());
     525           1 :         message += " nanoseconds ago.";
     526           1 :         request->on_error(
     527           2 :             Error{Error::CURL_DEADLINE_EXCEEDED_BEFORE_REQUEST_START,
     528           1 :                   std::move(message)});
     529             : 
     530           1 :         curl_.easy_cleanup(handle);
     531           1 :         delete request;
     532             : 
     533           1 :         continue;
     534           1 :       }
     535             : 
     536          48 :       log_on_error(curl_.easy_setopt_timeout_ms(
     537          48 :           handle, std::chrono::duration_cast<std::chrono::milliseconds>(timeout)
     538             :                       .count()));
     539          48 :       log_on_error(curl_.multi_add_handle(multi_handle_, handle));
     540          48 :       request_handles_.insert(handle);
     541             :     }
     542             : 
     543         276 :     if (shutting_down_) {
     544         227 :       break;
     545             :     }
     546          49 :   }
     547             : 
     548             :   // We're shutting down.  Clean up any remaining request handles.
     549         228 :   for (const auto &handle : request_handles_) {
     550             :     char *user_data;
     551           1 :     if (log_on_error(curl_.easy_getinfo_private(handle, &user_data)) ==
     552             :         CURLE_OK) {
     553           1 :       delete reinterpret_cast<Request *>(user_data);
     554             :     }
     555             : 
     556           1 :     log_on_error(curl_.multi_remove_handle(multi_handle_, handle));
     557           1 :     curl_.easy_cleanup(handle);
     558             :   }
     559             : 
     560         227 :   request_handles_.clear();
     561         227 : }
     562             : 
     563          47 : void CurlImpl::handle_message(const CURLMsg &message,
     564             :                               std::unique_lock<std::mutex> &lock) {
     565          47 :   if (message.msg != CURLMSG_DONE) {
     566           0 :     return;
     567             :   }
     568             : 
     569          47 :   auto *const request_handle = message.easy_handle;
     570             :   char *user_data;
     571          47 :   if (log_on_error(curl_.easy_getinfo_private(request_handle, &user_data)) !=
     572             :       CURLE_OK) {
     573           0 :     return;
     574             :   }
     575          47 :   auto &request = *reinterpret_cast<Request *>(user_data);
     576             : 
     577             :   // `request` is done.  If we got a response, then call the response
     578             :   // handler.  If an error occurred, then call the error handler.
     579          47 :   const auto result = message.data.result;
     580          47 :   if (result != CURLE_OK) {
     581          44 :     std::string error_message;
     582          44 :     error_message += "Error sending request with libcurl (";
     583          44 :     error_message += curl_.easy_strerror(result);
     584          44 :     error_message += "): ";
     585          44 :     error_message += request.error_buffer;
     586          44 :     lock.unlock();
     587          44 :     request.on_error(
     588          88 :         Error{Error::CURL_REQUEST_FAILURE, std::move(error_message)});
     589          44 :     lock.lock();
     590          44 :   } else {
     591             :     long status;
     592           3 :     if (log_on_error(curl_.easy_getinfo_response_code(request_handle,
     593           3 :                                                       &status)) != CURLE_OK) {
     594           0 :       status = -1;
     595             :     }
     596           3 :     HeaderReader reader(&request.response_headers_lower);
     597           3 :     lock.unlock();
     598           3 :     request.on_response(static_cast<int>(status), reader,
     599           3 :                         std::move(request.response_body));
     600           3 :     lock.lock();
     601           3 :   }
     602             : 
     603          47 :   log_on_error(curl_.multi_remove_handle(multi_handle_, request_handle));
     604          47 :   curl_.easy_cleanup(request_handle);
     605          47 :   request_handles_.erase(request_handle);
     606          47 :   delete &request;
     607             : }
     608             : 
     609          62 : CurlImpl::Request::~Request() { curl->slist_free_all(request_headers); }
     610             : 
     611          62 : CurlImpl::HeaderWriter::HeaderWriter(CurlLibrary &curl) : curl_(curl) {}
     612             : 
     613          62 : CurlImpl::HeaderWriter::~HeaderWriter() { curl_.slist_free_all(list_); }
     614             : 
     615          62 : curl_slist *CurlImpl::HeaderWriter::release() {
     616          62 :   auto list = list_;
     617          62 :   list_ = nullptr;
     618          62 :   return list;
     619             : }
     620             : 
     621          76 : void CurlImpl::HeaderWriter::set(StringView key, StringView value) {
     622          76 :   buffer_.clear();
     623          76 :   buffer_ += key;
     624          76 :   buffer_ += ": ";
     625          76 :   buffer_ += value;
     626             : 
     627          76 :   list_ = curl_.slist_append(list_, buffer_.c_str());
     628          76 : }
     629             : 
     630           3 : CurlImpl::HeaderReader::HeaderReader(
     631           3 :     std::unordered_map<std::string, std::string> *response_headers_lower)
     632           3 :     : response_headers_lower_(response_headers_lower) {}
     633             : 
     634           3 : Optional<StringView> CurlImpl::HeaderReader::lookup(StringView key) const {
     635           3 :   buffer_.clear();
     636           3 :   std::transform(key.begin(), key.end(), std::back_inserter(buffer_),
     637             :                  &to_lower);
     638             : 
     639           3 :   const auto found = response_headers_lower_->find(buffer_);
     640           3 :   if (found == response_headers_lower_->end()) {
     641           1 :     return nullopt;
     642             :   }
     643           2 :   return found->second;
     644             : }
     645             : 
     646           1 : void CurlImpl::HeaderReader::visit(
     647             :     const std::function<void(StringView key, StringView value)> &visitor)
     648             :     const {
     649           3 :   for (const auto &[key, value] : *response_headers_lower_) {
     650           2 :     visitor(key, value);
     651             :   }
     652           1 : }
     653             : 
     654             : }  // namespace tracing
     655             : }  // namespace datadog

Generated by: LCOV version 1.16