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.cpp1100
1 files changed, 413 insertions, 687 deletions
diff --git a/src/zenhttp/clients/httpclientcurl.cpp b/src/zenhttp/clients/httpclientcurl.cpp
index 341adc5f7..ec9b7bac6 100644
--- a/src/zenhttp/clients/httpclientcurl.cpp
+++ b/src/zenhttp/clients/httpclientcurl.cpp
@@ -7,6 +7,8 @@
#include <zencore/compactbinarypackage.h>
#include <zencore/compactbinaryutil.h>
#include <zencore/compress.h>
+#include <zencore/except.h>
+#include <zencore/filesystem.h>
#include <zencore/iobuffer.h>
#include <zencore/iohash.h>
#include <zencore/session.h>
@@ -93,15 +95,11 @@ struct HeaderCallbackData
std::vector<std::pair<std::string, std::string>>* Headers = nullptr;
};
-static size_t
-CurlHeaderCallback(char* Buffer, size_t Size, size_t Nmemb, void* UserData)
+// 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)
{
- auto* Data = static_cast<HeaderCallbackData*>(UserData);
- size_t TotalBytes = Size * Nmemb;
-
- std::string_view Line(Buffer, TotalBytes);
-
- // Trim trailing \r\n
while (!Line.empty() && (Line.back() == '\r' || Line.back() == '\n'))
{
Line.remove_suffix(1);
@@ -109,25 +107,39 @@ CurlHeaderCallback(char* Buffer, size_t Size, size_t Nmemb, void* UserData)
if (Line.empty())
{
- return TotalBytes;
+ return std::nullopt;
}
size_t ColonPos = Line.find(':');
- if (ColonPos != std::string_view::npos)
+ if (ColonPos == std::string_view::npos)
{
- std::string_view Key = Line.substr(0, ColonPos);
- std::string_view Value = Line.substr(ColonPos + 1);
+ return std::nullopt;
+ }
- // Trim whitespace
- while (!Key.empty() && Key.back() == ' ')
- {
- Key.remove_suffix(1);
- }
- while (!Value.empty() && Value.front() == ' ')
- {
- Value.remove_prefix(1);
- }
+ 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));
}
@@ -285,57 +297,102 @@ BuildHeaderList(const HttpClient::KeyValueMap& AdditionalHeader,
for (const auto& [Key, Value] : *AdditionalHeader)
{
- std::string HeaderLine = fmt::format("{}: {}", Key, Value);
- Headers = curl_slist_append(Headers, HeaderLine.c_str());
+ ExtendableStringBuilder<64> HeaderLine;
+ HeaderLine << Key << ": " << Value;
+ Headers = curl_slist_append(Headers, HeaderLine.c_str());
}
if (!SessionId.empty())
{
- std::string SessionHeader = fmt::format("UE-Session: {}", SessionId);
- Headers = curl_slist_append(Headers, SessionHeader.c_str());
+ ExtendableStringBuilder<64> SessionHeader;
+ SessionHeader << "UE-Session: " << SessionId;
+ Headers = curl_slist_append(Headers, SessionHeader.c_str());
}
if (AccessToken)
{
- std::string AuthHeader = fmt::format("Authorization: {}", AccessToken->Value);
- Headers = curl_slist_append(Headers, AuthHeader.c_str());
+ ExtendableStringBuilder<128> AuthHeader;
+ AuthHeader << "Authorization: " << AccessToken->Value;
+ Headers = curl_slist_append(Headers, AuthHeader.c_str());
}
for (const auto& [Key, Value] : ExtraHeaders)
{
- std::string HeaderLine = fmt::format("{}: {}", Key, Value);
- Headers = curl_slist_append(Headers, HeaderLine.c_str());
+ ExtendableStringBuilder<128> HeaderLine;
+ HeaderLine << Key << ": " << Value;
+ Headers = curl_slist_append(Headers, HeaderLine.c_str());
}
return Headers;
}
-static std::string
-BuildUrlWithParameters(std::string_view BaseUrl, std::string_view ResourcePath, const HttpClient::KeyValueMap& Parameters)
+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)
{
- std::string Url;
- Url.reserve(BaseUrl.size() + ResourcePath.size() + 64);
- Url.append(BaseUrl);
- Url.append(ResourcePath);
+ Url.Append(BaseUrl);
+ Url.Append(ResourcePath);
if (!Parameters->empty())
{
char Separator = '?';
for (const auto& [Key, Value] : *Parameters)
{
- char* EncodedKey = curl_easy_escape(nullptr, Key.c_str(), static_cast<int>(Key.size()));
- char* EncodedValue = curl_easy_escape(nullptr, Value.c_str(), static_cast<int>(Value.size()));
- Url += Separator;
- Url += EncodedKey;
- Url += '=';
- Url += EncodedValue;
- curl_free(EncodedKey);
- curl_free(EncodedValue);
+ Url.Append(Separator);
+ AppendUrlEncoded(Url, Key);
+ Url.Append('=');
+ AppendUrlEncoded(Url, Value);
Separator = '&';
}
}
-
- return Url;
}
//////////////////////////////////////////////////////////////////////////
@@ -359,6 +416,48 @@ CurlHttpClient::~CurlHttpClient()
});
}
+CurlHttpClient::Session::~Session()
+{
+ if (HeaderList)
+ {
+ curl_slist_free_all(HeaderList);
+ }
+ Outer->ReleaseSession(Handle);
+}
+
+void
+CurlHttpClient::Session::SetHeaders(curl_slist* Headers)
+{
+ if (HeaderList)
+ {
+ curl_slist_free_all(HeaderList);
+ }
+ HeaderList = Headers;
+ curl_easy_setopt(Handle, CURLOPT_HTTPHEADER, HeaderList);
+}
+
+CurlHttpClient::CurlResult
+CurlHttpClient::Session::PerformWithResponseCallbacks()
+{
+ std::string Body;
+ WriteCallbackData WriteData{.Body = &Body,
+ .CheckIfAbortFunction = Outer->m_CheckIfAbortFunction ? &Outer->m_CheckIfAbortFunction : nullptr};
+ HeaderCallbackData HdrData{};
+ std::vector<std::pair<std::string, std::string>> ResponseHeaders;
+ HdrData.Headers = &ResponseHeaders;
+
+ curl_easy_setopt(Handle, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
+ curl_easy_setopt(Handle, CURLOPT_WRITEDATA, &WriteData);
+ curl_easy_setopt(Handle, CURLOPT_HEADERFUNCTION, CurlHeaderCallback);
+ curl_easy_setopt(Handle, CURLOPT_HEADERDATA, &HdrData);
+
+ CurlResult Result = Perform();
+ Result.Body = std::move(Body);
+ Result.Headers = std::move(ResponseHeaders);
+
+ return Result;
+}
+
CurlHttpClient::CurlResult
CurlHttpClient::Session::Perform()
{
@@ -411,15 +510,7 @@ CurlHttpClient::ResponseWithPayload(std::string_view SessionId,
{
IoBuffer ResponseBuffer = Payload ? std::move(Payload) : IoBuffer(IoBuffer::Clone, Result.Body.data(), Result.Body.size());
- for (const auto& [Key, Value] : Result.Headers)
- {
- if (StrCaseCompare(Key.c_str(), "Content-Type") == 0)
- {
- const HttpContentType ContentType = ParseContentType(Value);
- ResponseBuffer.SetContentType(ContentType);
- break;
- }
- }
+ ApplyContentTypeFromHeaders(ResponseBuffer, Result.Headers);
if (!IsHttpSuccessCode(WorkResponseCode) && WorkResponseCode != HttpResponseCode::NotFound)
{
@@ -438,15 +529,9 @@ CurlHttpClient::ResponseWithPayload(std::string_view SessionId,
return Lhs.RangeOffset < Rhs.RangeOffset;
});
- HttpClient::KeyValueMap HeaderMap;
- for (const auto& [Key, Value] : Result.Headers)
- {
- HeaderMap->insert_or_assign(Key, Value);
- }
-
return HttpClient::Response{.StatusCode = WorkResponseCode,
.ResponsePayload = std::move(ResponseBuffer),
- .Header = std::move(HeaderMap),
+ .Header = BuildHeaderMap(Result.Headers),
.UploadedBytes = Result.UploadedBytes,
.DownloadedBytes = Result.DownloadedBytes,
.ElapsedSeconds = Result.ElapsedSeconds,
@@ -475,16 +560,10 @@ CurlHttpClient::CommonResponse(std::string_view SessionId,
}
}
- HttpClient::KeyValueMap HeaderMap;
- for (const auto& [Key, Value] : Result.Headers)
- {
- HeaderMap->insert_or_assign(Key, Value);
- }
-
return HttpClient::Response{
.StatusCode = WorkResponseCode,
.ResponsePayload = IoBufferBuilder::MakeCloneFromMemory(Result.Body.data(), Result.Body.size()),
- .Header = std::move(HeaderMap),
+ .Header = BuildHeaderMap(Result.Headers),
.UploadedBytes = Result.UploadedBytes,
.DownloadedBytes = Result.DownloadedBytes,
.ElapsedSeconds = Result.ElapsedSeconds,
@@ -493,14 +572,8 @@ CurlHttpClient::CommonResponse(std::string_view SessionId,
if (WorkResponseCode == HttpResponseCode::NoContent || (Result.Body.empty() && !Payload))
{
- HttpClient::KeyValueMap HeaderMap;
- for (const auto& [Key, Value] : Result.Headers)
- {
- HeaderMap->insert_or_assign(Key, Value);
- }
-
return HttpClient::Response{.StatusCode = WorkResponseCode,
- .Header = std::move(HeaderMap),
+ .Header = BuildHeaderMap(Result.Headers),
.UploadedBytes = Result.UploadedBytes,
.DownloadedBytes = Result.DownloadedBytes,
.ElapsedSeconds = Result.ElapsedSeconds};
@@ -519,25 +592,43 @@ CurlHttpClient::ValidatePayload(CurlResult& Result, std::unique_ptr<detail::Temp
IoBuffer ResponseBuffer = (Result.Body.empty() && PayloadFile) ? PayloadFile->BorrowIoBuffer()
: IoBuffer(IoBuffer::Wrap, Result.Body.data(), Result.Body.size());
- // Find Content-Length in headers
+ // Collect relevant headers in a single pass
+ std::string_view ContentLengthValue;
+ std::string_view IoHashValue;
+ std::string_view ContentTypeValue;
+
for (const auto& [Key, Value] : Result.Headers)
{
- if (StrCaseCompare(Key.c_str(), "Content-Length") == 0)
+ if (ContentLengthValue.empty() && StrCaseCompare(Key, "Content-Length") == 0)
{
- std::optional<uint64_t> ExpectedContentSize = ParseInt<uint64_t>(Value);
- if (!ExpectedContentSize.has_value())
- {
- Result.ErrorCode = CURLE_RECV_ERROR;
- Result.ErrorMessage = fmt::format("Can not parse Content-Length header. Value: '{}'", Value);
- return false;
- }
- if (ExpectedContentSize.value() != ResponseBuffer.GetSize())
- {
- Result.ErrorCode = CURLE_RECV_ERROR;
- Result.ErrorMessage = fmt::format("Payload size {} does not match Content-Length {}", ResponseBuffer.GetSize(), Value);
- return false;
- }
- break;
+ ContentLengthValue = Value;
+ }
+ else if (IoHashValue.empty() && StrCaseCompare(Key, "X-Jupiter-IoHash") == 0)
+ {
+ IoHashValue = Value;
+ }
+ else if (ContentTypeValue.empty() && StrCaseCompare(Key, "Content-Type") == 0)
+ {
+ ContentTypeValue = Value;
+ }
+ }
+
+ // Validate Content-Length
+ if (!ContentLengthValue.empty())
+ {
+ std::optional<uint64_t> ExpectedContentSize = ParseInt<uint64_t>(ContentLengthValue);
+ if (!ExpectedContentSize.has_value())
+ {
+ Result.ErrorCode = CURLE_RECV_ERROR;
+ Result.ErrorMessage = fmt::format("Can not parse Content-Length header. Value: '{}'", ContentLengthValue);
+ return false;
+ }
+ if (ExpectedContentSize.value() != ResponseBuffer.GetSize())
+ {
+ Result.ErrorCode = CURLE_RECV_ERROR;
+ Result.ErrorMessage =
+ fmt::format("Payload size {} does not match Content-Length {}", ResponseBuffer.GetSize(), ContentLengthValue);
+ return false;
}
}
@@ -546,66 +637,55 @@ CurlHttpClient::ValidatePayload(CurlResult& Result, std::unique_ptr<detail::Temp
return true;
}
- // Check X-Jupiter-IoHash
- for (const auto& [Key, Value] : Result.Headers)
+ // Validate X-Jupiter-IoHash
+ if (!IoHashValue.empty())
{
- if (StrCaseCompare(Key.c_str(), "X-Jupiter-IoHash") == 0)
+ IoHash ExpectedPayloadHash;
+ if (IoHash::TryParse(IoHashValue, ExpectedPayloadHash))
{
- IoHash ExpectedPayloadHash;
- if (IoHash::TryParse(Value, ExpectedPayloadHash))
+ IoHash PayloadHash = IoHash::HashBuffer(ResponseBuffer);
+ if (PayloadHash != ExpectedPayloadHash)
{
- IoHash PayloadHash = IoHash::HashBuffer(ResponseBuffer);
- if (PayloadHash != ExpectedPayloadHash)
- {
- Result.ErrorCode = CURLE_RECV_ERROR;
- Result.ErrorMessage = fmt::format("Payload hash {} does not match X-Jupiter-IoHash {}",
- PayloadHash.ToHexString(),
- ExpectedPayloadHash.ToHexString());
- return false;
- }
+ Result.ErrorCode = CURLE_RECV_ERROR;
+ Result.ErrorMessage = fmt::format("Payload hash {} does not match X-Jupiter-IoHash {}",
+ PayloadHash.ToHexString(),
+ ExpectedPayloadHash.ToHexString());
+ return false;
}
- break;
}
}
// Validate content-type specific payload
- for (const auto& [Key, Value] : Result.Headers)
+ if (ContentTypeValue == "application/x-ue-comp")
{
- if (StrCaseCompare(Key.c_str(), "Content-Type") == 0)
+ IoHash RawHash;
+ uint64_t RawSize;
+ if (CompressedBuffer::ValidateCompressedHeader(ResponseBuffer,
+ RawHash,
+ RawSize,
+ /*OutOptionalTotalCompressedSize*/ nullptr))
{
- if (Value == "application/x-ue-comp")
- {
- IoHash RawHash;
- uint64_t RawSize;
- if (CompressedBuffer::ValidateCompressedHeader(ResponseBuffer,
- RawHash,
- RawSize,
- /*OutOptionalTotalCompressedSize*/ nullptr))
- {
- return true;
- }
- else
- {
- Result.ErrorCode = CURLE_RECV_ERROR;
- Result.ErrorMessage = "Compressed binary failed validation";
- return false;
- }
- }
- if (Value == "application/x-ue-cb")
- {
- if (CbValidateError Error = ValidateCompactBinary(ResponseBuffer.GetView(), CbValidateMode::Default);
- Error == CbValidateError::None)
- {
- return true;
- }
- else
- {
- Result.ErrorCode = CURLE_RECV_ERROR;
- Result.ErrorMessage = fmt::format("Compact binary failed validation: {}", ToString(Error));
- return false;
- }
- }
- break;
+ return true;
+ }
+ else
+ {
+ Result.ErrorCode = CURLE_RECV_ERROR;
+ Result.ErrorMessage = "Compressed binary failed validation";
+ return false;
+ }
+ }
+ if (ContentTypeValue == "application/x-ue-cb")
+ {
+ if (CbValidateError Error = ValidateCompactBinary(ResponseBuffer.GetView(), CbValidateMode::Default);
+ Error == CbValidateError::None)
+ {
+ return true;
+ }
+ else
+ {
+ Result.ErrorCode = CURLE_RECV_ERROR;
+ Result.ErrorMessage = fmt::format("Compact binary failed validation: {}", ToString(Error));
+ return false;
}
}
@@ -666,10 +746,24 @@ CurlHttpClient::DoWithRetry(std::string_view SessionId, std::function<CurlResult
Attempt++;
if (ShouldLogErrorCode(HttpResponseCode(Result.StatusCode)))
{
- ZEN_INFO("{} Attempt {}/{}",
- CommonResponse(SessionId, std::move(Result), {}).ErrorMessage("Retry"),
- Attempt,
- m_ConnectionSettings.RetryCount + 1);
+ if (Result.ErrorCode != CURLE_OK)
+ {
+ ZEN_INFO("Retry (session: {}): HTTP error ({}) '{}' Attempt {}/{}",
+ SessionId,
+ static_cast<int>(MapCurlError(Result.ErrorCode)),
+ Result.ErrorMessage,
+ Attempt,
+ m_ConnectionSettings.RetryCount + 1);
+ }
+ else
+ {
+ ZEN_INFO("Retry (session: {}): HTTP status ({}) '{}' Attempt {}/{}",
+ SessionId,
+ Result.StatusCode,
+ zen::ToString(HttpResponseCode(Result.StatusCode)),
+ Attempt,
+ m_ConnectionSettings.RetryCount + 1);
+ }
}
Result = Func();
}
@@ -681,51 +775,14 @@ CurlHttpClient::DoWithRetry(std::string_view SessionId,
std::function<CurlResult()>&& Func,
std::unique_ptr<detail::TempPayloadFile>& PayloadFile)
{
- uint8_t Attempt = 0;
- CurlResult Result = Func();
- while (Attempt < m_ConnectionSettings.RetryCount)
- {
- if (m_CheckIfAbortFunction && m_CheckIfAbortFunction())
- {
- return Result;
- }
- if (!ShouldRetry(Result))
- {
- if (Result.ErrorCode != CURLE_OK || !IsHttpSuccessCode(Result.StatusCode))
- {
- break;
- }
- if (ValidatePayload(Result, PayloadFile))
- {
- break;
- }
- }
- Sleep(100 * (Attempt + 1));
- Attempt++;
- if (ShouldLogErrorCode(HttpResponseCode(Result.StatusCode)))
- {
- ZEN_INFO("{} Attempt {}/{}",
- CommonResponse(SessionId, std::move(Result), {}).ErrorMessage("Retry"),
- Attempt,
- m_ConnectionSettings.RetryCount + 1);
- }
- Result = Func();
- }
- return Result;
+ return DoWithRetry(SessionId, std::move(Func), [&](CurlResult& Result) { return ValidatePayload(Result, PayloadFile); });
}
//////////////////////////////////////////////////////////////////////////
CurlHttpClient::Session
-CurlHttpClient::AllocSession(std::string_view BaseUrl,
- std::string_view ResourcePath,
- const HttpClientSettings& ConnectionSettings,
- const KeyValueMap& AdditionalHeader,
- const KeyValueMap& Parameters,
- std::string_view SessionId,
- std::optional<HttpClientAccessToken> AccessToken)
+CurlHttpClient::AllocSession(std::string_view ResourcePath, const KeyValueMap& Parameters)
{
- ZEN_UNUSED(AccessToken, SessionId, AdditionalHeader);
ZEN_TRACE_CPU("CurlHttpClient::AllocSession");
CURL* Handle = nullptr;
m_SessionLock.WithExclusiveLock([&] {
@@ -739,6 +796,10 @@ CurlHttpClient::AllocSession(std::string_view BaseUrl,
if (Handle == nullptr)
{
Handle = curl_easy_init();
+ if (Handle == nullptr)
+ {
+ ThrowOutOfMemory("curl_easy_init");
+ }
}
else
{
@@ -746,33 +807,35 @@ CurlHttpClient::AllocSession(std::string_view BaseUrl,
}
// Unix domain socket
- if (!ConnectionSettings.UnixSocketPath.empty())
+ if (!m_ConnectionSettings.UnixSocketPath.empty())
{
- curl_easy_setopt(Handle, CURLOPT_UNIX_SOCKET_PATH, ConnectionSettings.UnixSocketPath.c_str());
+ std::string SocketPathUtf8 = PathToUtf8(m_ConnectionSettings.UnixSocketPath);
+ curl_easy_setopt(Handle, CURLOPT_UNIX_SOCKET_PATH, SocketPathUtf8.c_str());
}
// Build URL with parameters
- std::string Url = BuildUrlWithParameters(BaseUrl, ResourcePath, Parameters);
+ ExtendableStringBuilder<256> Url;
+ BuildUrlWithParameters(Url, m_BaseUri, ResourcePath, Parameters);
curl_easy_setopt(Handle, CURLOPT_URL, Url.c_str());
// Timeouts
- if (ConnectionSettings.ConnectTimeout.count() > 0)
+ if (m_ConnectionSettings.ConnectTimeout.count() > 0)
{
- curl_easy_setopt(Handle, CURLOPT_CONNECTTIMEOUT_MS, static_cast<long>(ConnectionSettings.ConnectTimeout.count()));
+ curl_easy_setopt(Handle, CURLOPT_CONNECTTIMEOUT_MS, static_cast<long>(m_ConnectionSettings.ConnectTimeout.count()));
}
- if (ConnectionSettings.Timeout.count() > 0)
+ if (m_ConnectionSettings.Timeout.count() > 0)
{
- curl_easy_setopt(Handle, CURLOPT_TIMEOUT_MS, static_cast<long>(ConnectionSettings.Timeout.count()));
+ curl_easy_setopt(Handle, CURLOPT_TIMEOUT_MS, static_cast<long>(m_ConnectionSettings.Timeout.count()));
}
// HTTP/2
- if (ConnectionSettings.AssumeHttp2)
+ if (m_ConnectionSettings.AssumeHttp2)
{
curl_easy_setopt(Handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
}
// Verbose/debug
- if (ConnectionSettings.Verbose)
+ if (m_ConnectionSettings.Verbose)
{
curl_easy_setopt(Handle, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(Handle, CURLOPT_DEBUGFUNCTION, CurlDebugCallback);
@@ -780,27 +843,27 @@ CurlHttpClient::AllocSession(std::string_view BaseUrl,
}
// SSL options
- if (ConnectionSettings.InsecureSsl)
+ if (m_ConnectionSettings.InsecureSsl)
{
curl_easy_setopt(Handle, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(Handle, CURLOPT_SSL_VERIFYHOST, 0L);
}
- if (!ConnectionSettings.CaBundlePath.empty())
+ if (!m_ConnectionSettings.CaBundlePath.empty())
{
- curl_easy_setopt(Handle, CURLOPT_CAINFO, ConnectionSettings.CaBundlePath.c_str());
+ curl_easy_setopt(Handle, CURLOPT_CAINFO, m_ConnectionSettings.CaBundlePath.c_str());
}
// Disable signal handling for thread safety
curl_easy_setopt(Handle, CURLOPT_NOSIGNAL, 1L);
- if (ConnectionSettings.ForbidReuseConnection)
+ if (m_ConnectionSettings.ForbidReuseConnection)
{
curl_easy_setopt(Handle, CURLOPT_FORBID_REUSE, 1L);
}
// Note: Headers are NOT set here. Each method builds its own header list
- // (potentially adding method-specific headers like Content-Type) and is
- // responsible for freeing it with curl_slist_free_all.
+ // (potentially adding method-specific headers like Content-Type) and passes
+ // ownership to the Session via SetHeaders().
return Session(this, Handle);
}
@@ -809,15 +872,13 @@ void
CurlHttpClient::ReleaseSession(CURL* Handle)
{
ZEN_TRACE_CPU("CurlHttpClient::ReleaseSession");
-
- // Free any header list that was set
- // curl_easy_reset will be called on next AllocSession, which cleans up the handle state.
- // We just push the handle back to the pool.
m_SessionLock.WithExclusiveLock([&] { m_Sessions.push_back(Handle); });
}
//////////////////////////////////////////////////////////////////////////
+// TransactPackage is a two-phase protocol (offer + send) with server-side state
+// between phases, so retrying individual phases would be incorrect.
CurlHttpClient::Response
CurlHttpClient::TransactPackage(std::string_view Url, CbPackage Package, const KeyValueMap& AdditionalHeader)
{
@@ -831,7 +892,7 @@ CurlHttpClient::TransactPackage(std::string_view Url, CbPackage Package, const K
const uint32_t RequestId = ++CurlHttpClientRequestIdCounter;
auto RequestIdString = fmt::to_string(RequestId);
- if (Attachments.empty() == false)
+ if (!Attachments.empty())
{
CbObjectWriter Writer;
Writer.BeginArray("offer");
@@ -850,27 +911,19 @@ CurlHttpClient::TransactPackage(std::string_view Url, CbPackage Package, const K
OfferExtraHeaders.emplace_back(HeaderContentType(HttpContentType::kCbPackageOffer));
OfferExtraHeaders.emplace_back("UE-Request", RequestIdString);
- Session Sess = AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
+ Session Sess = AllocSession(Url, {});
CURL* H = Sess.Get();
- curl_slist* HeaderList = BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken(), OfferExtraHeaders);
- curl_easy_setopt(H, CURLOPT_HTTPHEADER, HeaderList);
+ Sess.SetHeaders(BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken(), OfferExtraHeaders));
curl_easy_setopt(H, CURLOPT_POST, 1L);
curl_easy_setopt(H, CURLOPT_POSTFIELDS, reinterpret_cast<const char*>(MemWriter.Data()));
curl_easy_setopt(H, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(MemWriter.Size()));
- std::string FilterBody;
- WriteCallbackData WriteData{.Body = &FilterBody};
- curl_easy_setopt(H, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
- curl_easy_setopt(H, CURLOPT_WRITEDATA, &WriteData);
-
- CurlResult Result = Sess.Perform();
-
- curl_slist_free_all(HeaderList);
+ CurlResult Result = Sess.PerformWithResponseCallbacks();
- if (Result.ErrorCode == CURLE_OK && Result.StatusCode == 200)
+ if (Result.ErrorCode == CURLE_OK && IsHttpSuccessCode(Result.StatusCode))
{
- IoBuffer ResponseBuffer(IoBuffer::Wrap, FilterBody.data(), FilterBody.size());
+ IoBuffer ResponseBuffer(IoBuffer::Wrap, Result.Body.data(), Result.Body.size());
CbValidateError ValidationError = CbValidateError::None;
if (CbObject ResponseObject = ValidateAndReadCompactBinaryObject(std::move(ResponseBuffer), ValidationError);
ValidationError == CbValidateError::None)
@@ -908,41 +961,17 @@ CurlHttpClient::TransactPackage(std::string_view Url, CbPackage Package, const K
PkgExtraHeaders.emplace_back(HeaderContentType(HttpContentType::kCbPackage));
PkgExtraHeaders.emplace_back("UE-Request", RequestIdString);
- Session Sess = AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
+ Session Sess = AllocSession(Url, {});
CURL* H = Sess.Get();
- curl_slist* HeaderList = BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken(), PkgExtraHeaders);
- curl_easy_setopt(H, CURLOPT_HTTPHEADER, HeaderList);
+ Sess.SetHeaders(BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken(), PkgExtraHeaders));
curl_easy_setopt(H, CURLOPT_POST, 1L);
curl_easy_setopt(H, CURLOPT_POSTFIELDS, reinterpret_cast<const char*>(FlatMessage.GetData()));
curl_easy_setopt(H, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(FlatMessage.GetSize()));
- std::string PkgBody;
- WriteCallbackData WriteData{.Body = &PkgBody};
- curl_easy_setopt(H, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
- curl_easy_setopt(H, CURLOPT_WRITEDATA, &WriteData);
-
- CurlResult Result = Sess.Perform();
-
- curl_slist_free_all(HeaderList);
+ CurlResult Result = Sess.PerformWithResponseCallbacks();
- if (Result.ErrorCode != CURLE_OK || !IsHttpSuccessCode(Result.StatusCode))
- {
- return {.StatusCode = HttpResponseCode(Result.StatusCode)};
- }
-
- IoBuffer ResponseBuffer(IoBuffer::Clone, PkgBody.data(), PkgBody.size());
-
- for (const auto& [Key, Value] : Result.Headers)
- {
- if (StrCaseCompare(Key.c_str(), "Content-Type") == 0)
- {
- ResponseBuffer.SetContentType(ParseContentType(Value));
- break;
- }
- }
-
- return {.StatusCode = HttpResponseCode(Result.StatusCode), .ResponsePayload = std::move(ResponseBuffer)};
+ return CommonResponse(m_SessionId, std::move(Result), {}, {});
}
//////////////////////////////////////////////////////////////////////////
@@ -957,44 +986,26 @@ CurlHttpClient::Put(std::string_view Url, const IoBuffer& Payload, const KeyValu
return CommonResponse(
m_SessionId,
- DoWithRetry(m_SessionId,
- [&]() -> CurlResult {
- Session Sess =
- AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
- CURL* H = Sess.Get();
-
- curl_slist* Headers =
- BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken(), {HeaderContentType(Payload.GetContentType())});
- curl_easy_setopt(H, CURLOPT_HTTPHEADER, Headers);
-
- 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};
- curl_easy_setopt(H, CURLOPT_READFUNCTION, CurlReadCallback);
- curl_easy_setopt(H, CURLOPT_READDATA, &ReadData);
-
- std::string Body;
- WriteCallbackData WriteData{.Body = &Body};
- HeaderCallbackData HdrData{};
- std::vector<std::pair<std::string, std::string>> ResponseHeaders;
- HdrData.Headers = &ResponseHeaders;
+ DoWithRetry(
+ m_SessionId,
+ [&]() -> CurlResult {
+ Session Sess = AllocSession(Url, {});
+ CURL* H = Sess.Get();
- curl_easy_setopt(H, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
- curl_easy_setopt(H, CURLOPT_WRITEDATA, &WriteData);
- curl_easy_setopt(H, CURLOPT_HEADERFUNCTION, CurlHeaderCallback);
- curl_easy_setopt(H, CURLOPT_HEADERDATA, &HdrData);
+ Sess.SetHeaders(
+ BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken(), {HeaderContentType(Payload.GetContentType())}));
- CurlResult Result = Sess.Perform();
- Result.Body = std::move(Body);
- Result.Headers = std::move(ResponseHeaders);
+ curl_easy_setopt(H, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt(H, CURLOPT_INFILESIZE_LARGE, static_cast<curl_off_t>(Payload.GetSize()));
- curl_slist_free_all(Headers);
+ ReadCallbackData 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);
- return Result;
- }),
+ return Sess.PerformWithResponseCallbacks();
+ }),
{});
}
@@ -1005,39 +1016,19 @@ CurlHttpClient::Put(std::string_view Url, const KeyValueMap& Parameters)
return CommonResponse(
m_SessionId,
- DoWithRetry(
- m_SessionId,
- [&]() -> CurlResult {
- KeyValueMap HeaderWithContentLength{std::pair<std::string_view, std::string_view>{"Content-Length", "0"}};
- Session Sess =
- AllocSession(m_BaseUri, Url, m_ConnectionSettings, HeaderWithContentLength, Parameters, m_SessionId, GetAccessToken());
- CURL* H = Sess.Get();
-
- curl_slist* Headers = BuildHeaderList(HeaderWithContentLength, m_SessionId, GetAccessToken());
- curl_easy_setopt(H, CURLOPT_HTTPHEADER, Headers);
-
- curl_easy_setopt(H, CURLOPT_UPLOAD, 1L);
- curl_easy_setopt(H, CURLOPT_INFILESIZE_LARGE, 0LL);
-
- std::string Body;
- WriteCallbackData WriteData{.Body = &Body};
- HeaderCallbackData HdrData{};
- std::vector<std::pair<std::string, std::string>> ResponseHeaders;
- HdrData.Headers = &ResponseHeaders;
-
- curl_easy_setopt(H, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
- curl_easy_setopt(H, CURLOPT_WRITEDATA, &WriteData);
- curl_easy_setopt(H, CURLOPT_HEADERFUNCTION, CurlHeaderCallback);
- curl_easy_setopt(H, CURLOPT_HEADERDATA, &HdrData);
+ DoWithRetry(m_SessionId,
+ [&]() -> CurlResult {
+ KeyValueMap HeaderWithContentLength{std::pair<std::string_view, std::string_view>{"Content-Length", "0"}};
+ Session Sess = AllocSession(Url, Parameters);
+ CURL* H = Sess.Get();
- CurlResult Result = Sess.Perform();
- Result.Body = std::move(Body);
- Result.Headers = std::move(ResponseHeaders);
+ Sess.SetHeaders(BuildHeaderList(HeaderWithContentLength, m_SessionId, GetAccessToken()));
- curl_slist_free_all(Headers);
+ curl_easy_setopt(H, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt(H, CURLOPT_INFILESIZE_LARGE, 0LL);
- return Result;
- }),
+ return Sess.PerformWithResponseCallbacks();
+ }),
{});
}
@@ -1045,43 +1036,20 @@ CurlHttpClient::Response
CurlHttpClient::Get(std::string_view Url, const KeyValueMap& AdditionalHeader, const KeyValueMap& Parameters)
{
ZEN_TRACE_CPU("CurlHttpClient::Get");
- return CommonResponse(
- m_SessionId,
- DoWithRetry(
- m_SessionId,
- [&]() -> CurlResult {
- Session Sess =
- AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, Parameters, m_SessionId, GetAccessToken());
- CURL* H = Sess.Get();
-
- curl_slist* Headers = BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken());
- curl_easy_setopt(H, CURLOPT_HTTPHEADER, Headers);
- curl_easy_setopt(H, CURLOPT_HTTPGET, 1L);
-
- std::string Body;
- WriteCallbackData WriteData{.Body = &Body};
- HeaderCallbackData HdrData{};
- std::vector<std::pair<std::string, std::string>> ResponseHeaders;
- HdrData.Headers = &ResponseHeaders;
-
- curl_easy_setopt(H, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
- curl_easy_setopt(H, CURLOPT_WRITEDATA, &WriteData);
- curl_easy_setopt(H, CURLOPT_HEADERFUNCTION, CurlHeaderCallback);
- curl_easy_setopt(H, CURLOPT_HEADERDATA, &HdrData);
-
- CurlResult Result = Sess.Perform();
- Result.Body = std::move(Body);
- Result.Headers = std::move(ResponseHeaders);
-
- curl_slist_free_all(Headers);
-
- return Result;
- },
- [this](CurlResult& Result) {
- std::unique_ptr<detail::TempPayloadFile> NoTempFile;
- return ValidatePayload(Result, NoTempFile);
- }),
- {});
+ return CommonResponse(m_SessionId,
+ DoWithRetry(
+ m_SessionId,
+ [&]() -> CurlResult {
+ Session Sess = AllocSession(Url, Parameters);
+ Sess.SetHeaders(BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken()));
+ curl_easy_setopt(Sess.Get(), CURLOPT_HTTPGET, 1L);
+ return Sess.PerformWithResponseCallbacks();
+ },
+ [this](CurlResult& Result) {
+ std::unique_ptr<detail::TempPayloadFile> NoTempFile;
+ return ValidatePayload(Result, NoTempFile);
+ }),
+ {});
}
CurlHttpClient::Response
@@ -1089,33 +1057,15 @@ CurlHttpClient::Head(std::string_view Url, const KeyValueMap& AdditionalHeader)
{
ZEN_TRACE_CPU("CurlHttpClient::Head");
- return CommonResponse(
- m_SessionId,
- DoWithRetry(m_SessionId,
- [&]() -> CurlResult {
- Session Sess =
- AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
- CURL* H = Sess.Get();
-
- curl_slist* Headers = BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken());
- curl_easy_setopt(H, CURLOPT_HTTPHEADER, Headers);
- curl_easy_setopt(H, CURLOPT_NOBODY, 1L);
-
- HeaderCallbackData HdrData{};
- std::vector<std::pair<std::string, std::string>> ResponseHeaders;
- HdrData.Headers = &ResponseHeaders;
-
- curl_easy_setopt(H, CURLOPT_HEADERFUNCTION, CurlHeaderCallback);
- curl_easy_setopt(H, CURLOPT_HEADERDATA, &HdrData);
-
- CurlResult Result = Sess.Perform();
- Result.Headers = std::move(ResponseHeaders);
-
- curl_slist_free_all(Headers);
-
- return Result;
- }),
- {});
+ return CommonResponse(m_SessionId,
+ DoWithRetry(m_SessionId,
+ [&]() -> CurlResult {
+ Session Sess = AllocSession(Url, {});
+ Sess.SetHeaders(BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken()));
+ curl_easy_setopt(Sess.Get(), CURLOPT_NOBODY, 1L);
+ return Sess.PerformWithResponseCallbacks();
+ }),
+ {});
}
CurlHttpClient::Response
@@ -1123,38 +1073,15 @@ CurlHttpClient::Delete(std::string_view Url, const KeyValueMap& AdditionalHeader
{
ZEN_TRACE_CPU("CurlHttpClient::Delete");
- return CommonResponse(
- m_SessionId,
- DoWithRetry(m_SessionId,
- [&]() -> CurlResult {
- Session Sess =
- AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
- CURL* H = Sess.Get();
-
- curl_slist* Headers = BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken());
- curl_easy_setopt(H, CURLOPT_HTTPHEADER, Headers);
- curl_easy_setopt(H, CURLOPT_CUSTOMREQUEST, "DELETE");
-
- std::string Body;
- WriteCallbackData WriteData{.Body = &Body};
- HeaderCallbackData HdrData{};
- std::vector<std::pair<std::string, std::string>> ResponseHeaders;
- HdrData.Headers = &ResponseHeaders;
-
- curl_easy_setopt(H, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
- curl_easy_setopt(H, CURLOPT_WRITEDATA, &WriteData);
- curl_easy_setopt(H, CURLOPT_HEADERFUNCTION, CurlHeaderCallback);
- curl_easy_setopt(H, CURLOPT_HEADERDATA, &HdrData);
-
- CurlResult Result = Sess.Perform();
- Result.Body = std::move(Body);
- Result.Headers = std::move(ResponseHeaders);
-
- curl_slist_free_all(Headers);
-
- return Result;
- }),
- {});
+ return CommonResponse(m_SessionId,
+ DoWithRetry(m_SessionId,
+ [&]() -> CurlResult {
+ Session Sess = AllocSession(Url, {});
+ Sess.SetHeaders(BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken()));
+ curl_easy_setopt(Sess.Get(), CURLOPT_CUSTOMREQUEST, "DELETE");
+ return Sess.PerformWithResponseCallbacks();
+ }),
+ {});
}
CurlHttpClient::Response
@@ -1162,39 +1089,16 @@ CurlHttpClient::Post(std::string_view Url, const KeyValueMap& AdditionalHeader,
{
ZEN_TRACE_CPU("CurlHttpClient::PostNoPayload");
- return CommonResponse(
- m_SessionId,
- DoWithRetry(m_SessionId,
- [&]() -> CurlResult {
- Session Sess =
- AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, Parameters, m_SessionId, GetAccessToken());
- CURL* H = Sess.Get();
-
- curl_slist* Headers = BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken());
- curl_easy_setopt(H, CURLOPT_HTTPHEADER, Headers);
- curl_easy_setopt(H, CURLOPT_POST, 1L);
- curl_easy_setopt(H, CURLOPT_POSTFIELDSIZE, 0L);
-
- std::string Body;
- WriteCallbackData WriteData{.Body = &Body};
- HeaderCallbackData HdrData{};
- std::vector<std::pair<std::string, std::string>> ResponseHeaders;
- HdrData.Headers = &ResponseHeaders;
-
- curl_easy_setopt(H, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
- curl_easy_setopt(H, CURLOPT_WRITEDATA, &WriteData);
- curl_easy_setopt(H, CURLOPT_HEADERFUNCTION, CurlHeaderCallback);
- curl_easy_setopt(H, CURLOPT_HEADERDATA, &HdrData);
-
- CurlResult Result = Sess.Perform();
- Result.Body = std::move(Body);
- Result.Headers = std::move(ResponseHeaders);
-
- curl_slist_free_all(Headers);
-
- return Result;
- }),
- {});
+ return CommonResponse(m_SessionId,
+ DoWithRetry(m_SessionId,
+ [&]() -> CurlResult {
+ Session Sess = AllocSession(Url, Parameters);
+ Sess.SetHeaders(BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken()));
+ curl_easy_setopt(Sess.Get(), CURLOPT_POST, 1L);
+ curl_easy_setopt(Sess.Get(), CURLOPT_POSTFIELDSIZE, 0L);
+ return Sess.PerformWithResponseCallbacks();
+ }),
+ {});
}
CurlHttpClient::Response
@@ -1213,12 +1117,10 @@ CurlHttpClient::Post(std::string_view Url, const IoBuffer& Payload, ZenContentTy
DoWithRetry(
m_SessionId,
[&]() -> CurlResult {
- Session Sess = AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
+ Session Sess = AllocSession(Url, {});
CURL* H = Sess.Get();
- // Rebuild headers with content type
- curl_slist* Headers = BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken(), {HeaderContentType(ContentType)});
- curl_easy_setopt(H, CURLOPT_HTTPHEADER, Headers);
+ Sess.SetHeaders(BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken(), {HeaderContentType(ContentType)}));
IoBufferFileReference FileRef = {nullptr, 0, 0};
if (Payload.GetFileReference(FileRef))
@@ -1234,46 +1136,14 @@ CurlHttpClient::Post(std::string_view Url, const IoBuffer& Payload, ZenContentTy
curl_easy_setopt(H, CURLOPT_READFUNCTION, CurlFileReadCallback);
curl_easy_setopt(H, CURLOPT_READDATA, &ReadData);
- std::string Body;
- WriteCallbackData WriteData{.Body = &Body};
- HeaderCallbackData HdrData{};
- std::vector<std::pair<std::string, std::string>> ResponseHeaders;
- HdrData.Headers = &ResponseHeaders;
-
- curl_easy_setopt(H, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
- curl_easy_setopt(H, CURLOPT_WRITEDATA, &WriteData);
- curl_easy_setopt(H, CURLOPT_HEADERFUNCTION, CurlHeaderCallback);
- curl_easy_setopt(H, CURLOPT_HEADERDATA, &HdrData);
-
- CurlResult Result = Sess.Perform();
- Result.Body = std::move(Body);
- Result.Headers = std::move(ResponseHeaders);
-
- curl_slist_free_all(Headers);
- return Result;
+ return Sess.PerformWithResponseCallbacks();
}
curl_easy_setopt(H, CURLOPT_POST, 1L);
curl_easy_setopt(H, CURLOPT_POSTFIELDS, reinterpret_cast<const char*>(Payload.GetData()));
curl_easy_setopt(H, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(Payload.GetSize()));
- std::string Body;
- WriteCallbackData WriteData{.Body = &Body};
- HeaderCallbackData HdrData{};
- std::vector<std::pair<std::string, std::string>> ResponseHeaders;
- HdrData.Headers = &ResponseHeaders;
-
- curl_easy_setopt(H, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
- curl_easy_setopt(H, CURLOPT_WRITEDATA, &WriteData);
- curl_easy_setopt(H, CURLOPT_HEADERFUNCTION, CurlHeaderCallback);
- curl_easy_setopt(H, CURLOPT_HEADERDATA, &HdrData);
-
- CurlResult Result = Sess.Perform();
- Result.Body = std::move(Body);
- Result.Headers = std::move(ResponseHeaders);
-
- curl_slist_free_all(Headers);
- return Result;
+ return Sess.PerformWithResponseCallbacks();
}),
{});
}
@@ -1295,12 +1165,11 @@ CurlHttpClient::Post(std::string_view Url,
PayloadString.clear();
PayloadFile.reset();
- Session Sess = AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
+ Session Sess = AllocSession(Url, {});
CURL* H = Sess.Get();
- curl_slist* Headers =
- BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken(), {HeaderContentType(ZenContentType::kCbObject)});
- curl_easy_setopt(H, CURLOPT_HTTPHEADER, Headers);
+ Sess.SetHeaders(
+ BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken(), {HeaderContentType(ZenContentType::kCbObject)}));
curl_easy_setopt(H, CURLOPT_POST, 1L);
curl_easy_setopt(H, CURLOPT_POSTFIELDS, reinterpret_cast<const char*>(Payload.GetBuffer().GetData()));
@@ -1329,33 +1198,11 @@ CurlHttpClient::Post(std::string_view Url,
auto* Data = static_cast<PostHeaderCallbackData*>(UserData);
size_t TotalBytes = Size * Nmemb;
- std::string_view Line(Buffer, TotalBytes);
- while (!Line.empty() && (Line.back() == '\r' || Line.back() == '\n'))
+ if (auto Header = ParseHeaderLine(std::string_view(Buffer, TotalBytes)))
{
- Line.remove_suffix(1);
- }
-
- if (Line.empty())
- {
- return TotalBytes;
- }
-
- size_t ColonPos = Line.find(':');
- if (ColonPos != std::string_view::npos)
- {
- 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);
- }
+ auto& [Key, Value] = *Header;
- if (StrCaseCompare(std::string(Key).c_str(), "Content-Length") == 0)
+ if (StrCaseCompare(Key, "Content-Length") == 0)
{
std::optional<size_t> ContentLength = ParseInt<size_t>(Value);
if (ContentLength.has_value())
@@ -1444,7 +1291,6 @@ CurlHttpClient::Post(std::string_view Url,
Res.Body = std::move(PayloadString);
}
- curl_slist_free_all(Headers);
return Res;
},
PayloadFile);
@@ -1467,13 +1313,10 @@ CurlHttpClient::Post(std::string_view Url, const CompositeBuffer& Payload, ZenCo
m_SessionId,
DoWithRetry(m_SessionId,
[&]() -> CurlResult {
- Session Sess =
- AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
- CURL* H = Sess.Get();
+ Session Sess = AllocSession(Url, {});
+ CURL* H = Sess.Get();
- curl_slist* Headers =
- BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken(), {HeaderContentType(ContentType)});
- curl_easy_setopt(H, CURLOPT_HTTPHEADER, Headers);
+ Sess.SetHeaders(BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken(), {HeaderContentType(ContentType)}));
detail::CompositeBufferReadStream Reader(Payload, 512u * 1024u);
@@ -1485,23 +1328,7 @@ CurlHttpClient::Post(std::string_view Url, const CompositeBuffer& Payload, ZenCo
curl_easy_setopt(H, CURLOPT_READFUNCTION, CurlStreamReadCallback);
curl_easy_setopt(H, CURLOPT_READDATA, &ReadData);
- std::string Body;
- WriteCallbackData WriteData{.Body = &Body};
- HeaderCallbackData HdrData{};
- std::vector<std::pair<std::string, std::string>> ResponseHeaders;
- HdrData.Headers = &ResponseHeaders;
-
- curl_easy_setopt(H, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
- curl_easy_setopt(H, CURLOPT_WRITEDATA, &WriteData);
- curl_easy_setopt(H, CURLOPT_HEADERFUNCTION, CurlHeaderCallback);
- curl_easy_setopt(H, CURLOPT_HEADERDATA, &HdrData);
-
- CurlResult Result = Sess.Perform();
- Result.Body = std::move(Body);
- Result.Headers = std::move(ResponseHeaders);
-
- curl_slist_free_all(Headers);
- return Result;
+ return Sess.PerformWithResponseCallbacks();
}),
{});
}
@@ -1516,12 +1343,11 @@ CurlHttpClient::Upload(std::string_view Url, const IoBuffer& Payload, const KeyV
DoWithRetry(
m_SessionId,
[&]() -> CurlResult {
- Session Sess = AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
+ Session Sess = AllocSession(Url, {});
CURL* H = Sess.Get();
- curl_slist* Headers =
- BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken(), {HeaderContentType(Payload.GetContentType())});
- curl_easy_setopt(H, CURLOPT_HTTPHEADER, Headers);
+ Sess.SetHeaders(
+ BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken(), {HeaderContentType(Payload.GetContentType())}));
curl_easy_setopt(H, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(H, CURLOPT_INFILESIZE_LARGE, static_cast<curl_off_t>(Payload.GetSize()));
@@ -1538,23 +1364,7 @@ CurlHttpClient::Upload(std::string_view Url, const IoBuffer& Payload, const KeyV
curl_easy_setopt(H, CURLOPT_READFUNCTION, CurlFileReadCallback);
curl_easy_setopt(H, CURLOPT_READDATA, &ReadData);
- std::string Body;
- WriteCallbackData WriteData{.Body = &Body};
- HeaderCallbackData HdrData{};
- std::vector<std::pair<std::string, std::string>> ResponseHeaders;
- HdrData.Headers = &ResponseHeaders;
-
- curl_easy_setopt(H, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
- curl_easy_setopt(H, CURLOPT_WRITEDATA, &WriteData);
- curl_easy_setopt(H, CURLOPT_HEADERFUNCTION, CurlHeaderCallback);
- curl_easy_setopt(H, CURLOPT_HEADERDATA, &HdrData);
-
- CurlResult Result = Sess.Perform();
- Result.Body = std::move(Body);
- Result.Headers = std::move(ResponseHeaders);
-
- curl_slist_free_all(Headers);
- return Result;
+ return Sess.PerformWithResponseCallbacks();
}
ReadCallbackData ReadData{.DataPtr = static_cast<const uint8_t*>(Payload.GetData()),
@@ -1563,23 +1373,7 @@ CurlHttpClient::Upload(std::string_view Url, const IoBuffer& Payload, const KeyV
curl_easy_setopt(H, CURLOPT_READFUNCTION, CurlReadCallback);
curl_easy_setopt(H, CURLOPT_READDATA, &ReadData);
- std::string Body;
- WriteCallbackData WriteData{.Body = &Body};
- HeaderCallbackData HdrData{};
- std::vector<std::pair<std::string, std::string>> ResponseHeaders;
- HdrData.Headers = &ResponseHeaders;
-
- curl_easy_setopt(H, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
- curl_easy_setopt(H, CURLOPT_WRITEDATA, &WriteData);
- curl_easy_setopt(H, CURLOPT_HEADERFUNCTION, CurlHeaderCallback);
- curl_easy_setopt(H, CURLOPT_HEADERDATA, &HdrData);
-
- CurlResult Result = Sess.Perform();
- Result.Body = std::move(Body);
- Result.Headers = std::move(ResponseHeaders);
-
- curl_slist_free_all(Headers);
- return Result;
+ return Sess.PerformWithResponseCallbacks();
}),
{});
}
@@ -1596,13 +1390,10 @@ CurlHttpClient::Upload(std::string_view Url,
m_SessionId,
DoWithRetry(m_SessionId,
[&]() -> CurlResult {
- Session Sess =
- AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
- CURL* H = Sess.Get();
+ Session Sess = AllocSession(Url, {});
+ CURL* H = Sess.Get();
- curl_slist* Headers =
- BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken(), {HeaderContentType(ContentType)});
- curl_easy_setopt(H, CURLOPT_HTTPHEADER, Headers);
+ Sess.SetHeaders(BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken(), {HeaderContentType(ContentType)}));
curl_easy_setopt(H, CURLOPT_UPLOAD, 1L);
curl_easy_setopt(H, CURLOPT_INFILESIZE_LARGE, static_cast<curl_off_t>(Payload.GetSize()));
@@ -1615,23 +1406,7 @@ CurlHttpClient::Upload(std::string_view Url,
curl_easy_setopt(H, CURLOPT_READFUNCTION, CurlStreamReadCallback);
curl_easy_setopt(H, CURLOPT_READDATA, &ReadData);
- std::string Body;
- WriteCallbackData WriteData{.Body = &Body};
- HeaderCallbackData HdrData{};
- std::vector<std::pair<std::string, std::string>> ResponseHeaders;
- HdrData.Headers = &ResponseHeaders;
-
- curl_easy_setopt(H, CURLOPT_WRITEFUNCTION, CurlWriteCallback);
- curl_easy_setopt(H, CURLOPT_WRITEDATA, &WriteData);
- curl_easy_setopt(H, CURLOPT_HEADERFUNCTION, CurlHeaderCallback);
- curl_easy_setopt(H, CURLOPT_HEADERDATA, &HdrData);
-
- CurlResult Result = Sess.Perform();
- Result.Body = std::move(Body);
- Result.Headers = std::move(ResponseHeaders);
-
- curl_slist_free_all(Headers);
- return Result;
+ return Sess.PerformWithResponseCallbacks();
}),
{});
}
@@ -1651,11 +1426,10 @@ CurlHttpClient::Download(std::string_view Url, const std::filesystem::path& Temp
CurlResult Result = DoWithRetry(
m_SessionId,
[&]() -> CurlResult {
- Session Sess = AllocSession(m_BaseUri, Url, m_ConnectionSettings, AdditionalHeader, {}, m_SessionId, GetAccessToken());
+ Session Sess = AllocSession(Url, {});
CURL* H = Sess.Get();
- curl_slist* DlHeaders = BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken());
- curl_easy_setopt(H, CURLOPT_HTTPHEADER, DlHeaders);
+ Sess.SetHeaders(BuildHeaderList(AdditionalHeader, m_SessionId, GetAccessToken()));
curl_easy_setopt(H, CURLOPT_HTTPGET, 1L);
// Reset state from any previous attempt
@@ -1673,7 +1447,7 @@ CurlHttpClient::Download(std::string_view Url, const std::filesystem::path& Temp
{
std::string_view RangeValue(RangeIt->second);
size_t RangeStartPos = RangeValue.find('=', 5);
- if (RangeStartPos != std::string::npos)
+ if (RangeStartPos != std::string_view::npos)
{
RangeStartPos++;
while (RangeStartPos < RangeValue.length() && RangeValue[RangeStartPos] == ' ')
@@ -1685,14 +1459,14 @@ CurlHttpClient::Download(std::string_view Url, const std::filesystem::path& Temp
while (RangeStartPos < RangeValue.length())
{
size_t RangeEnd = RangeValue.find_first_of(", \r\n", RangeStartPos);
- if (RangeEnd == std::string::npos)
+ if (RangeEnd == std::string_view::npos)
{
RangeEnd = RangeValue.length();
}
std::string_view RangeString = RangeValue.substr(RangeStartPos, RangeEnd - RangeStartPos);
size_t RangeSplitPos = RangeString.find('-');
- if (RangeSplitPos != std::string::npos)
+ if (RangeSplitPos != std::string_view::npos)
{
std::optional<size_t> RequestedRangeStart = ParseInt<size_t>(RangeString.substr(0, RangeSplitPos));
std::optional<size_t> RequestedRangeEnd = ParseInt<size_t>(RangeString.substr(RangeSplitPos + 1));
@@ -1742,36 +1516,12 @@ CurlHttpClient::Download(std::string_view Url, const std::filesystem::path& Temp
auto* Data = static_cast<DownloadHeaderCallbackData*>(UserData);
size_t TotalBytes = Size * Nmemb;
- std::string_view Line(Buffer, TotalBytes);
-
- while (!Line.empty() && (Line.back() == '\r' || Line.back() == '\n'))
- {
- Line.remove_suffix(1);
- }
-
- if (Line.empty())
+ if (auto Header = ParseHeaderLine(std::string_view(Buffer, TotalBytes)))
{
- return TotalBytes;
- }
-
- size_t ColonPos = Line.find(':');
- if (ColonPos != std::string_view::npos)
- {
- std::string_view KeyView = Line.substr(0, ColonPos);
- std::string_view Value = Line.substr(ColonPos + 1);
-
- while (!KeyView.empty() && KeyView.back() == ' ')
- {
- KeyView.remove_suffix(1);
- }
- while (!Value.empty() && Value.front() == ' ')
- {
- Value.remove_prefix(1);
- }
-
+ auto& [KeyView, Value] = *Header;
const std::string Key(KeyView);
- if (StrCaseCompare(Key.c_str(), "Content-Length") == 0)
+ if (StrCaseCompare(Key, "Content-Length") == 0)
{
std::optional<size_t> ContentLength = ParseInt<size_t>(Value);
if (ContentLength.has_value())
@@ -1795,7 +1545,7 @@ CurlHttpClient::Download(std::string_view Url, const std::filesystem::path& Temp
}
}
}
- else if (StrCaseCompare(Key.c_str(), "Content-Type") == 0)
+ else if (StrCaseCompare(Key, "Content-Type") == 0)
{
*Data->IsMultiRange = Data->BoundaryParser->Init(Value);
if (!*Data->IsMultiRange)
@@ -1803,7 +1553,7 @@ CurlHttpClient::Download(std::string_view Url, const std::filesystem::path& Temp
*Data->ContentTypeOut = ParseContentType(Value);
}
}
- else if (StrCaseCompare(Key.c_str(), "Content-Range") == 0)
+ else if (StrCaseCompare(Key, "Content-Range") == 0)
{
if (!*Data->IsMultiRange)
{
@@ -1819,7 +1569,7 @@ CurlHttpClient::Download(std::string_view Url, const std::filesystem::path& Temp
}
}
- Data->Headers->emplace_back(std::string(Key), std::string(Value));
+ Data->Headers->emplace_back(Key, std::string(Value));
}
return TotalBytes;
@@ -1894,11 +1644,11 @@ CurlHttpClient::Download(std::string_view Url, const std::filesystem::path& Temp
auto SupportsRanges = [](const CurlResult& R) -> bool {
for (const auto& [K, V] : R.Headers)
{
- if (StrCaseCompare(K.c_str(), "Content-Range") == 0)
+ if (StrCaseCompare(K, "Content-Range") == 0)
{
return true;
}
- if (StrCaseCompare(K.c_str(), "Accept-Ranges") == 0)
+ if (StrCaseCompare(K, "Accept-Ranges") == 0)
{
return V == "bytes"sv;
}
@@ -1924,7 +1674,7 @@ CurlHttpClient::Download(std::string_view Url, const std::filesystem::path& Temp
std::string ContentLengthValue;
for (const auto& [K, V] : Res.Headers)
{
- if (StrCaseCompare(K.c_str(), "Content-Length") == 0)
+ if (StrCaseCompare(K, "Content-Length") == 0)
{
ContentLengthValue = V;
break;
@@ -1943,6 +1693,7 @@ CurlHttpClient::Download(std::string_view Url, const std::filesystem::path& Temp
}
KeyValueMap HeadersWithRange(AdditionalHeader);
+ uint8_t ResumeAttempt = 0;
do
{
uint64_t DownloadedSize = PayloadFile ? PayloadFile->GetSize() : PayloadString.length();
@@ -1957,12 +1708,10 @@ CurlHttpClient::Download(std::string_view Url, const std::filesystem::path& Temp
}
HeadersWithRange.Entries.insert_or_assign("Range", Range);
- Session ResumeSess =
- AllocSession(m_BaseUri, Url, m_ConnectionSettings, HeadersWithRange, {}, m_SessionId, GetAccessToken());
- CURL* ResumeH = ResumeSess.Get();
+ Session ResumeSess = AllocSession(Url, {});
+ CURL* ResumeH = ResumeSess.Get();
- curl_slist* ResumeHdrList = BuildHeaderList(HeadersWithRange, m_SessionId, GetAccessToken());
- curl_easy_setopt(ResumeH, CURLOPT_HTTPHEADER, ResumeHdrList);
+ ResumeSess.SetHeaders(BuildHeaderList(HeadersWithRange, m_SessionId, GetAccessToken()));
curl_easy_setopt(ResumeH, CURLOPT_HTTPGET, 1L);
std::vector<std::pair<std::string, std::string>> ResumeHeaders;
@@ -1983,72 +1732,51 @@ CurlHttpClient::Download(std::string_view Url, const std::filesystem::path& Temp
auto* Data = static_cast<ResumeHeaderCbData*>(UserData);
size_t TotalBytes = Size * Nmemb;
- std::string_view Line(Buffer, TotalBytes);
- while (!Line.empty() && (Line.back() == '\r' || Line.back() == '\n'))
- {
- Line.remove_suffix(1);
- }
-
- if (Line.empty())
+ auto Header = ParseHeaderLine(std::string_view(Buffer, TotalBytes));
+ if (!Header)
{
return TotalBytes;
}
+ auto& [Key, Value] = *Header;
- size_t ColonPos = Line.find(':');
- if (ColonPos != std::string_view::npos)
+ if (StrCaseCompare(Key, "Content-Range") == 0)
{
- std::string_view Key = Line.substr(0, ColonPos);
- std::string_view Value = Line.substr(ColonPos + 1);
- while (!Key.empty() && Key.back() == ' ')
+ if (Value.starts_with("bytes "sv))
{
- Key.remove_suffix(1);
- }
- while (!Value.empty() && Value.front() == ' ')
- {
- Value.remove_prefix(1);
- }
-
- if (StrCaseCompare(std::string(Key).c_str(), "Content-Range") == 0)
- {
- if (Value.starts_with("bytes "sv))
+ size_t RangeStartEnd = Value.find('-', 6);
+ if (RangeStartEnd != std::string_view::npos)
{
- size_t RangeStartEnd = Value.find('-', 6);
- if (RangeStartEnd != std::string_view::npos)
+ const std::optional<uint64_t> Start = ParseInt<uint64_t>(Value.substr(6, RangeStartEnd - 6));
+ if (Start)
{
- const std::optional<uint64_t> Start =
- ParseInt<uint64_t>(Value.substr(6, RangeStartEnd - 6));
- if (Start)
+ uint64_t DownloadedSize =
+ *Data->PayloadFile ? (*Data->PayloadFile)->GetSize() : Data->PayloadString->length();
+ if (Start.value() == DownloadedSize)
{
- uint64_t DownloadedSize = *Data->PayloadFile ? (*Data->PayloadFile)->GetSize()
- : Data->PayloadString->length();
- if (Start.value() == DownloadedSize)
- {
- Data->Headers->emplace_back(std::string(Key), std::string(Value));
- return TotalBytes;
- }
- else if (Start.value() > DownloadedSize)
- {
- return 0;
- }
- if (*Data->PayloadFile)
- {
- (*Data->PayloadFile)->ResetWritePos(Start.value());
- }
- else
- {
- *Data->PayloadString = Data->PayloadString->substr(0, Start.value());
- }
Data->Headers->emplace_back(std::string(Key), std::string(Value));
return TotalBytes;
}
+ else if (Start.value() > DownloadedSize)
+ {
+ return 0;
+ }
+ if (*Data->PayloadFile)
+ {
+ (*Data->PayloadFile)->ResetWritePos(Start.value());
+ }
+ else
+ {
+ *Data->PayloadString = Data->PayloadString->substr(0, Start.value());
+ }
+ Data->Headers->emplace_back(std::string(Key), std::string(Value));
+ return TotalBytes;
}
}
- return 0;
}
-
- Data->Headers->emplace_back(std::string(Key), std::string(Value));
+ return 0;
}
+ Data->Headers->emplace_back(std::string(Key), std::string(Value));
return TotalBytes;
};
@@ -2064,8 +1792,8 @@ CurlHttpClient::Download(std::string_view Url, const std::filesystem::path& Temp
Res = ResumeSess.Perform();
Res.Headers = std::move(ResumeHeaders);
- curl_slist_free_all(ResumeHdrList);
- } while (ShouldResumeCheck(Res));
+ ResumeAttempt++;
+ } while (ResumeAttempt < m_ConnectionSettings.RetryCount && ShouldResumeCheck(Res));
}
}
}
@@ -2075,8 +1803,6 @@ CurlHttpClient::Download(std::string_view Url, const std::filesystem::path& Temp
Res.Body = std::move(PayloadString);
}
- curl_slist_free_all(DlHeaders);
-
return Res;
},
PayloadFile);