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
|