aboutsummaryrefslogtreecommitdiff
path: root/src/zenhttp/include
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/include
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/include')
-rw-r--r--src/zenhttp/include/zenhttp/formatters.h2
-rw-r--r--src/zenhttp/include/zenhttp/httpclient.h74
-rw-r--r--src/zenhttp/include/zenhttp/httpserver.h15
-rw-r--r--src/zenhttp/include/zenhttp/httpwsclient.h4
4 files changed, 84 insertions, 11 deletions
diff --git a/src/zenhttp/include/zenhttp/formatters.h b/src/zenhttp/include/zenhttp/formatters.h
index 57ab01158..90180391c 100644
--- a/src/zenhttp/include/zenhttp/formatters.h
+++ b/src/zenhttp/include/zenhttp/formatters.h
@@ -84,7 +84,7 @@ struct fmt::formatter<zen::HttpClient::Response>
return fmt::format_to(Ctx.out(),
"Failed: Elapsed: {}, Reason: ({}) '{}",
NiceResponseTime,
- Response.Error.value().ErrorCode,
+ static_cast<int>(Response.Error.value().ErrorCode),
Response.Error.value().ErrorMessage);
}
else
diff --git a/src/zenhttp/include/zenhttp/httpclient.h b/src/zenhttp/include/zenhttp/httpclient.h
index 1bb36a298..2e21e3bd6 100644
--- a/src/zenhttp/include/zenhttp/httpclient.h
+++ b/src/zenhttp/include/zenhttp/httpclient.h
@@ -30,6 +30,34 @@ class CompositeBuffer;
*/
+enum class HttpClientErrorCode : int
+{
+ kOK = 0,
+ kConnectionFailure,
+ kHostResolutionFailure,
+ kProxyResolutionFailure,
+ kInternalError,
+ kNetworkReceiveError,
+ kNetworkSendFailure,
+ kOperationTimedOut,
+ kSSLConnectError,
+ kSSLCertificateError,
+ kSSLCACertError,
+ kGenericSSLError,
+ kRequestCancelled,
+ kOtherError,
+};
+
+enum class HttpClientBackend : uint8_t
+{
+ kDefault,
+ kCpr,
+ kCurl,
+};
+
+void SetDefaultHttpClientBackend(std::string_view Backend);
+void SetDefaultHttpClientBackend(HttpClientBackend Backend);
+
struct HttpClientAccessToken
{
using Clock = std::chrono::system_clock;
@@ -59,6 +87,22 @@ struct HttpClientSettings
Oid SessionId = Oid::Zero;
bool Verbose = false;
uint64_t MaximumInMemoryDownloadSize = 1024u * 1024u;
+ HttpClientBackend Backend = HttpClientBackend::kDefault;
+
+ /// Unix domain socket path. When non-empty, the client connects via this
+ /// socket instead of TCP. BaseUri is still used for the Host header and URL.
+ std::string UnixSocketPath;
+
+ /// Disable HTTP keep-alive by closing the connection after each request.
+ /// Useful for testing per-connection overhead.
+ bool ForbidReuseConnection = false;
+
+ /// Skip TLS certificate verification (for testing with self-signed certs).
+ bool InsecureSsl = false;
+
+ /// CA certificate bundle path for TLS verification. When non-empty, overrides
+ /// the system default CA store.
+ std::string CaBundlePath;
/// HTTP status codes that are expected and should not be logged as warnings.
/// 404 is always treated as expected regardless of this list.
@@ -70,22 +114,22 @@ class HttpClientError : public std::runtime_error
public:
using _Mybase = runtime_error;
- HttpClientError(const std::string& Message, int Error, HttpResponseCode ResponseCode)
+ HttpClientError(const std::string& Message, HttpClientErrorCode Error, HttpResponseCode ResponseCode)
: _Mybase(Message)
, m_Error(Error)
, m_ResponseCode(ResponseCode)
{
}
- HttpClientError(const char* Message, int Error, HttpResponseCode ResponseCode)
+ HttpClientError(const char* Message, HttpClientErrorCode Error, HttpResponseCode ResponseCode)
: _Mybase(Message)
, m_Error(Error)
, m_ResponseCode(ResponseCode)
{
}
- inline int GetInternalErrorCode() const { return m_Error; }
- inline HttpResponseCode GetHttpResponseCode() const { return m_ResponseCode; }
+ inline HttpClientErrorCode GetInternalErrorCode() const { return m_Error; }
+ inline HttpResponseCode GetHttpResponseCode() const { return m_ResponseCode; }
enum class ResponseClass : std::int8_t
{
@@ -112,8 +156,8 @@ public:
ResponseClass GetResponseClass() const;
private:
- const int m_Error = 0;
- const HttpResponseCode m_ResponseCode = HttpResponseCode::ImATeapot;
+ const HttpClientErrorCode m_Error = HttpClientErrorCode::kOK;
+ const HttpResponseCode m_ResponseCode = HttpResponseCode::ImATeapot;
};
class HttpClientBase;
@@ -137,11 +181,23 @@ public:
struct ErrorContext
{
- int ErrorCode = 0;
- std::string ErrorMessage;
+ HttpClientErrorCode ErrorCode;
+ std::string ErrorMessage;
/** True when the error is a transport-level connection failure (connect timeout, refused, DNS) */
- bool IsConnectionError() const;
+ bool IsConnectionError() const
+ {
+ switch (ErrorCode)
+ {
+ case HttpClientErrorCode::kConnectionFailure:
+ case HttpClientErrorCode::kOperationTimedOut:
+ case HttpClientErrorCode::kHostResolutionFailure:
+ case HttpClientErrorCode::kProxyResolutionFailure:
+ return true;
+ default:
+ return false;
+ }
+ }
};
struct KeyValueMap
diff --git a/src/zenhttp/include/zenhttp/httpserver.h b/src/zenhttp/include/zenhttp/httpserver.h
index 0e1714669..d98877d16 100644
--- a/src/zenhttp/include/zenhttp/httpserver.h
+++ b/src/zenhttp/include/zenhttp/httpserver.h
@@ -255,6 +255,9 @@ public:
*/
std::string_view GetExternalHost() const { return m_ExternalHost; }
+ /** Returns the effective HTTPS port, or 0 if HTTPS is not enabled. Only valid after Initialize(). */
+ int GetEffectiveHttpsPort() const { return m_EffectiveHttpsPort; }
+
/** Returns total bytes received and sent across all connections since server start. */
virtual uint64_t GetTotalBytesReceived() const { return 0; }
virtual uint64_t GetTotalBytesSent() const { return 0; }
@@ -290,7 +293,8 @@ public:
private:
std::vector<HttpService*> m_KnownServices;
- int m_EffectivePort = 0;
+ int m_EffectivePort = 0;
+ int m_EffectiveHttpsPort = 0;
std::string m_ExternalHost;
metrics::Meter m_RequestMeter;
std::string m_DefaultRedirect;
@@ -308,6 +312,7 @@ private:
virtual void OnClose() = 0;
protected:
+ void SetEffectiveHttpsPort(int Port) { m_EffectiveHttpsPort = Port; }
virtual std::string OnGetExternalHost() const;
};
@@ -324,12 +329,20 @@ struct HttpServerConfig
std::vector<HttpServerPluginConfig> PluginConfigs;
bool ForceLoopback = false;
unsigned int ThreadCount = 0;
+ std::string UnixSocketPath; // Unix domain socket path (empty = disabled, non-Windows only)
+ int HttpsPort = 0; // HTTPS listen port (0 = disabled, ASIO backend)
+ std::string CertFile; // PEM certificate chain file path
+ std::string KeyFile; // PEM private key file path
struct
{
unsigned int AsyncWorkThreadCount = 0;
bool IsAsyncResponseEnabled = true;
bool IsRequestLoggingEnabled = false;
+ int HttpsPort = 0; // 0 = HTTPS disabled
+ std::string CertThumbprint; // Hex SHA-1 (40 chars) for auto SSL binding
+ std::string CertStoreName = "MY"; // Windows certificate store name
+ bool HttpsOnly = false; // When true, disable HTTP listener
} HttpSys;
};
diff --git a/src/zenhttp/include/zenhttp/httpwsclient.h b/src/zenhttp/include/zenhttp/httpwsclient.h
index 926ec1e3d..34d338b1d 100644
--- a/src/zenhttp/include/zenhttp/httpwsclient.h
+++ b/src/zenhttp/include/zenhttp/httpwsclient.h
@@ -43,6 +43,10 @@ struct HttpWsClientSettings
std::string LogCategory = "wsclient";
std::chrono::milliseconds ConnectTimeout{5000};
std::optional<std::function<HttpClientAccessToken()>> AccessTokenProvider;
+
+ /// Unix domain socket path. When non-empty, connects via this socket
+ /// instead of TCP. The URL host is still used for the Host header.
+ std::string UnixSocketPath;
};
/**