aboutsummaryrefslogtreecommitdiff
path: root/src/zenhttp/httpclient.cpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-03-10 17:27:26 +0100
committerGitHub Enterprise <[email protected]>2026-03-10 17:27:26 +0100
commitd0a07e555577dcd4a8f55f1b45d9e8e4e6366ab7 (patch)
tree2dfe1e3e0b620043d358e0b7f8bdf8320d985491 /src/zenhttp/httpclient.cpp
parentchangelog entry which was inadvertently omitted from PR merge (diff)
downloadzen-d0a07e555577dcd4a8f55f1b45d9e8e4e6366ab7.tar.xz
zen-d0a07e555577dcd4a8f55f1b45d9e8e4e6366ab7.zip
HttpClient using libcurl, Unix Sockets for HTTP. HTTPS support (#770)
The main goal of this change is to eliminate the cpr back-end altogether and replace it with the curl implementation. I would expect to drop cpr as soon as we feel happy with the libcurl back-end. That would leave us with a direct dependency on libcurl only, and cpr can be eliminated as a dependency. ### HttpClient Backend Overhaul - Implemented a new **libcurl-based HttpClient** backend (`httpclientcurl.cpp`, ~2000 lines) as an alternative to the cpr-based one - Made HttpClient backend **configurable at runtime** via constructor arguments and `-httpclient=...` CLI option (for zen, zenserver, and tests) - Extended HttpClient test suite to cover multipart/content-range scenarios ### Unix Domain Socket Support - Added Unix domain socket support to **httpasio** (server side) - Added Unix domain socket support to **HttpClient** - Added Unix domain socket support to **HttpWsClient** (WebSocket client) - Templatized `HttpServerConnectionT<SocketType>` and `WsAsioConnectionT<SocketType>` to handle TCP, Unix, and SSL sockets uniformly via `if constexpr` dispatch ### HTTPS Support - Added **preliminary HTTPS support to httpasio** (for Mac/Linux via OpenSSL) - Added **basic HTTPS support for http.sys** (Windows) - Implemented HTTPS test for httpasio - Split `InitializeServer` into smaller sub-functions for http.sys ### Other Notable Changes - Improved **zenhttp-test stability** with dynamic port allocation - Enhanced port retry logic in http.sys (handles ERROR_ACCESS_DENIED) - Fatal signal/exception handlers for backtrace generation in tests - Added `zen bench http` subcommand to exercise network + HTTP client/server communication stack
Diffstat (limited to 'src/zenhttp/httpclient.cpp')
-rw-r--r--src/zenhttp/httpclient.cpp121
1 files changed, 117 insertions, 4 deletions
diff --git a/src/zenhttp/httpclient.cpp b/src/zenhttp/httpclient.cpp
index 281d512cf..9baf4346e 100644
--- a/src/zenhttp/httpclient.cpp
+++ b/src/zenhttp/httpclient.cpp
@@ -40,6 +40,35 @@ extern HttpClientBase* CreateCprHttpClient(std::string_view BaseUri,
const HttpClientSettings& ConnectionSettings,
std::function<bool()>&& CheckIfAbortFunction);
+extern HttpClientBase* CreateCurlHttpClient(std::string_view BaseUri,
+ const HttpClientSettings& ConnectionSettings,
+ std::function<bool()>&& CheckIfAbortFunction);
+
+static HttpClientBackend g_DefaultHttpClientBackend = HttpClientBackend::kCpr;
+
+void
+SetDefaultHttpClientBackend(HttpClientBackend Backend)
+{
+ g_DefaultHttpClientBackend = Backend;
+}
+
+void
+SetDefaultHttpClientBackend(std::string_view Backend)
+{
+ if (Backend == "cpr")
+ {
+ g_DefaultHttpClientBackend = HttpClientBackend::kCpr;
+ }
+ else if (Backend == "curl")
+ {
+ g_DefaultHttpClientBackend = HttpClientBackend::kCurl;
+ }
+ else
+ {
+ g_DefaultHttpClientBackend = HttpClientBackend::kDefault;
+ }
+}
+
using namespace std::literals;
//////////////////////////////////////////////////////////////////////////
@@ -104,6 +133,71 @@ HttpClientBase::GetAccessToken()
//////////////////////////////////////////////////////////////////////////
+HttpClientError::ResponseClass
+HttpClientError::GetResponseClass() const
+{
+ if (m_Error != HttpClientErrorCode::kOK)
+ {
+ switch (m_Error)
+ {
+ case HttpClientErrorCode::kConnectionFailure:
+ return ResponseClass::kHttpCantConnectError;
+ case HttpClientErrorCode::kHostResolutionFailure:
+ case HttpClientErrorCode::kProxyResolutionFailure:
+ return ResponseClass::kHttpNoHost;
+ case HttpClientErrorCode::kInternalError:
+ case HttpClientErrorCode::kNetworkReceiveError:
+ case HttpClientErrorCode::kNetworkSendFailure:
+ case HttpClientErrorCode::kOperationTimedOut:
+ return ResponseClass::kHttpTimeout;
+ case HttpClientErrorCode::kSSLConnectError:
+ case HttpClientErrorCode::kSSLCertificateError:
+ case HttpClientErrorCode::kSSLCACertError:
+ case HttpClientErrorCode::kGenericSSLError:
+ return ResponseClass::kHttpSLLError;
+ default:
+ return ResponseClass::kHttpOtherClientError;
+ }
+ }
+ else if (IsHttpSuccessCode(m_ResponseCode))
+ {
+ return ResponseClass::kSuccess;
+ }
+ else
+ {
+ switch (m_ResponseCode)
+ {
+ case HttpResponseCode::Unauthorized:
+ return ResponseClass::kHttpUnauthorized;
+ case HttpResponseCode::NotFound:
+ return ResponseClass::kHttpNotFound;
+ case HttpResponseCode::Forbidden:
+ return ResponseClass::kHttpForbidden;
+ case HttpResponseCode::Conflict:
+ return ResponseClass::kHttpConflict;
+ case HttpResponseCode::InternalServerError:
+ return ResponseClass::kHttpInternalServerError;
+ case HttpResponseCode::ServiceUnavailable:
+ return ResponseClass::kHttpServiceUnavailable;
+ case HttpResponseCode::BadGateway:
+ return ResponseClass::kHttpBadGateway;
+ case HttpResponseCode::GatewayTimeout:
+ return ResponseClass::kHttpGatewayTimeout;
+ default:
+ if (m_ResponseCode >= HttpResponseCode::InternalServerError)
+ {
+ return ResponseClass::kHttpOtherServerError;
+ }
+ else
+ {
+ return ResponseClass::kHttpOtherClientError;
+ }
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+
std::vector<std::pair<uint64_t, uint64_t>>
HttpClient::Response::GetRanges(std::span<const std::pair<uint64_t, uint64_t>> OffsetAndLengthPairs) const
{
@@ -222,7 +316,11 @@ HttpClient::Response::ErrorMessage(std::string_view Prefix) const
{
if (Error.has_value())
{
- return fmt::format("{}{}HTTP error ({}) '{}'", Prefix, Prefix.empty() ? ""sv : ": "sv, Error->ErrorCode, Error->ErrorMessage);
+ return fmt::format("{}{}HTTP error ({}) '{}'",
+ Prefix,
+ Prefix.empty() ? ""sv : ": "sv,
+ static_cast<int>(Error->ErrorCode),
+ Error->ErrorMessage);
}
else if (StatusCode != HttpResponseCode::ImATeapot && (int)StatusCode)
{
@@ -245,19 +343,34 @@ HttpClient::Response::ThrowError(std::string_view ErrorPrefix)
{
if (!IsSuccess())
{
- throw HttpClientError(ErrorMessage(ErrorPrefix), Error.has_value() ? Error.value().ErrorCode : 0, StatusCode);
+ throw HttpClientError(ErrorMessage(ErrorPrefix),
+ Error.has_value() ? Error.value().ErrorCode : HttpClientErrorCode::kOK,
+ StatusCode);
}
}
//////////////////////////////////////////////////////////////////////////
HttpClient::HttpClient(std::string_view BaseUri, const HttpClientSettings& ConnectionSettings, std::function<bool()>&& CheckIfAbortFunction)
-: m_BaseUri(BaseUri)
+: m_Log(zen::logging::Get(ConnectionSettings.LogCategory))
+, m_BaseUri(BaseUri)
, m_ConnectionSettings(ConnectionSettings)
{
m_SessionId = GetSessionIdString();
- m_Inner = CreateCprHttpClient(BaseUri, ConnectionSettings, std::move(CheckIfAbortFunction));
+ HttpClientBackend EffectiveBackend =
+ ConnectionSettings.Backend != HttpClientBackend::kDefault ? ConnectionSettings.Backend : g_DefaultHttpClientBackend;
+
+ switch (EffectiveBackend)
+ {
+ case HttpClientBackend::kCurl:
+ m_Inner = CreateCurlHttpClient(BaseUri, ConnectionSettings, std::move(CheckIfAbortFunction));
+ break;
+ case HttpClientBackend::kCpr:
+ default:
+ m_Inner = CreateCprHttpClient(BaseUri, ConnectionSettings, std::move(CheckIfAbortFunction));
+ break;
+ }
}
HttpClient::~HttpClient()