From d0a07e555577dcd4a8f55f1b45d9e8e4e6366ab7 Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Tue, 10 Mar 2026 17:27:26 +0100 Subject: 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` and `WsAsioConnectionT` 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 --- src/zenhttp/servers/wsasio.cpp | 64 ++++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 18 deletions(-) (limited to 'src/zenhttp/servers/wsasio.cpp') diff --git a/src/zenhttp/servers/wsasio.cpp b/src/zenhttp/servers/wsasio.cpp index b2543277a..5ae48f5b3 100644 --- a/src/zenhttp/servers/wsasio.cpp +++ b/src/zenhttp/servers/wsasio.cpp @@ -1,6 +1,7 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "wsasio.h" +#include "asio_socket_traits.h" #include "wsframecodec.h" #include @@ -17,14 +18,16 @@ WsLog() ////////////////////////////////////////////////////////////////////////// -WsAsioConnection::WsAsioConnection(std::unique_ptr Socket, IWebSocketHandler& Handler, HttpServer* Server) +template +WsAsioConnectionT::WsAsioConnectionT(std::unique_ptr Socket, IWebSocketHandler& Handler, HttpServer* Server) : m_Socket(std::move(Socket)) , m_Handler(Handler) , m_HttpServer(Server) { } -WsAsioConnection::~WsAsioConnection() +template +WsAsioConnectionT::~WsAsioConnectionT() { m_IsOpen.store(false); if (m_HttpServer) @@ -33,14 +36,16 @@ WsAsioConnection::~WsAsioConnection() } } +template void -WsAsioConnection::Start() +WsAsioConnectionT::Start() { EnqueueRead(); } +template bool -WsAsioConnection::IsOpen() const +WsAsioConnectionT::IsOpen() const { return m_IsOpen.load(std::memory_order_relaxed); } @@ -50,23 +55,25 @@ WsAsioConnection::IsOpen() const // Read loop // +template void -WsAsioConnection::EnqueueRead() +WsAsioConnectionT::EnqueueRead() { if (!m_IsOpen.load(std::memory_order_relaxed)) { return; } - Ref Self(this); + Ref Self(this); asio::async_read(*m_Socket, m_ReadBuffer, asio::transfer_at_least(1), [Self](const asio::error_code& Ec, std::size_t ByteCount) { Self->OnDataReceived(Ec, ByteCount); }); } +template void -WsAsioConnection::OnDataReceived(const asio::error_code& Ec, [[maybe_unused]] std::size_t ByteCount) +WsAsioConnectionT::OnDataReceived(const asio::error_code& Ec, [[maybe_unused]] std::size_t ByteCount) { if (Ec) { @@ -90,8 +97,9 @@ WsAsioConnection::OnDataReceived(const asio::error_code& Ec, [[maybe_unused]] st } } +template void -WsAsioConnection::ProcessReceivedData() +WsAsioConnectionT::ProcessReceivedData() { while (m_ReadBuffer.size() > 0) { @@ -162,8 +170,8 @@ WsAsioConnection::ProcessReceivedData() // Shut down the socket std::error_code ShutdownEc; - m_Socket->shutdown(asio::socket_base::shutdown_both, ShutdownEc); - m_Socket->close(ShutdownEc); + SocketTraits::ShutdownBoth(*m_Socket, ShutdownEc); + SocketTraits::Close(*m_Socket, ShutdownEc); return; } @@ -179,8 +187,9 @@ WsAsioConnection::ProcessReceivedData() // Write queue // +template void -WsAsioConnection::SendText(std::string_view Text) +WsAsioConnectionT::SendText(std::string_view Text) { if (!m_IsOpen.load(std::memory_order_relaxed)) { @@ -192,8 +201,9 @@ WsAsioConnection::SendText(std::string_view Text) EnqueueWrite(std::move(Frame)); } +template void -WsAsioConnection::SendBinary(std::span Data) +WsAsioConnectionT::SendBinary(std::span Data) { if (!m_IsOpen.load(std::memory_order_relaxed)) { @@ -204,14 +214,16 @@ WsAsioConnection::SendBinary(std::span Data) EnqueueWrite(std::move(Frame)); } +template void -WsAsioConnection::Close(uint16_t Code, std::string_view Reason) +WsAsioConnectionT::Close(uint16_t Code, std::string_view Reason) { DoClose(Code, Reason); } +template void -WsAsioConnection::DoClose(uint16_t Code, std::string_view Reason) +WsAsioConnectionT::DoClose(uint16_t Code, std::string_view Reason) { if (!m_IsOpen.exchange(false)) { @@ -227,8 +239,9 @@ WsAsioConnection::DoClose(uint16_t Code, std::string_view Reason) m_Handler.OnWebSocketClose(*this, Code, Reason); } +template void -WsAsioConnection::EnqueueWrite(std::vector Frame) +WsAsioConnectionT::EnqueueWrite(std::vector Frame) { if (m_HttpServer) { @@ -252,8 +265,9 @@ WsAsioConnection::EnqueueWrite(std::vector Frame) } } +template void -WsAsioConnection::FlushWriteQueue() +WsAsioConnectionT::FlushWriteQueue() { std::vector Frame; @@ -272,7 +286,7 @@ WsAsioConnection::FlushWriteQueue() return; } - Ref Self(this); + Ref Self(this); // Move Frame into a shared_ptr so we can create the buffer and capture ownership // in the same async_write call without evaluation order issues. @@ -283,8 +297,9 @@ WsAsioConnection::FlushWriteQueue() [Self, OwnedFrame](const asio::error_code& Ec, std::size_t ByteCount) { Self->OnWriteComplete(Ec, ByteCount); }); } +template void -WsAsioConnection::OnWriteComplete(const asio::error_code& Ec, [[maybe_unused]] std::size_t ByteCount) +WsAsioConnectionT::OnWriteComplete(const asio::error_code& Ec, [[maybe_unused]] std::size_t ByteCount) { if (Ec) { @@ -308,4 +323,17 @@ WsAsioConnection::OnWriteComplete(const asio::error_code& Ec, [[maybe_unused]] s FlushWriteQueue(); } +////////////////////////////////////////////////////////////////////////// +// Explicit template instantiations + +template class WsAsioConnectionT; + +#if defined(ASIO_HAS_LOCAL_SOCKETS) +template class WsAsioConnectionT; +#endif + +#if ZEN_USE_OPENSSL +template class WsAsioConnectionT>; +#endif + } // namespace zen::asio_http -- cgit v1.2.3