From 6ffa791a84579a44bc2432240f15723cf66dd023 Mon Sep 17 00:00:00 2001 From: Mykhailo Kuchma Date: Thu, 6 Feb 2025 17:47:47 +0100 Subject: [PATCH] Minor refactoring in NetworkCurl Use RAII for curl easy handles Change the RequestHandle initialization sequence Relates-To: OCMAM-418 Signed-off-by: Mykhailo Kuchma --- .../src/http/curl/NetworkCurl.cpp | 601 +++++++++--------- olp-cpp-sdk-core/src/http/curl/NetworkCurl.h | 98 ++- 2 files changed, 329 insertions(+), 370 deletions(-) diff --git a/olp-cpp-sdk-core/src/http/curl/NetworkCurl.cpp b/olp-cpp-sdk-core/src/http/curl/NetworkCurl.cpp index 40fbbaf63..f809c73a6 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 @@ -118,9 +118,8 @@ const auto kCurlCaBundleName = "ca-bundle.crt"; std::string DefaultCaBundlePath() { return kCurlCaBundleName; } std::string CaBundlePath() { - std::string bundle_path; - bundle_path = DefaultCaBundlePath(); - if (!olp::utils::Dir::FileExists(bundle_path)) { + std::string bundle_path = DefaultCaBundlePath(); + if (!utils::Dir::FileExists(bundle_path)) { bundle_path.clear(); } return bundle_path; @@ -142,7 +141,7 @@ int BlockSigpipe() { return err; } - err = pthread_sigmask(SIG_BLOCK, &sigset, NULL); + err = pthread_sigmask(SIG_BLOCK, &sigset, nullptr); return err; } @@ -180,27 +179,29 @@ curl_proxytype ToCurlProxyType(olp::http::NetworkProxySettings::Type type) { int ConvertErrorCode(CURLcode curl_code) { if (curl_code == CURLE_OK) { return 0; - } else if ((curl_code == CURLE_REMOTE_ACCESS_DENIED) || - (curl_code == CURLE_SSL_CERTPROBLEM) || - (curl_code == CURLE_SSL_CIPHER) || - (curl_code == CURLE_LOGIN_DENIED)) { + } + if (curl_code == CURLE_REMOTE_ACCESS_DENIED || + curl_code == CURLE_SSL_CERTPROBLEM || curl_code == CURLE_SSL_CIPHER || + curl_code == CURLE_LOGIN_DENIED) { return static_cast(ErrorCode::AUTHORIZATION_ERROR); - } else if (curl_code == CURLE_SSL_CACERT) { - return static_cast(ErrorCode::AUTHENTICATION_ERROR); - } else if ((curl_code == CURLE_UNSUPPORTED_PROTOCOL) || - (curl_code == CURLE_URL_MALFORMAT)) { - return static_cast(ErrorCode::INVALID_URL_ERROR); + } #if CURL_AT_LEAST_VERSION(7, 24, 0) - } else if (curl_code == CURLE_FTP_ACCEPT_FAILED) { + if (curl_code == CURLE_FTP_ACCEPT_FAILED) { return static_cast(ErrorCode::AUTHORIZATION_ERROR); + } #endif - } else if (curl_code == CURLE_COULDNT_RESOLVE_HOST) { + if (curl_code == CURLE_SSL_CACERT) { + return static_cast(ErrorCode::AUTHENTICATION_ERROR); + } + if (curl_code == CURLE_UNSUPPORTED_PROTOCOL || + curl_code == CURLE_URL_MALFORMAT || + curl_code == CURLE_COULDNT_RESOLVE_HOST) { return static_cast(ErrorCode::INVALID_URL_ERROR); - } else if (curl_code == CURLE_OPERATION_TIMEDOUT) { + } + if (curl_code == CURLE_OPERATION_TIMEDOUT) { return static_cast(ErrorCode::TIMEOUT_ERROR); - } else { - return static_cast(ErrorCode::IO_ERROR); } + return static_cast(ErrorCode::IO_ERROR); } /** @@ -290,6 +291,63 @@ 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, + const 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 +} + } // anonymous namespace NetworkCurl::NetworkCurl(NetworkInitializationSettings settings) @@ -416,14 +474,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 +522,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 + // handle 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 +584,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=" @@ -545,8 +594,7 @@ SendOutcome NetworkCurl::Send(NetworkRequest request, } } - RequestId request_id{ - static_cast(RequestIdConstants::RequestIdMin)}; + RequestId request_id{}; { std::lock_guard lock(event_mutex_); @@ -574,8 +622,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,30 +632,29 @@ 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; } + handle->id = id; + + // 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->request_body = request.GetBody(); + handle->request_headers = SetupHeaders(request.GetHeaders()); + 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(); - - 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->curl_handle.get(); - CURL* curl_handle = handle->handle; + curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1L); if (verbose_) { curl_easy_setopt(curl_handle, CURLOPT_VERBOSE, 1L); @@ -622,18 +669,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 +694,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 @@ -727,7 +739,7 @@ ErrorCode NetworkCurl::SendImplementation( // `::count` is defined on all duration types, to be on the safe side // regarding durations on NetworkConfig. Refactoring of NetworkConfig to // return different types will be handled gracefully here. Force specific type - // of the representation & type expected by curl. + // of the representation and type expected by curl. const long connect_timeout_ms = CountIn(config.GetConnectionTimeoutDuration()); const long timeout_ms = @@ -782,7 +794,7 @@ void NetworkCurl::Cancel(RequestId id) { std::lock_guard lock(event_mutex_); for (auto& handle : handles_) { if (handle.in_use && (handle.id == id)) { - handle.cancelled = true; + handle.is_cancelled = true; AddEvent(EventInfo::Type::CANCEL_EVENT, &handle); OLP_SDK_LOG_DEBUG(kLogTag, "Cancel request with id=" << id); @@ -810,72 +822,43 @@ 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 default, but keep the curl_handle. + std::shared_ptr curl_handle; + std::swap(curl_handle, handle->curl_handle); + curl_easy_reset(curl_handle.get()); + *handle = RequestHandle{}; + std::swap(curl_handle, handle->curl_handle); // 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 +871,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 +888,43 @@ 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 (that->IsStarted() && !handle->is_cancelled) { + 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)); + 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)); + handle, static_cast(size * nmemb)); } } @@ -959,11 +936,11 @@ size_t NetworkCurl::HeaderFunction(char* ptr, size_t size, size_t nitems, const size_t len = size * nitems; std::shared_ptr that = handle->self.lock(); - if (!that || !that->IsStarted() || handle->cancelled) { + if (!that || !that->IsStarted() || handle->is_cancelled) { return len; } - if (!handle->header_callback) { + if (!handle->out_header_callback) { return len; } @@ -989,12 +966,12 @@ 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); 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 +981,94 @@ 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; - - if (!callback) { - OLP_SDK_LOG_WARNING( - kLogTag, - "CompleteMessage - message without callback, id=" << rhandle.id); - ReleaseHandleUnlocked(&rhandle, cleanup_easy_handle); - return; - } + RequestHandle* request_handle = FindRequestHandle(curl_handle); + if (!request_handle) { + OLP_SDK_LOG_WARNING(kLogTag, "Message completed to unknown request"); + return; + } - uint64_t upload_bytes = 0u; - uint64_t download_bytes = 0u; - GetTrafficData(rhandle.handle, upload_bytes, download_bytes); + logging::ScopedLogContext scopedLogContext(request_handle->log_context); + auto callback = std::move(request_handle->out_completion_callback); - auto response = NetworkResponse() - .WithRequestId(rhandle.id) - .WithBytesDownloaded(download_bytes) - .WithBytesUploaded(upload_bytes); + if (!callback) { + OLP_SDK_LOG_WARNING(kLogTag, + "CompleteMessage - message without callback, id=" + << request_handle->id); + return ReleaseHandleUnlocked(request_handle, cleanup_easy_handle); + } - if (rhandle.cancelled) { - response.WithStatus(static_cast(ErrorCode::CANCELLED_ERROR)) - .WithError("Cancelled"); - ReleaseHandleUnlocked(&rhandle, cleanup_easy_handle); + uint64_t upload_bytes = 0u; + uint64_t download_bytes = 0u; + GetTrafficData(curl_handle, upload_bytes, download_bytes); - lock.unlock(); - callback(response); - return; - } + auto response = NetworkResponse() + .WithRequestId(request_handle->id) + .WithBytesDownloaded(download_bytes) + .WithBytesUploaded(upload_bytes); - 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); + if (request_handle->is_cancelled) { + response.WithStatus(static_cast(ErrorCode::CANCELLED_ERROR)) + .WithError("Cancelled"); + ReleaseHandleUnlocked(request_handle, cleanup_easy_handle); - if ((rhandle.offset == 0u) && - (status == HttpStatusCode::PARTIAL_CONTENT)) { - status = HttpStatusCode::OK; - } + lock.unlock(); + callback(response); + return; + } - // For local file there is no server response so status is 0 - if ((status == 0) && (result == CURLE_OK)) { - 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); - 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); - } + if (status == HttpStatusCode::PARTIAL_CONTENT) { + status = HttpStatusCode::OK; + } - status = ConvertErrorCode(result); + // For local file, there is no server response so status is 0 + if ((status == 0) && (result == CURLE_OK)) { + status = HttpStatusCode::OK; } - const char* url; - curl_easy_getinfo(rhandle.handle, CURLINFO_EFFECTIVE_URL, &url); + 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); + } - 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); + status = ConvertErrorCode(result); + } - response.WithStatus(status).WithError(error); - ReleaseHandleUnlocked(&rhandle, cleanup_easy_handle); + const char* url; + curl_easy_getinfo(curl_handle, CURLINFO_EFFECTIVE_URL, &url); - lock.unlock(); - callback(response); - } else { - OLP_SDK_LOG_WARNING(kLogTag, "Message completed to unknown request"); - } + 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_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 +1092,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; } + CURL* curl_handle = request_handle->curl_handle.get(); + if (event.type == EventInfo::Type::SEND_EVENT) { - auto res = curl_multi_add_handle(curl_, rhandle->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)); + 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=" << 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, remove it from curl + 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,16 +1159,16 @@ 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; + ((msg = curl_multi_info_read(curl_, &msgs_in_queue)))) { + CURL* curl_handle = msg->easy_handle; uint64_t upload_bytes = 0u; uint64_t download_bytes = 0u; - GetTrafficData(handle, upload_bytes, download_bytes); + GetTrafficData(curl_handle, upload_bytes, download_bytes); 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 @@ -1199,35 +1176,37 @@ void NetworkCurl::Run() { 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; + } + + logging::ScopedLogContext scopedLogContext( + request_handle->log_context); + + auto callback = std::move(request_handle->out_completion_callback); - curl_multi_remove_handle(curl_, rhandle.handle); - const bool cleanup_easy_handle = true; - ReleaseHandleUnlocked(&rhandle, cleanup_easy_handle); + 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(); + auto response = + NetworkResponse() + .WithRequestId(request_handle->id) + .WithStatus(static_cast(ErrorCode::IO_ERROR)) + .WithError("CURL error") + .WithBytesDownloaded(download_bytes) + .WithBytesUploaded(upload_bytes); + callback(response); + lock.lock(); } + + curl_multi_remove_handle(curl_, curl_handle); + const bool cleanup_easy_handle = true; + ReleaseHandleUnlocked(request_handle, cleanup_easy_handle); } } } diff --git a/olp-cpp-sdk-core/src/http/curl/NetworkCurl.h b/olp-cpp-sdk-core/src/http/curl/NetworkCurl.h index 96240d089..d22d8d0d6 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. @@ -40,10 +40,10 @@ #error cURL enabled network implementation for Android requires MD5 from OpenSSL #endif -// Android uses MD5 to encode certificates name, but OpenSSL uses SHA1 for -// certificates lookup -> so OpenSSL won't be able to get appropriate +// Android uses MD5 to encode certificate name, but OpenSSL uses SHA1 for +// certificate lookup, so OpenSSL won't be able to get the appropriate // certificate as it won't be able to locate one. We need to add our custom -// lookup method that uses MD5. Old SHA1 lookup will be left as is. +// lookup method that uses MD5. Old SHA1 lookup will be left as it is. #define OLP_SDK_USE_MD5_CERT_LOOKUP #endif @@ -65,7 +65,7 @@ namespace http { /** * @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: /** @@ -102,8 +102,8 @@ class NetworkCurl : public olp::http::Network, * @brief Implementation of Send method from Network abstract class. */ SendOutcome Send(NetworkRequest request, Payload payload, Callback callback, - HeaderCallback header_callback = nullptr, - DataCallback data_callback = nullptr) override; + HeaderCallback header_callback, + DataCallback data_callback) override; /** * @brief Implementation of Cancel method from Network abstract class. @@ -112,26 +112,26 @@ class NetworkCurl : public olp::http::Network, private: /** - * @brief Context of each individual network request. + * @brief Context of each network request. */ struct RequestHandle { + 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::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 is_cancelled{false}; char error_text[CURL_ERROR_SIZE]{}; std::shared_ptr log_context; }; @@ -186,19 +186,18 @@ 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 - * or cancelled. After this call there will be no more callbacks triggered and + * or canceled. After this call, there will be no more callbacks triggered and * users can consider the request as done. * @return ErrorCode. */ 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. @@ -213,7 +212,7 @@ class NetworkCurl : public olp::http::Network, void Deinitialize(); /** - * @brief Check whether network is initialized. + * @brief Check whether the network is initialized. * @return @c true if initialized, @c false otherwise. */ bool Initialized() const; @@ -232,54 +231,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 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. + * 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.