diff --git a/olp-cpp-sdk-core/include/olp/core/http/NetworkInitializationSettings.h b/olp-cpp-sdk-core/include/olp/core/http/NetworkInitializationSettings.h index ae1ca4250..e5b8bfb3a 100644 --- a/olp-cpp-sdk-core/include/olp/core/http/NetworkInitializationSettings.h +++ b/olp-cpp-sdk-core/include/olp/core/http/NetworkInitializationSettings.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 HERE Europe B.V. + * Copyright (C) 2023-2025 HERE Europe B.V. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,10 @@ #pragma once -#include "olp/core/CoreApi.h" -#include "olp/core/http/CertificateSettings.h" +#include + +#include +#include namespace olp { namespace http { @@ -38,6 +40,12 @@ struct CORE_API NetworkInitializationSettings { * @brief The custom certificate settings. */ CertificateSettings certificate_settings; + + /** + * @brief Enable the underlying implementation to produce additional logs. + * Note: This is EXPERIMENTAL settings, only CURL implementation supports it. + */ + boost::optional log_file_path; }; } // namespace http diff --git a/olp-cpp-sdk-core/src/http/curl/NetworkCurl.cpp b/olp-cpp-sdk-core/src/http/curl/NetworkCurl.cpp index 40fbbaf63..39b79f920 100644 --- a/olp-cpp-sdk-core/src/http/curl/NetworkCurl.cpp +++ b/olp-cpp-sdk-core/src/http/curl/NetworkCurl.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2024 HERE Europe B.V. + * Copyright (C) 2019-2025 HERE Europe B.V. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ #include "olp/core/utils/Thread.h" #if defined(HAVE_SIGNAL_H) -#include +#include #endif #ifdef OLP_SDK_USE_MD5_CERT_LOOKUP @@ -142,7 +142,7 @@ int BlockSigpipe() { return err; } - err = pthread_sigmask(SIG_BLOCK, &sigset, NULL); + err = pthread_sigmask(SIG_BLOCK, &sigset, nullptr); return err; } @@ -203,22 +203,18 @@ int ConvertErrorCode(CURLcode curl_code) { } } -/** - * @brief CURL get upload/download data. - * @param[in] handle CURL easy handle. - * @param[out] upload_bytes uploaded bytes(headers+data). - * @param[out] download_bytes downloaded bytes(headers+data). - */ void GetTrafficData(CURL* handle, uint64_t& upload_bytes, - uint64_t& download_bytes) { + uint64_t& download_headers_size, + uint64_t& download_body_bytes) { upload_bytes = 0; - download_bytes = 0; + download_headers_size = 0; + download_body_bytes = 0; long headers_size; if (curl_easy_getinfo(handle, CURLINFO_HEADER_SIZE, &headers_size) == CURLE_OK && headers_size > 0) { - download_bytes += headers_size; + download_headers_size = headers_size; } #if CURL_AT_LEAST_VERSION(7, 55, 0) @@ -226,14 +222,14 @@ void GetTrafficData(CURL* handle, uint64_t& upload_bytes, if (curl_easy_getinfo(handle, CURLINFO_SIZE_DOWNLOAD_T, &length_downloaded) == CURLE_OK && length_downloaded > 0) { - download_bytes += length_downloaded; + download_body_bytes = length_downloaded; } #else double length_downloaded; if (curl_easy_getinfo(handle, CURLINFO_SIZE_DOWNLOAD, &length_downloaded) == CURLE_OK && length_downloaded > 0.0) { - download_bytes += length_downloaded; + download_body_bytes += length_downloaded; } #endif @@ -290,13 +286,160 @@ long CountIn(Duration&& duration) { return static_cast(count); } +std::shared_ptr SetupHeaders(const Headers& headers) { + curl_slist* list{nullptr}; + std::ostringstream ss; + for (const auto& header : headers) { + ss << header.first << ": " << header.second; + list = curl_slist_append(list, ss.str().c_str()); + ss.str(""); + ss.clear(); + } + return {list, curl_slist_free_all}; +} + +void SetupProxy(CURL* curl_handle, const NetworkProxySettings& proxy) { + if (proxy.GetType() == NetworkProxySettings::Type::NONE) { + return; + } + + curl_easy_setopt(curl_handle, CURLOPT_PROXY, proxy.GetHostname().c_str()); + curl_easy_setopt(curl_handle, CURLOPT_PROXYPORT, proxy.GetPort()); + + const auto proxy_type = proxy.GetType(); + if (proxy_type != NetworkProxySettings::Type::HTTP) { + curl_easy_setopt(curl_handle, CURLOPT_PROXYTYPE, + ToCurlProxyType(proxy_type)); + } + + // We expect that both fields are empty or filled + const auto& username = proxy.GetUsername(); + const auto& password = proxy.GetPassword(); + if (!username.empty() && !password.empty()) { + curl_easy_setopt(curl_handle, CURLOPT_PROXYUSERNAME, username.c_str()); + curl_easy_setopt(curl_handle, CURLOPT_PROXYPASSWORD, password.c_str()); + } +} + +void SetupRequestBody(CURL* curl_handle, NetworkRequest::RequestBodyType body) { + if (body && !body->empty()) { + curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, body->size()); + curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, &body->front()); + } else { + // Some services (eg. Google) require the field size even if zero + curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, 0L); + } +} + +void SetupDns(CURL* curl_handle, const std::vector& dns_servers) { +#if CURL_AT_LEAST_VERSION(7, 24, 0) + if (!dns_servers.empty()) { + const std::string& dns_list = dns_servers.size() == 1 + ? dns_servers.front() + : ConcatenateDnsAddresses(dns_servers); + curl_easy_setopt(curl_handle, CURLOPT_DNS_SERVERS, dns_list.c_str()); + } +#endif +} + +const char* MethodName(NetworkRequest::HttpVerb verb) { + const char* names[] = {"GET", "POST", "HEAD", "PUT", + "DEL", "PATCH", "OPTIONS"}; + + if (verb >= NetworkRequest::HttpVerb::GET && + verb <= NetworkRequest::HttpVerb::OPTIONS) { + return names[static_cast(verb)]; + } + return "UNKNOWN"; +} + +SessionRecording::Timings GetTimings(CURL* handle) { + using Microseconds = SessionRecording::MicroSeconds; + + SessionRecording::Timings timings{}; + + long queue_time_us = 0; +#if CURL_AT_LEAST_VERSION(8, 6, 0) + if (curl_easy_getinfo(handle, CURLINFO_QUEUE_TIME_T, &queue_time_us) == + CURLE_OK && + queue_time_us > 0) { + timings.queue_time = Microseconds(queue_time_us); + } +#else + timings.queue_time = Microseconds(queue_time_us); +#endif + + // 7.61.0 + long name_lookup_us = 0; + if (curl_easy_getinfo(handle, CURLINFO_NAMELOOKUP_TIME_T, &name_lookup_us) == + CURLE_OK && + name_lookup_us > 0) { + timings.name_lookup_time = Microseconds(name_lookup_us); + } + + // 7.61.0 + long connect_time_us = 0; + if (curl_easy_getinfo(handle, CURLINFO_CONNECT_TIME_T, &connect_time_us) == + CURLE_OK && + connect_time_us > 0) { + timings.connect_time = Microseconds(connect_time_us); + } + + // 7.61.0 + long app_connect_time_us = 0; + if (curl_easy_getinfo(handle, CURLINFO_APPCONNECT_TIME_T, + &app_connect_time_us) == CURLE_OK && + app_connect_time_us > 0) { + timings.app_connect_time = Microseconds(app_connect_time_us); + } + + // 7.61.0 + long pre_transfer_time_us = 0; + if (curl_easy_getinfo(handle, CURLINFO_PRETRANSFER_TIME_T, + &pre_transfer_time_us) == CURLE_OK && + pre_transfer_time_us > 0) { + timings.pre_transfer_time = Microseconds(pre_transfer_time_us); + } + + // 7.61.0 + long start_transfer_time_us = 0; + if (curl_easy_getinfo(handle, CURLINFO_STARTTRANSFER_TIME_T, + &start_transfer_time_us) == CURLE_OK && + start_transfer_time_us > 0) { + timings.start_transfer_time = Microseconds(start_transfer_time_us); + } + + // 8.10.0 +#if CURL_AT_LEAST_VERSION(8, 10, 0) + long post_transfer_time_us = 0; + if (curl_easy_getinfo(handle, CURLINFO_POSTTRANSFER_TIME_T, + &post_transfer_time_us) == CURLE_OK && + post_transfer_time_us > 0) { + timings.post_transfer_time = Microseconds(post_transfer_time_us); + } +#else + timings.post_transfer_time = timings.start_transfer_time; +#endif + + // 7.61.0 + long total_time_us = 0; + if (curl_easy_getinfo(handle, CURLINFO_TOTAL_TIME_T, &total_time_us) == + CURLE_OK && + total_time_us > 0) { + timings.total_time = Microseconds(total_time_us); + } + + return timings; +} + } // anonymous namespace NetworkCurl::NetworkCurl(NetworkInitializationSettings settings) : handles_(settings.max_requests_count), static_handle_count_( std::max(static_cast(1u), settings.max_requests_count / 4u)), - certificate_settings_(std::move(settings.certificate_settings)) { + certificate_settings_(std::move(settings.certificate_settings)), + curl_log_path_(settings.log_file_path) { OLP_SDK_LOG_TRACE(kLogTag, "Created NetworkCurl with address=" << this << ", handles_count=" << settings.max_requests_count); @@ -307,6 +450,19 @@ NetworkCurl::NetworkCurl(NetworkInitializationSettings settings) static_cast(error)); } + if (curl_log_path_) { + stderr_ = fopen(curl_log_path_->c_str(), "w+"); + if (stderr_ == nullptr) { + OLP_SDK_LOG_WARNING( + kLogTag, "Failed to init curl logging, error: " << strerror(errno)); + } else { + OLP_SDK_LOG_INFO_F(kLogTag, "Curl logs enabled to file: %s", + curl_log_path_->c_str()); + } + } + + session_recording_.emplace(SessionRecording()); + #ifdef OLP_SDK_CURL_HAS_SUPPORT_SSL_BLOBS SetupCertificateBlobs(); #else @@ -358,6 +514,11 @@ NetworkCurl::~NetworkCurl() { if (stderr_) { fclose(stderr_); } + if (session_recording_) { + session_recording_->locked([](const SessionRecording& recording) { + recording.ArchiveToFile("/tmp/test.har"); + }); + } } bool NetworkCurl::Initialize() { @@ -416,14 +577,6 @@ bool NetworkCurl::Initialize() { const auto connects_cache_size = handles_.size() * 4; curl_multi_setopt(curl_, CURLMOPT_MAXCONNECTS, connects_cache_size); - // handles setup - std::shared_ptr that = shared_from_this(); - for (auto& handle : handles_) { - handle.handle = nullptr; - handle.in_use = false; - handle.self = that; - } - std::unique_lock lock(event_mutex_); // start worker thread thread_ = std::thread(&NetworkCurl::Run, this); @@ -472,20 +625,20 @@ void NetworkCurl::Deinitialize() { } void NetworkCurl::Teardown() { - std::vector > completed_messages; + std::vector> completed_messages; { std::lock_guard lock(event_mutex_); events_.clear(); // handles teardown for (auto& handle : handles_) { - if (handle.handle) { + if (handle.curl_handle) { if (handle.in_use) { - curl_multi_remove_handle(curl_, handle.handle); - completed_messages.emplace_back(handle.id, handle.callback); + curl_multi_remove_handle(curl_, handle.curl_handle.get()); + completed_messages.emplace_back(handle.id, + handle.out_completion_callback); } - curl_easy_cleanup(handle.handle); - handle.handle = nullptr; + handle.curl_handle = nullptr; } handle.self.reset(); } @@ -534,9 +687,8 @@ size_t NetworkCurl::AmountPending() { SendOutcome NetworkCurl::Send(NetworkRequest request, std::shared_ptr payload, - Network::Callback callback, - Network::HeaderCallback header_callback, - Network::DataCallback data_callback) { + Callback callback, HeaderCallback header_callback, + DataCallback data_callback) { if (!Initialized()) { if (!Initialize()) { OLP_SDK_LOG_ERROR(kLogTag, "Send failed - network is uninitialized, url=" @@ -574,8 +726,8 @@ SendOutcome NetworkCurl::Send(NetworkRequest request, ErrorCode NetworkCurl::SendImplementation( const NetworkRequest& request, RequestId id, const std::shared_ptr& payload, - Network::HeaderCallback header_callback, - Network::DataCallback data_callback, Network::Callback callback) { + HeaderCallback header_callback, DataCallback data_callback, + Callback callback) { if (!IsStarted()) { OLP_SDK_LOG_ERROR( kLogTag, "Send failed - network is offline, url=" << request.GetUrl()); @@ -584,36 +736,44 @@ ErrorCode NetworkCurl::SendImplementation( const auto& config = request.GetSettings(); - RequestHandle* handle = - GetHandle(id, std::move(callback), std::move(header_callback), - std::move(data_callback), payload, request.GetBody()); + RequestHandle* handle = InitRequestHandle(); if (!handle) { return ErrorCode::NETWORK_OVERLOAD_ERROR; } + // Set request output callbacks + handle->out_completion_callback = std::move(callback); + handle->out_header_callback = std::move(header_callback); + handle->out_data_callback = std::move(data_callback); + handle->out_data_stream = payload; + + handle->id = id; + + handle->request_url = request.GetUrl(); + handle->request_method = MethodName(request.GetVerb()); + handle->request_body = request.GetBody(); + handle->request_headers = SetupHeaders(request.GetHeaders()); + + if (session_recording_) { + session_recording_->locked([&](SessionRecording& recording) { + recording.SetRequestParameters( + id, handle->request_url, request.GetVerb(), request.GetHeaders(), + handle->request_body ? handle->request_body->size() : 0); + }); + } + OLP_SDK_LOG_DEBUG(kLogTag, "Send request with url=" << utils::CensorCredentialsInUrl(request.GetUrl()) << ", id=" << id); - handle->ignore_offset = false; // request.IgnoreOffset(); - handle->skip_content = false; // config->SkipContentWhenError(); + CURL* curl_handle = handle->curl_handle.get(); - for (const auto& header : request.GetHeaders()) { - std::ostringstream sstrm; - sstrm << header.first; - sstrm << ": "; - sstrm << header.second; - handle->chunk = curl_slist_append(handle->chunk, sstrm.str().c_str()); - } - - CURL* curl_handle = handle->handle; + curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1L); - if (verbose_) { + if (stderr_ != nullptr) { curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L); - if (stderr_ != nullptr) { - curl_easy_setopt(curl_handle, CURLOPT_STDERR, stderr_); - } + curl_easy_setopt(curl_handle, CURLOPT_STDERR, stderr_); } else { curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 0L); } @@ -622,18 +782,15 @@ ErrorCode NetworkCurl::SendImplementation( const std::string& url = request.GetUrl(); curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str()); + auto verb = request.GetVerb(); - if (verb == NetworkRequest::HttpVerb::POST || - verb == NetworkRequest::HttpVerb::PUT || - verb == NetworkRequest::HttpVerb::PATCH) { - if (verb == NetworkRequest::HttpVerb::POST) { - curl_easy_setopt(curl_handle, CURLOPT_POST, 1L); - } else if (verb == NetworkRequest::HttpVerb::PUT) { - // http://stackoverflow.com/questions/7569826/send-string-in-put-request-with-libcurl - curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, "PUT"); - } else if (verb == NetworkRequest::HttpVerb::PATCH) { - curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, "PATCH"); - } + if (verb == NetworkRequest::HttpVerb::POST) { + curl_easy_setopt(curl_handle, CURLOPT_POST, 1L); + } else if (verb == NetworkRequest::HttpVerb::PUT) { + // http://stackoverflow.com/questions/7569826/send-string-in-put-request-with-libcurl + curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, "PUT"); + } else if (verb == NetworkRequest::HttpVerb::PATCH) { + curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, "PATCH"); } else if (verb == NetworkRequest::HttpVerb::DEL) { curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, "DELETE"); } else if (verb == NetworkRequest::HttpVerb::OPTIONS) { @@ -650,47 +807,15 @@ ErrorCode NetworkCurl::SendImplementation( verb != NetworkRequest::HttpVerb::HEAD) { // These can also be used to add body data to a CURLOPT_CUSTOMREQUEST // such as delete. - if (handle->body && !handle->body->empty()) { - curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, - handle->body->size()); - curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, &handle->body->front()); - } else { - // Some services (eg. Google) require the field size even if zero - curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, 0L); - } + SetupRequestBody(curl_handle, handle->request_body); } - const auto& proxy = config.GetProxySettings(); - if (proxy.GetType() != NetworkProxySettings::Type::NONE) { - curl_easy_setopt(curl_handle, CURLOPT_PROXY, proxy.GetHostname().c_str()); - curl_easy_setopt(curl_handle, CURLOPT_PROXYPORT, proxy.GetPort()); - const auto proxy_type = proxy.GetType(); - if (proxy_type != NetworkProxySettings::Type::HTTP) { - curl_easy_setopt(curl_handle, CURLOPT_PROXYTYPE, - ToCurlProxyType(proxy_type)); - } + SetupProxy(curl_handle, config.GetProxySettings()); + SetupDns(curl_handle, config.GetDNSServers()); - // We expect that both fields are empty or filled - if (!proxy.GetUsername().empty() && !proxy.GetPassword().empty()) { - curl_easy_setopt(curl_handle, CURLOPT_PROXYUSERNAME, - proxy.GetUsername().c_str()); - curl_easy_setopt(curl_handle, CURLOPT_PROXYPASSWORD, - proxy.GetPassword().c_str()); - } - } - -#if CURL_AT_LEAST_VERSION(7, 24, 0) - const auto& dns_servers = config.GetDNSServers(); - if (!dns_servers.empty()) { - const std::string& dns_list = dns_servers.size() == 1 - ? dns_servers.front() - : ConcatenateDnsAddresses(dns_servers); - curl_easy_setopt(curl_handle, CURLOPT_DNS_SERVERS, dns_list.c_str()); - } -#endif - - if (handle->chunk) { - curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, handle->chunk); + if (handle->request_headers) { + curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, + handle->request_headers.get()); } #ifdef OLP_SDK_CURL_HAS_SUPPORT_SSL_BLOBS @@ -810,72 +935,52 @@ void NetworkCurl::AddEvent(EventInfo::Type type, RequestHandle* handle) { #endif } -NetworkCurl::RequestHandle* NetworkCurl::GetHandle( - RequestId id, Network::Callback callback, - Network::HeaderCallback header_callback, - Network::DataCallback data_callback, Network::Payload payload, - NetworkRequest::RequestBodyType body) { - if (!IsStarted()) { - OLP_SDK_LOG_ERROR(kLogTag, - "GetHandle failed - network is offline, id=" << id); +NetworkCurl::RequestHandle* NetworkCurl::InitRequestHandle() { + std::lock_guard lock(event_mutex_); + + const auto unused_handle_it = + std::find_if(handles_.begin(), handles_.end(), + [](const RequestHandle& request_handle) { + return request_handle.in_use == false; + }); + + if (unused_handle_it == handles_.end()) { return nullptr; } - std::lock_guard lock(event_mutex_); - for (auto& handle : handles_) { - if (!handle.in_use) { - if (!handle.handle) { - handle.handle = curl_easy_init(); - if (!handle.handle) { - OLP_SDK_LOG_ERROR(kLogTag, - "GetHandle - curl_easy_init failed, id=" << id); - return nullptr; - } - curl_easy_setopt(handle.handle, CURLOPT_NOSIGNAL, 1L); - } - handle.in_use = true; - handle.callback = std::move(callback); - handle.header_callback = std::move(header_callback); - handle.data_callback = std::move(data_callback); - handle.id = id; - handle.count = 0u; - handle.offset = 0u; - handle.chunk = nullptr; - handle.cancelled = false; - handle.payload = std::move(payload); - handle.body = std::move(body); - handle.send_time = std::chrono::steady_clock::now(); - handle.error_text[0] = 0; - handle.skip_content = false; - handle.log_context = logging::GetContext(); - - return &handle; - } + + if (!unused_handle_it->curl_handle) { + unused_handle_it->curl_handle = {curl_easy_init(), curl_easy_cleanup}; } - OLP_SDK_LOG_DEBUG(kLogTag, - "GetHandle failed - all CURL handles are busy, id=" << id); - return nullptr; -} + if (!unused_handle_it->curl_handle) { + return nullptr; + } -void NetworkCurl::ReleaseHandle(RequestHandle* handle, - bool cleanup_easy_handle) { - std::lock_guard lock(event_mutex_); - ReleaseHandleUnlocked(handle, cleanup_easy_handle); + unused_handle_it->in_use = true; + unused_handle_it->self = shared_from_this(); + unused_handle_it->send_time = std::chrono::steady_clock::now(); + unused_handle_it->log_context = logging::GetContext(); + + return &*unused_handle_it; } void NetworkCurl::ReleaseHandleUnlocked(RequestHandle* handle, bool cleanup_easy_handle) { - curl_easy_reset(handle->handle); - if (handle->chunk) { - curl_slist_free_all(handle->chunk); - handle->chunk = nullptr; - } - handle->in_use = false; - handle->callback = nullptr; - handle->header_callback = nullptr; - handle->data_callback = nullptr; - handle->payload.reset(); - handle->body.reset(); + // Reset the RequestHandle to defaults, but keep the curl_handle and + // response_headers allocated buffer. + std::shared_ptr curl_handle; + std::vector> response_headers; + + std::swap(curl_handle, handle->curl_handle); + std::swap(response_headers, handle->response_headers); + + response_headers.clear(); + curl_easy_reset(curl_handle.get()); + + *handle = RequestHandle{}; + + std::swap(curl_handle, handle->curl_handle); + std::swap(response_headers, handle->response_headers); // When using C-Ares on Android, DNS parameters are calculated in // curl_easy_init(). Those parameters are not reset in curl_easy_reset(...), @@ -888,8 +993,7 @@ void NetworkCurl::ReleaseHandleUnlocked(RequestHandle* handle, #if defined(ANDROID) if (cleanup_easy_handle) { - curl_easy_cleanup(handle->handle); - handle->handle = nullptr; + handle->curl_handle = nullptr; } #endif OLP_SDK_CORE_UNUSED(cleanup_easy_handle); @@ -906,48 +1010,46 @@ size_t NetworkCurl::RxFunction(void* ptr, size_t size, size_t nmemb, if (!that) { return len; } - long status = 0L; - curl_easy_getinfo(handle->handle, CURLINFO_RESPONSE_CODE, &status); - if (handle->skip_content && status != http::HttpStatusCode::OK && - status != http::HttpStatusCode::PARTIAL_CONTENT && - status != http::HttpStatusCode::CREATED && status != 0L) { - return len; - } if (that->IsStarted() && !handle->cancelled) { - if (handle->data_callback) { - handle->data_callback(reinterpret_cast(ptr), - handle->offset + handle->count, len); + if (handle->out_data_callback) { + handle->out_data_callback(static_cast(ptr), + handle->bytes_received, len); } - if (handle->payload) { - if (!handle->ignore_offset) { - if (handle->payload->tellp() != std::streampos(handle->count)) { - handle->payload->seekp(handle->count); - if (handle->payload->fail()) { - OLP_SDK_LOG_WARNING(kLogTag, - "Payload seekp() failed, id=" << handle->id); - handle->payload->clear(); - } + + const auto& stream = handle->out_data_stream; + if (stream) { + if (stream->tellp() != + static_cast(handle->bytes_received)) { + stream->seekp(static_cast(handle->bytes_received)); + if (stream->fail()) { + OLP_SDK_LOG_WARNING(kLogTag, + "Payload seekp() failed, id=" << handle->id); + stream->clear(); } } - const char* data = reinterpret_cast(ptr); - handle->payload->write(data, len); + stream->write(static_cast(ptr), + static_cast(len)); } - handle->count += len; + + handle->bytes_received += len; } - // In case we have curl verbose and stderr enabled log the error content + // In case we have curl verbose and stderr enabled to log the error content if (that->stderr_) { long http_status = 0L; - curl_easy_getinfo(handle->handle, CURLINFO_RESPONSE_CODE, &http_status); + curl_easy_getinfo(handle->curl_handle.get(), CURLINFO_RESPONSE_CODE, + &http_status); if (http_status >= http::HttpStatusCode::BAD_REQUEST) { // Log the error content to help troubleshooting - fprintf(that->stderr_, "\n---ERRORCONTENT BEGIN HANDLE=%p BLOCKSIZE=%u\n", - handle, (uint32_t)(size * nmemb)); + fprintf(that->stderr_, + "\n---ERROR CONTENT BEGIN HANDLE: %p, BLOCK SIZE: %u\n", handle, + static_cast(size * nmemb)); fwrite(ptr, size, nmemb, that->stderr_); - fprintf(that->stderr_, "\n---ERRORCONTENT END HANDLE=%p BLOCKSIZE=%u\n", - handle, (uint32_t)(size * nmemb)); + fprintf(that->stderr_, + "\n---ERROR CONTENT END HANDLE: %p, BLOCK SIZE: %u\n", handle, + static_cast(size * nmemb)); } } @@ -963,7 +1065,7 @@ size_t NetworkCurl::HeaderFunction(char* ptr, size_t size, size_t nitems, return len; } - if (!handle->header_callback) { + if (!handle->out_header_callback) { return len; } @@ -989,12 +1091,13 @@ size_t NetworkCurl::HeaderFunction(char* ptr, size_t size, size_t nitems, } // Callback with header key+value - handle->header_callback(key, value); + handle->out_header_callback(key, value); + handle->response_headers.emplace_back(std::move(key), std::move(value)); return len; } -void NetworkCurl::CompleteMessage(CURL* handle, CURLcode result) { +void NetworkCurl::CompleteMessage(CURL* curl_handle, CURLcode result) { std::unique_lock lock(event_mutex_); // When curl returns an error of the handle, it is possible that error @@ -1004,96 +1107,126 @@ void NetworkCurl::CompleteMessage(CURL* handle, CURLcode result) { // handle. const bool cleanup_easy_handle = result != CURLE_OK; - int index = GetHandleIndex(handle); - if (index >= 0 && index < static_cast(handles_.size())) { - RequestHandle& rhandle = handles_[index]; - logging::ScopedLogContext scopedLogContext(rhandle.log_context); - auto callback = rhandle.callback; + RequestHandle* request_handle = FindRequestHandle(curl_handle); + if (!request_handle) { + OLP_SDK_LOG_WARNING(kLogTag, "Message completed to unknown request"); + return; + } - if (!callback) { - OLP_SDK_LOG_WARNING( - kLogTag, - "CompleteMessage - message without callback, id=" << rhandle.id); - ReleaseHandleUnlocked(&rhandle, cleanup_easy_handle); - return; - } + logging::ScopedLogContext scopedLogContext(request_handle->log_context); + auto callback = std::move(request_handle->out_completion_callback); - uint64_t upload_bytes = 0u; - uint64_t download_bytes = 0u; - GetTrafficData(rhandle.handle, upload_bytes, download_bytes); + if (!callback) { + OLP_SDK_LOG_WARNING(kLogTag, + "CompleteMessage - message without callback, id=" + << request_handle->id); + return ReleaseHandleUnlocked(request_handle, cleanup_easy_handle); + } - auto response = NetworkResponse() - .WithRequestId(rhandle.id) - .WithBytesDownloaded(download_bytes) - .WithBytesUploaded(upload_bytes); + uint64_t upload_bytes = 0u; + uint64_t download_headers_bytes = 0u; + uint64_t download_body_bytes = 0u; + GetTrafficData(curl_handle, upload_bytes, download_headers_bytes, + download_body_bytes); - if (rhandle.cancelled) { - response.WithStatus(static_cast(ErrorCode::CANCELLED_ERROR)) - .WithError("Cancelled"); - ReleaseHandleUnlocked(&rhandle, cleanup_easy_handle); + auto response = + NetworkResponse() + .WithRequestId(request_handle->id) + .WithBytesDownloaded(download_headers_bytes + download_body_bytes) + .WithBytesUploaded(upload_bytes); - lock.unlock(); - callback(response); - return; - } + if (request_handle->cancelled) { + response.WithStatus(static_cast(ErrorCode::CANCELLED_ERROR)) + .WithError("Cancelled"); + ReleaseHandleUnlocked(request_handle, cleanup_easy_handle); - std::string error("Success"); - int status; - if ((result == CURLE_OK) || (result == CURLE_HTTP_RETURNED_ERROR)) { - long http_status = 0L; - curl_easy_getinfo(rhandle.handle, CURLINFO_RESPONSE_CODE, &http_status); - status = static_cast(http_status); + lock.unlock(); + callback(response); + return; + } - if ((rhandle.offset == 0u) && - (status == HttpStatusCode::PARTIAL_CONTENT)) { - status = HttpStatusCode::OK; - } + std::string error("Success"); + int status; + if ((result == CURLE_OK) || (result == CURLE_HTTP_RETURNED_ERROR)) { + long http_status = 0L; + curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &http_status); + status = static_cast(http_status); - // For local file there is no server response so status is 0 - if ((status == 0) && (result == CURLE_OK)) { - status = HttpStatusCode::OK; - } + if (status == HttpStatusCode::PARTIAL_CONTENT) { + status = HttpStatusCode::OK; + } - error = HttpErrorToString(status); - } else { - rhandle.error_text[CURL_ERROR_SIZE - 1u] = '\0'; - if (std::strlen(rhandle.error_text) > 0u) { - error = rhandle.error_text; - } else { - error = curl_easy_strerror(result); - } + // For local file, there is no server response so status is 0 + if ((status == 0) && (result == CURLE_OK)) { + status = HttpStatusCode::OK; + } - status = ConvertErrorCode(result); + error = HttpErrorToString(status); + } else { + request_handle->error_text[CURL_ERROR_SIZE - 1u] = '\0'; + if (std::strlen(request_handle->error_text) > 0u) { + error = request_handle->error_text; + } else { + error = curl_easy_strerror(result); } - const char* url; - curl_easy_getinfo(rhandle.handle, CURLINFO_EFFECTIVE_URL, &url); + status = ConvertErrorCode(result); + } - OLP_SDK_LOG_DEBUG(kLogTag, - "Message completed, id=" - << rhandle.id << ", url='" - << utils::CensorCredentialsInUrl(url) << "', status=(" - << status << ") " << error - << ", time=" << GetElapsedTime(rhandle.send_time) - << "ms, bytes=" << download_bytes + upload_bytes); + const char* url; + curl_easy_getinfo(curl_handle, CURLINFO_EFFECTIVE_URL, &url); - response.WithStatus(status).WithError(error); - ReleaseHandleUnlocked(&rhandle, cleanup_easy_handle); + if (session_recording_) { + long http_version = 0; + curl_easy_getinfo(curl_handle, CURLINFO_HTTP_VERSION, &http_version); - lock.unlock(); - callback(response); - } else { - OLP_SDK_LOG_WARNING(kLogTag, "Message completed to unknown request"); + if (http_version == CURL_HTTP_VERSION_1_0 || + http_version == CURL_HTTP_VERSION_1_1) { + http_version = 1; + } else if (http_version == CURL_HTTP_VERSION_2_0 || + http_version == CURL_HTTP_VERSION_2TLS || + http_version == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) { + http_version = 2; + } else { + http_version = 0; + } + + const char* content_type; + curl_easy_getinfo(curl_handle, CURLINFO_CONTENT_TYPE, &content_type); + + session_recording_->locked([&](SessionRecording& recording) { + recording.SetResponseParameters( + request_handle->id, static_cast(http_version), status, + request_handle->response_headers, content_type, + download_headers_bytes, download_body_bytes); + recording.SetRequestTimings(request_handle->id, GetTimings(curl_handle)); + }); } + + OLP_SDK_LOG_DEBUG( + kLogTag, + "Message completed, id=" + << request_handle->id << ", url='" + << utils::CensorCredentialsInUrl(url) << "', status=(" << status + << ") " << error << ", time=" + << GetElapsedTime(request_handle->send_time) << "ms, bytes=" + << download_headers_bytes + download_body_bytes + upload_bytes); + + response.WithStatus(status).WithError(error); + + ReleaseHandleUnlocked(request_handle, cleanup_easy_handle); + + lock.unlock(); + callback(response); } -int NetworkCurl::GetHandleIndex(CURL* handle) { - for (size_t index = 0u; index < handles_.size(); index++) { - if (handles_[index].in_use && (handles_[index].handle == handle)) { - return static_cast(index); +NetworkCurl::RequestHandle* NetworkCurl::FindRequestHandle(const CURL* handle) { + for (auto& request_handle : handles_) { + if (request_handle.in_use && request_handle.curl_handle.get() == handle) { + return &request_handle; } } - return -1; + return nullptr; } void NetworkCurl::Run() { @@ -1117,36 +1250,38 @@ void NetworkCurl::Run() { events_.pop_front(); // Only handle handles that are actually used - auto* rhandle = event.handle; - if (!rhandle->in_use) { + auto* request_handle = event.handle; + if (!request_handle->in_use) { continue; } + const auto curl_handle = request_handle->curl_handle.get(); + if (event.type == EventInfo::Type::SEND_EVENT) { - auto res = curl_multi_add_handle(curl_, rhandle->handle); + auto res = curl_multi_add_handle(curl_, curl_handle); if ((res != CURLM_OK) && (res != CURLM_CALL_MULTI_PERFORM)) { - OLP_SDK_LOG_ERROR(kLogTag, - "Send failed, id=" << rhandle->id << ", error=" - << curl_multi_strerror(res)); + OLP_SDK_LOG_ERROR( + kLogTag, "Send failed, id=" << request_handle->id << ", error=" + << curl_multi_strerror(res)); // Do not add the handle to msgs vector in case it is a duplicate // handle error as it will be reset in CompleteMessage handler, // and curl will crash in the next call of curl_multi_perform // function. In any other case, lets complete the message. if (res != CURLM_ADDED_ALREADY) { - msgs.push_back(rhandle->handle); + msgs.push_back(curl_handle); } } } else { - // Request was cancelled, so lets remove it from curl - auto code = curl_multi_remove_handle(curl_, rhandle->handle); + // The Request was canceled, so let's remove it from a multi handle + auto code = curl_multi_remove_handle(curl_, curl_handle); if (code != CURLM_OK) { OLP_SDK_LOG_ERROR(kLogTag, "curl_multi_remove_handle failed, error=" << curl_multi_strerror(code)); } lock.unlock(); - CompleteMessage(rhandle->handle, CURLE_OPERATION_TIMEDOUT); + CompleteMessage(curl_handle, CURLE_OPERATION_TIMEDOUT); lock.lock(); } } @@ -1182,52 +1317,59 @@ void NetworkCurl::Run() { std::unique_lock lock(event_mutex_); while (IsStarted() && - (msg = curl_multi_info_read(curl_, &msgs_in_queue))) { - CURL* handle = msg->easy_handle; - uint64_t upload_bytes = 0u; - uint64_t download_bytes = 0u; - GetTrafficData(handle, upload_bytes, download_bytes); + ((msg = curl_multi_info_read(curl_, &msgs_in_queue)))) { + CURL* curl_handle = msg->easy_handle; if (msg->msg == CURLMSG_DONE) { - curl_multi_remove_handle(curl_, handle); + curl_multi_remove_handle(curl_, curl_handle); lock.unlock(); - CompleteMessage(handle, msg->data.result); + CompleteMessage(curl_handle, msg->data.result); lock.lock(); } else { - // Actually this part should never be executed + // Actually, this part should never be executed OLP_SDK_LOG_ERROR( kLogTag, "Request completed with unknown state, error=" << msg->msg); - int handle_index = GetHandleIndex(handle); - if (handle_index >= 0) { - RequestHandle& rhandle = handles_[handle_index]; - logging::ScopedLogContext scopedLogContext(rhandle.log_context); - - auto callback = rhandle.callback; - if (!callback) { - OLP_SDK_LOG_WARNING( - kLogTag, - "Request completed without callback, id=" << rhandle.id); - } else { - lock.unlock(); - auto response = - NetworkResponse() - .WithRequestId(rhandle.id) - .WithStatus(static_cast(ErrorCode::IO_ERROR)) - .WithError("CURL error") - .WithBytesDownloaded(download_bytes) - .WithBytesUploaded(upload_bytes); - callback(response); - lock.lock(); - } + auto* request_handle = FindRequestHandle(curl_handle); + if (!request_handle) { + OLP_SDK_LOG_ERROR(kLogTag, "Unknown handle completed"); + continue; + } - curl_multi_remove_handle(curl_, rhandle.handle); - const bool cleanup_easy_handle = true; - ReleaseHandleUnlocked(&rhandle, cleanup_easy_handle); + logging::ScopedLogContext scopedLogContext( + request_handle->log_context); + + auto callback = std::move(request_handle->out_completion_callback); + + if (!callback) { + OLP_SDK_LOG_WARNING(kLogTag, + "Request completed without callback, id=" + << request_handle->id); } else { - OLP_SDK_LOG_ERROR(kLogTag, "Unknown handle completed"); + lock.unlock(); + + uint64_t upload_bytes = 0u; + uint64_t download_headers_bytes = 0u; + uint64_t download_body_bytes = 0u; + GetTrafficData(curl_handle, upload_bytes, download_headers_bytes, + download_body_bytes); + + auto response = + NetworkResponse() + .WithRequestId(request_handle->id) + .WithStatus(static_cast(ErrorCode::IO_ERROR)) + .WithError("CURL error") + .WithBytesDownloaded(download_headers_bytes + + download_body_bytes) + .WithBytesUploaded(upload_bytes); + callback(response); + lock.lock(); } + + curl_multi_remove_handle(curl_, curl_handle); + constexpr bool cleanup_easy_handle = true; + ReleaseHandleUnlocked(request_handle, cleanup_easy_handle); } } } @@ -1237,7 +1379,7 @@ void NetworkCurl::Run() { } // - // Wait for next action or upload/download + // Wait for the next action or upload/download // { // NOTE: curl_multi_wait has a fatal flow in it and it was corrected by diff --git a/olp-cpp-sdk-core/src/http/curl/NetworkCurl.h b/olp-cpp-sdk-core/src/http/curl/NetworkCurl.h index 96240d089..4fcf676a2 100644 --- a/olp-cpp-sdk-core/src/http/curl/NetworkCurl.h +++ b/olp-cpp-sdk-core/src/http/curl/NetworkCurl.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2024 HERE Europe B.V. + * Copyright (C) 2019-2025 HERE Europe B.V. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,10 +27,13 @@ #include #include #include +#include #include #include +#include #include +#include #include #if defined(OLP_SDK_ENABLE_ANDROID_CURL) && !defined(ANDROID_HOST) @@ -59,13 +62,331 @@ #include "olp/core/http/NetworkRequest.h" #include "olp/core/logging/LogContext.h" +#include +#include +#include +#include +#include +#include + namespace olp { namespace http { +class SessionRecording { + public: + static constexpr RequestId max_value = 100000; + + // Chrono microseconds type but with a lower range to save some memory. + using MicroSeconds = std::chrono::duration; + + struct Timings { + MicroSeconds queue_time; + MicroSeconds name_lookup_time; + MicroSeconds connect_time; + MicroSeconds app_connect_time; + MicroSeconds pre_transfer_time; + MicroSeconds post_transfer_time; + MicroSeconds start_transfer_time; + MicroSeconds total_time; + }; + + void SetRequestParameters( + const RequestId request_id, const std::string& url, + const NetworkRequest::HttpVerb method, + const std::vector>& headers, + const size_t body_size) { + if (request_id >= max_value) { + return; + } + + const auto url_hash = hash_(url); + cache_[url_hash] = url; + + auto* entry = GetEntry(request_id); + + entry->url = url_hash; + entry->method = static_cast(method); + + entry->request_headers_offset = headers_.size(); + entry->request_headers_count = headers.size(); + + for (const auto& header : headers) { + cache_[hash_(header.first)] = header.first; + cache_[hash_(header.second)] = header.second; + headers_.emplace_back(hash_(header.first), hash_(header.second)); + entry->request_headers_size += + header.first.size() + header.second.size() + 4; + } + + entry->request_body_size = body_size; + + entry->start_time = std::chrono::system_clock::now(); + } + + void SetResponseParameters( + const RequestId request_id, const int http_version, const int status_code, + const std::vector>& headers, + const std::string& content_type, const unsigned long headers_size, + const unsigned long body_size) { + if (request_id >= max_value) { + return; + } + + auto* entry = GetEntry(request_id); + + entry->status_code = static_cast(status_code); + entry->http_version = static_cast(http_version); + + entry->response_headers_offset = headers_.size(); + entry->response_headers_count = headers.size(); + + for (const auto& header : headers) { + cache_[hash_(header.first)] = header.first; + cache_[hash_(header.second)] = header.second; + headers_.emplace_back(hash_(header.first), hash_(header.second)); + } + + entry->response_headers_size = static_cast(headers_size); + entry->response_body_size = static_cast(body_size); + + entry->duration = std::chrono::duration_cast( + std::chrono::system_clock::now() - entry->start_time); + + cache_[hash_(content_type)] = content_type; + entry->content_type = hash_(content_type); + } + + void SetRequestTimings(const RequestId request_id, const Timings& timings) { + if (request_id > timings_.size()) { + timings_.resize(request_id); + } + + timings_[request_id - 1] = timings; + } + + void ArchiveToFile(const std::string& out_file_path) const { + std::ofstream file(out_file_path); + + rapidjson::OStreamWrapper os(file); + rapidjson::PrettyWriter writer(os); + writer.StartObject(); + writer.Key("log"); + writer.StartObject(); + + // Version + writer.Key("version"); + writer.String("1.2"); + + // Creator + writer.Key("creator"); + writer.StartObject(); + writer.Key("name"); + writer.String("DataSDK"); + writer.Key("version"); + writer.String("1.0"); + writer.EndObject(); + + // Entries + writer.Key("entries"); + writer.StartArray(); + for (auto request_index = 0u; request_index < requests_.size(); + ++request_index) { + const auto& request = requests_[request_index]; + writer.StartObject(); + // startedDateTime + writer.Key("startedDateTime"); + + std::stringstream ss; + std::time_t t = std::chrono::system_clock::to_time_t(request.start_time); + auto ms = std::chrono::duration_cast( + request.start_time.time_since_epoch()) % + 1000; + ss << std::put_time(std::localtime(&t), "%FT%T") << '.' + << std::setfill('0') << std::setw(3) << ms.count() << "Z"; + + writer.String(ss.str().c_str()); + + // time + writer.Key("time"); + writer.Int(request.duration.count()); + // writer.Int(timings_[request_index].total_time.count() / 1000); + + // request + writer.Key("request"); + writer.StartObject(); + { + // method + writer.Key("method"); + writer.String("GET"); + // url + writer.Key("url"); + writer.String(cache_.at(request.url).c_str()); + // httpVersion + writer.Key("httpVersion"); + writer.String("HTTP/1.1"); + // cookies [] + writer.Key("cookies"); + writer.StartArray(); + writer.EndArray(); + // headers [] + writer.Key("headers"); + writer.StartArray(); + for (auto i = 0u; i < request.request_headers_count; ++i) { + auto header = headers_[request.request_headers_offset + i]; + writer.StartObject(); + writer.Key("name"); + writer.Key(cache_.at(header.first).c_str()); + writer.Key("value"); + writer.Key(cache_.at(header.second).c_str()); + writer.EndObject(); + } + writer.EndArray(); + // queryString [] + writer.Key("queryString"); + writer.StartArray(); + writer.EndArray(); + // headersSize int + writer.Key("headersSize"); + writer.Int(request.request_headers_size); + // bodySize int + writer.Key("bodySize"); + writer.Int(request.request_body_size); + } + writer.EndObject(); + + // response + writer.Key("response"); + writer.StartObject(); + { + writer.Key("status"); + writer.Int(request.status_code); + + writer.Key("httpVersion"); + if (request.http_version == 1) { + writer.String("HTTP/1.1"); + } else if (request.http_version == 2) { + writer.String("HTTP/2"); + } else { + writer.String("UNKNOWN"); + } + + writer.Key("cookies"); + writer.StartArray(); + writer.EndArray(); + + writer.Key("headers"); + writer.StartArray(); + for (auto i = 0u; i < request.response_headers_count; ++i) { + auto header = headers_[request.response_headers_offset + i]; + writer.StartObject(); + writer.Key("name"); + writer.String(cache_.at(header.first).c_str()); + writer.Key("value"); + writer.String(cache_.at(header.second).c_str()); + writer.EndObject(); + } + writer.EndArray(); + + writer.Key("content"); + writer.StartObject(); + writer.Key("size"); + writer.Int(request.response_body_size); + writer.Key("mimeType"); + writer.String(cache_.at(request.content_type).c_str()); + writer.EndObject(); + + writer.Key("redirectURL"); + writer.String(""); + + writer.Key("headersSize"); + writer.Int(request.response_headers_size); + + writer.Key("bodySize"); + writer.Int(request.response_body_size); + } + writer.EndObject(); + + // timings + writer.Key("timings"); + writer.StartObject(); + if (!timings_.empty()) { + const auto& timings = timings_[request_index]; + writer.Key("blocked"); + writer.Double(timings.queue_time.count() / 1000.0); + writer.Key("dns"); + writer.Double((timings.name_lookup_time - timings.queue_time).count() / + 1000.0); + writer.Key("connect"); + writer.Double( + (timings.connect_time - timings.name_lookup_time).count() / 1000.0); + writer.Key("ssl"); + writer.Double( + (timings.app_connect_time - timings.connect_time).count() / 1000.0); + writer.Key("send"); + writer.Double( + (timings.pre_transfer_time - timings.app_connect_time).count() / + 1000.0); + writer.Key("wait"); + writer.Double( + (timings.start_transfer_time - timings.pre_transfer_time).count() / + 1000.0); + writer.Key("receive"); + writer.Double( + (timings.total_time - timings.start_transfer_time).count() / + 1000.0); + } + writer.EndObject(); + + writer.EndObject(); + } + writer.EndArray(); + + writer.EndObject(); + writer.EndObject(); + } + + private: + struct RequestEntry { + size_t url{}; + size_t content_type{}; + + std::chrono::time_point start_time; + std::chrono::milliseconds duration{}; + + uint16_t request_headers_offset{}; + uint16_t request_headers_size{}; + uint16_t response_headers_offset{}; + uint16_t response_headers_size{}; + uint8_t request_headers_count{}; + uint8_t response_headers_count{}; + uint32_t request_body_size{}; + uint32_t response_body_size{}; + + uint8_t method{}; + uint8_t http_version{}; + uint8_t status_code{}; + }; + + RequestEntry* GetEntry(const RequestId request_id) { + if (request_id > requests_.size()) { + requests_.resize(request_id); + } + + return &requests_[request_id - 1]; + } + + std::string out_file_path_; + std::unordered_map cache_{}; + std::vector requests_{}; + std::vector timings_{}; + std::vector> headers_{}; + std::hash hash_; +}; + /** * @brief The implementation of Network based on cURL. */ -class NetworkCurl : public olp::http::Network, +class NetworkCurl : public Network, public std::enable_shared_from_this { public: /** @@ -112,26 +433,29 @@ class NetworkCurl : public olp::http::Network, private: /** - * @brief Context of each individual network request. + * @brief Context of each network request. */ struct RequestHandle { + std::string request_url; + std::string request_method; + NetworkRequest::RequestBodyType request_body; + std::shared_ptr request_headers; + + HeaderCallback out_header_callback; + DataCallback out_data_callback; + Payload out_data_stream; + Callback out_completion_callback; + std::uint64_t bytes_received{0}; + + std::vector> response_headers; + std::chrono::steady_clock::time_point send_time{}; - NetworkRequest::RequestBodyType body{}; - Network::Payload payload{}; std::weak_ptr self{}; - Callback callback{}; - HeaderCallback header_callback{}; - DataCallback data_callback{}; - std::uint64_t count{}; - std::uint64_t offset{}; - CURL* handle{nullptr}; - struct curl_slist* chunk{nullptr}; - int index{}; + + std::shared_ptr curl_handle; RequestId id{}; - bool ignore_offset{}; - bool in_use{}; - bool cancelled{}; - bool skip_content{}; + bool in_use{false}; + bool cancelled{false}; char error_text[CURL_ERROR_SIZE]{}; std::shared_ptr log_context; }; @@ -186,7 +510,7 @@ class NetworkCurl : public olp::http::Network, * @param[out] payload Stream to store response payload data. * @param[in] header_callback Callback that is called for every header from * response. - * @param[in] data-callback Callback to be called when a chunk of data is + * @param[in] data_callback Callback to be called when a chunk of data is * received. This callback can be triggered multiple times all prior to the * final Callback call. * @param[in] callback Callback to be called when request is fully processed @@ -196,9 +520,8 @@ class NetworkCurl : public olp::http::Network, */ ErrorCode SendImplementation(const NetworkRequest& request, RequestId id, const std::shared_ptr& payload, - Network::HeaderCallback header_callback, - Network::DataCallback data_callback, - Network::Callback callback); + HeaderCallback header_callback, + DataCallback data_callback, Callback callback); /** * @brief Initialize internal data structures, start worker thread. @@ -232,54 +555,35 @@ class NetworkCurl : public olp::http::Network, size_t AmountPending(); /** - * @brief Find handle index in handles_ by handle value. + * @brief Find a handle in handles_ by curl handle. * @param[in] handle CURL handle. - * @return index of associated RequestHandle in handles_ array. + * @return Pointer to the allocated RequestHandle. */ - int GetHandleIndex(CURL* handle); + RequestHandle* FindRequestHandle(const CURL* handle); /** * @brief Allocate new handle RequestHandle. - * @param[in] id Unique request id. - * @param[in] callback Request's callback. - * @param[in] header_callback Request's header callback. - * @param[in] data_callback Request's data callback. - * @param[in] payload Stream for response body. - * @return Pointer to allocated RequestHandle. - */ - RequestHandle* GetHandle(RequestId id, Network::Callback callback, - Network::HeaderCallback headerCallback, - Network::DataCallback dataCallback, - Network::Payload payload, - NetworkRequest::RequestBodyType body); - - /** - * @brief Reset handle after network request is done. - * This method handles synchronization between caller's thread and worker - * thread. - * @param[in] handle Request handle. - * @param[in] cleanup_handle If true then handle is completelly release. - * Otherwise handle is reset, which preserves DNS cache, Session ID cache, - * cookies and so on. + * + * @return Pointer to the allocated RequestHandle. */ - void ReleaseHandle(RequestHandle* handle, bool cleanup_handle); + RequestHandle* InitRequestHandle(); /** - * @brief Reset handle after network request is done. + * @brief Reset the handle after network request is done. * @param[in] handle Request handle. - * @param[in] cleanup_handle If true then handle is completelly release. - * Otherwise handle is reset, which preserves DNS cache, Session ID cache, - * cookies and so on. + * @param[in] cleanup_handle If true, then a handle is completely released. + * Otherwise, a handle is reset, which preserves DNS cache, Session ID cache, + * cookies, and so on. */ - void ReleaseHandleUnlocked(RequestHandle* handle, bool cleanup_handle); + static void ReleaseHandleUnlocked(RequestHandle* handle, bool cleanup_handle); /** * @brief Routine that is called when the last bit of response is received. * - * @param[in] handle CURL handle associated with request. + * @param[in] curl_handle CURL handle associated with request. * @param[in] result CURL return code. */ - void CompleteMessage(CURL* handle, CURLcode result); + void CompleteMessage(CURL* curl_handle, CURLcode result); /** * @brief CURL read callback. @@ -376,9 +680,6 @@ class NetworkCurl : public olp::http::Network, /// CURL multi handle. Shared among all network requests. CURLM* curl_{nullptr}; - /// Turn on and off verbose mode for CURL. - bool verbose_{false}; - /// Set custom stderr for CURL. FILE* stderr_{nullptr}; @@ -392,6 +693,11 @@ class NetworkCurl : public olp::http::Network, /// blobs so cURL does not need to copy them. CertificateSettings certificate_settings_; + /// The path to store CURL logs + boost::optional curl_log_path_; + + boost::optional> session_recording_; + #ifdef OLP_SDK_CURL_HAS_SUPPORT_SSL_BLOBS /// SSL certificate blobs. boost::optional ssl_certificates_blobs_;