diff options
Diffstat (limited to 'src/zenhttp/clients/httpclientcurl.cpp')
| -rw-r--r-- | src/zenhttp/clients/httpclientcurl.cpp | 293 |
1 files changed, 22 insertions, 271 deletions
diff --git a/src/zenhttp/clients/httpclientcurl.cpp b/src/zenhttp/clients/httpclientcurl.cpp index d150b44c6..56b9c39c5 100644 --- a/src/zenhttp/clients/httpclientcurl.cpp +++ b/src/zenhttp/clients/httpclientcurl.cpp @@ -1,6 +1,7 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "httpclientcurl.h" +#include "httpclientcurlhelpers.h" #include <zencore/compactbinary.h> #include <zencore/compactbinarybuilder.h> @@ -29,153 +30,7 @@ static std::atomic<uint32_t> CurlHttpClientRequestIdCounter{0}; ////////////////////////////////////////////////////////////////////////// -static HttpClientErrorCode -MapCurlError(CURLcode Code) -{ - switch (Code) - { - case CURLE_OK: - return HttpClientErrorCode::kOK; - case CURLE_COULDNT_CONNECT: - return HttpClientErrorCode::kConnectionFailure; - case CURLE_COULDNT_RESOLVE_HOST: - return HttpClientErrorCode::kHostResolutionFailure; - case CURLE_COULDNT_RESOLVE_PROXY: - return HttpClientErrorCode::kProxyResolutionFailure; - case CURLE_RECV_ERROR: - return HttpClientErrorCode::kNetworkReceiveError; - case CURLE_SEND_ERROR: - return HttpClientErrorCode::kNetworkSendFailure; - case CURLE_OPERATION_TIMEDOUT: - return HttpClientErrorCode::kOperationTimedOut; - case CURLE_SSL_CONNECT_ERROR: - return HttpClientErrorCode::kSSLConnectError; - case CURLE_SSL_CERTPROBLEM: - return HttpClientErrorCode::kSSLCertificateError; - case CURLE_PEER_FAILED_VERIFICATION: - return HttpClientErrorCode::kSSLCACertError; - case CURLE_SSL_CIPHER: - case CURLE_SSL_ENGINE_NOTFOUND: - case CURLE_SSL_ENGINE_SETFAILED: - return HttpClientErrorCode::kGenericSSLError; - case CURLE_ABORTED_BY_CALLBACK: - return HttpClientErrorCode::kRequestCancelled; - default: - return HttpClientErrorCode::kOtherError; - } -} - -////////////////////////////////////////////////////////////////////////// -// -// Curl callback helpers - -struct WriteCallbackData -{ - std::string* Body = nullptr; - std::function<bool()>* CheckIfAbortFunction = nullptr; -}; - -static size_t -CurlWriteCallback(char* Ptr, size_t Size, size_t Nmemb, void* UserData) -{ - auto* Data = static_cast<WriteCallbackData*>(UserData); - size_t TotalBytes = Size * Nmemb; - - if (Data->CheckIfAbortFunction && *Data->CheckIfAbortFunction && (*Data->CheckIfAbortFunction)()) - { - return 0; // Signal abort to curl - } - - Data->Body->append(Ptr, TotalBytes); - return TotalBytes; -} - -struct HeaderCallbackData -{ - std::vector<std::pair<std::string, std::string>>* Headers = nullptr; -}; - -// Trims trailing CRLF, splits on the first colon, and trims whitespace from key and value. -// Returns nullopt for blank lines or lines without a colon (e.g. HTTP status lines). -static std::optional<std::pair<std::string_view, std::string_view>> -ParseHeaderLine(std::string_view Line) -{ - while (!Line.empty() && (Line.back() == '\r' || Line.back() == '\n')) - { - Line.remove_suffix(1); - } - - if (Line.empty()) - { - return std::nullopt; - } - - size_t ColonPos = Line.find(':'); - if (ColonPos == std::string_view::npos) - { - return std::nullopt; - } - - std::string_view Key = Line.substr(0, ColonPos); - std::string_view Value = Line.substr(ColonPos + 1); - - while (!Key.empty() && Key.back() == ' ') - { - Key.remove_suffix(1); - } - while (!Value.empty() && Value.front() == ' ') - { - Value.remove_prefix(1); - } - - return std::pair{Key, Value}; -} - -static size_t -CurlHeaderCallback(char* Buffer, size_t Size, size_t Nmemb, void* UserData) -{ - auto* Data = static_cast<HeaderCallbackData*>(UserData); - size_t TotalBytes = Size * Nmemb; - - if (auto Header = ParseHeaderLine(std::string_view(Buffer, TotalBytes))) - { - auto& [Key, Value] = *Header; - Data->Headers->emplace_back(std::string(Key), std::string(Value)); - } - - return TotalBytes; -} - -struct ReadCallbackData -{ - const uint8_t* DataPtr = nullptr; - size_t DataSize = 0; - size_t Offset = 0; - std::function<bool()>* CheckIfAbortFunction = nullptr; -}; - -static size_t -CurlReadCallback(char* Buffer, size_t Size, size_t Nmemb, void* UserData) -{ - auto* Data = static_cast<ReadCallbackData*>(UserData); - size_t MaxRead = Size * Nmemb; - - if (Data->CheckIfAbortFunction && *Data->CheckIfAbortFunction && (*Data->CheckIfAbortFunction)()) - { - return CURL_READFUNC_ABORT; - } - - size_t Remaining = Data->DataSize - Data->Offset; - size_t ToRead = std::min(MaxRead, Remaining); - - if (ToRead > 0) - { - memcpy(Buffer, Data->DataPtr + Data->Offset, ToRead); - Data->Offset += ToRead; - } - - return ToRead; -} +// Curl callback helpers and shared utilities are in httpclientcurlhelpers.h struct StreamReadCallbackData { @@ -281,120 +136,6 @@ CurlDebugCallback(CURL* Handle, curl_infotype Type, char* Data, size_t Size, voi ////////////////////////////////////////////////////////////////////////// -static std::pair<std::string, std::string> -HeaderContentType(ZenContentType ContentType) -{ - return std::make_pair("Content-Type", std::string(MapContentTypeToString(ContentType))); -} - -static curl_slist* -BuildHeaderList(const HttpClient::KeyValueMap& AdditionalHeader, - std::string_view SessionId, - const std::optional<std::string>& AccessToken, - const std::vector<std::pair<std::string, std::string>>& ExtraHeaders = {}) -{ - curl_slist* Headers = nullptr; - - for (const auto& [Key, Value] : *AdditionalHeader) - { - ExtendableStringBuilder<64> HeaderLine; - HeaderLine << Key << ": " << Value; - Headers = curl_slist_append(Headers, HeaderLine.c_str()); - } - - if (!SessionId.empty()) - { - ExtendableStringBuilder<64> SessionHeader; - SessionHeader << "UE-Session: " << SessionId; - Headers = curl_slist_append(Headers, SessionHeader.c_str()); - } - - if (AccessToken.has_value()) - { - ExtendableStringBuilder<128> AuthHeader; - AuthHeader << "Authorization: " << AccessToken.value(); - Headers = curl_slist_append(Headers, AuthHeader.c_str()); - } - - for (const auto& [Key, Value] : ExtraHeaders) - { - ExtendableStringBuilder<128> HeaderLine; - HeaderLine << Key << ": " << Value; - Headers = curl_slist_append(Headers, HeaderLine.c_str()); - } - - return Headers; -} - -static HttpClient::KeyValueMap -BuildHeaderMap(const std::vector<std::pair<std::string, std::string>>& Headers) -{ - HttpClient::KeyValueMap HeaderMap; - for (const auto& [Key, Value] : Headers) - { - HeaderMap->insert_or_assign(Key, Value); - } - return HeaderMap; -} - -// Scans response headers for Content-Type and applies it to the buffer. -static void -ApplyContentTypeFromHeaders(IoBuffer& Buffer, const std::vector<std::pair<std::string, std::string>>& Headers) -{ - for (const auto& [Key, Value] : Headers) - { - if (StrCaseCompare(Key, "Content-Type") == 0) - { - Buffer.SetContentType(ParseContentType(Value)); - break; - } - } -} - -static void -AppendUrlEncoded(StringBuilderBase& Out, std::string_view Input) -{ - static constexpr char HexDigits[] = "0123456789ABCDEF"; - static constexpr AsciiSet Unreserved("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~"); - - for (char C : Input) - { - if (Unreserved.Contains(C)) - { - Out.Append(C); - } - else - { - uint8_t Byte = static_cast<uint8_t>(C); - char Encoded[3] = {'%', HexDigits[Byte >> 4], HexDigits[Byte & 0x0F]}; - Out.Append(std::string_view(Encoded, 3)); - } - } -} - -static void -BuildUrlWithParameters(StringBuilderBase& Url, - std::string_view BaseUrl, - std::string_view ResourcePath, - const HttpClient::KeyValueMap& Parameters) -{ - Url.Append(BaseUrl); - Url.Append(ResourcePath); - - if (!Parameters->empty()) - { - char Separator = '?'; - for (const auto& [Key, Value] : *Parameters) - { - Url.Append(Separator); - AppendUrlEncoded(Url, Key); - Url.Append('='); - AppendUrlEncoded(Url, Value); - Separator = '&'; - } - } -} - ////////////////////////////////////////////////////////////////////////// CurlHttpClient::CurlHttpClient(std::string_view BaseUri, @@ -440,9 +181,9 @@ CurlHttpClient::CurlResult CurlHttpClient::Session::PerformWithResponseCallbacks() { std::string Body; - WriteCallbackData WriteData{.Body = &Body, + CurlWriteCallbackData WriteData{.Body = &Body, .CheckIfAbortFunction = Outer->m_CheckIfAbortFunction ? &Outer->m_CheckIfAbortFunction : nullptr}; - HeaderCallbackData HdrData{}; + CurlHeaderCallbackData HdrData{}; std::vector<std::pair<std::string, std::string>> ResponseHeaders; HdrData.Headers = &ResponseHeaders; @@ -487,6 +228,13 @@ CurlHttpClient::Session::Perform() curl_easy_getinfo(Handle, CURLINFO_SIZE_DOWNLOAD_T, &DownBytes); Result.DownloadedBytes = static_cast<int64_t>(DownBytes); + char* EffectiveUrl = nullptr; + curl_easy_getinfo(Handle, CURLINFO_EFFECTIVE_URL, &EffectiveUrl); + if (EffectiveUrl) + { + Result.Url = EffectiveUrl; + } + return Result; } @@ -553,8 +301,9 @@ CurlHttpClient::CommonResponse(std::string_view SessionId, if (Result.ErrorCode != CURLE_OPERATION_TIMEDOUT && Result.ErrorCode != CURLE_COULDNT_CONNECT && Result.ErrorCode != CURLE_ABORTED_BY_CALLBACK) { - ZEN_WARN("HttpClient client failure (session: {}): ({}) '{}'", + ZEN_WARN("HttpClient client failure (session: {}, url: {}): ({}) '{}'", SessionId, + Result.Url, static_cast<int>(Result.ErrorCode), Result.ErrorMessage); } @@ -702,6 +451,7 @@ CurlHttpClient::ShouldRetry(const CurlResult& Result) case CURLE_RECV_ERROR: case CURLE_SEND_ERROR: case CURLE_OPERATION_TIMEDOUT: + case CURLE_PARTIAL_FILE: return true; default: return false; @@ -748,10 +498,11 @@ CurlHttpClient::DoWithRetry(std::string_view SessionId, std::function<CurlResult { if (Result.ErrorCode != CURLE_OK) { - ZEN_INFO("Retry (session: {}): HTTP error ({}) '{}' Attempt {}/{}", + ZEN_INFO("Retry (session: {}): HTTP error ({}) '{}' (Curl error: {}) Attempt {}/{}", SessionId, static_cast<int>(MapCurlError(Result.ErrorCode)), Result.ErrorMessage, + static_cast<int>(Result.ErrorCode), Attempt, m_ConnectionSettings.RetryCount + 1); } @@ -998,9 +749,9 @@ CurlHttpClient::Put(std::string_view Url, const IoBuffer& Payload, const KeyValu curl_easy_setopt(H, CURLOPT_UPLOAD, 1L); curl_easy_setopt(H, CURLOPT_INFILESIZE_LARGE, static_cast<curl_off_t>(Payload.GetSize())); - ReadCallbackData ReadData{.DataPtr = static_cast<const uint8_t*>(Payload.GetData()), - .DataSize = Payload.GetSize(), - .CheckIfAbortFunction = m_CheckIfAbortFunction ? &m_CheckIfAbortFunction : nullptr}; + CurlReadCallbackData ReadData{.DataPtr = static_cast<const uint8_t*>(Payload.GetData()), + .DataSize = Payload.GetSize(), + .CheckIfAbortFunction = m_CheckIfAbortFunction ? &m_CheckIfAbortFunction : nullptr}; curl_easy_setopt(H, CURLOPT_READFUNCTION, CurlReadCallback); curl_easy_setopt(H, CURLOPT_READDATA, &ReadData); @@ -1367,9 +1118,9 @@ CurlHttpClient::Upload(std::string_view Url, const IoBuffer& Payload, const KeyV return Sess.PerformWithResponseCallbacks(); } - ReadCallbackData ReadData{.DataPtr = static_cast<const uint8_t*>(Payload.GetData()), - .DataSize = Payload.GetSize(), - .CheckIfAbortFunction = m_CheckIfAbortFunction ? &m_CheckIfAbortFunction : nullptr}; + CurlReadCallbackData ReadData{.DataPtr = static_cast<const uint8_t*>(Payload.GetData()), + .DataSize = Payload.GetSize(), + .CheckIfAbortFunction = m_CheckIfAbortFunction ? &m_CheckIfAbortFunction : nullptr}; curl_easy_setopt(H, CURLOPT_READFUNCTION, CurlReadCallback); curl_easy_setopt(H, CURLOPT_READDATA, &ReadData); |