diff options
| author | Stefan Boberg <[email protected]> | 2026-03-10 17:27:26 +0100 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2026-03-10 17:27:26 +0100 |
| commit | d0a07e555577dcd4a8f55f1b45d9e8e4e6366ab7 (patch) | |
| tree | 2dfe1e3e0b620043d358e0b7f8bdf8320d985491 /src/zenhttp/include | |
| parent | changelog entry which was inadvertently omitted from PR merge (diff) | |
| download | zen-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.h | 2 | ||||
| -rw-r--r-- | src/zenhttp/include/zenhttp/httpclient.h | 74 | ||||
| -rw-r--r-- | src/zenhttp/include/zenhttp/httpserver.h | 15 | ||||
| -rw-r--r-- | src/zenhttp/include/zenhttp/httpwsclient.h | 4 |
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; }; /** |