aboutsummaryrefslogtreecommitdiff
path: root/src/zenhttp/clients/httpclientcurl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/zenhttp/clients/httpclientcurl.cpp')
-rw-r--r--src/zenhttp/clients/httpclientcurl.cpp293
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);