diff options
| author | Stefan Boberg <[email protected]> | 2026-03-13 22:31:57 +0100 |
|---|---|---|
| committer | GitHub Enterprise <[email protected]> | 2026-03-13 22:31:57 +0100 |
| commit | eee6a326529e2b84b50f5329e5ce563a69bb22e7 (patch) | |
| tree | f1b6737e70fa6fc40eb39efbf14fd19f52d089bf | |
| parent | Merge branch 'main' into sb/compute-scheduler (diff) | |
| parent | Made CPR optional, html generated at build time (#840) (diff) | |
| download | zen-sb/compute-scheduler.tar.xz zen-sb/compute-scheduler.zip | |
Merge branch 'main' into sb/compute-schedulersb/compute-scheduler
47 files changed, 1202 insertions, 1030 deletions
diff --git a/.gitignore b/.gitignore index 0aa028930..5c9195566 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .DS_Store .claude/settings.local.json .profile/ +.xwin-cache/ # User-specific files *.suo @@ -112,3 +113,6 @@ CMake* # Ue tool chain temp directory .tmp-ue-toolchain/ + +# Generated frontend zip (built automatically by xmake) +src/zenserver/frontend/html.zip @@ -173,7 +173,7 @@ The codebase is organized into layered modules with clear dependencies: - Web UI bundled as ZIP in `src/zenserver/frontend/*.zip` - Dashboards for hub, orchestrator, and compute services are located in `src/zenserver/frontent/html/` - These are the files which end up being bundled into the front-end zip mentioned above -- Update with `xmake updatefrontend` after modifying HTML/JS, and check in the resulting zip +- The zip is generated automatically at build time when source files change **Memory Management:** - Can use mimalloc or rpmalloc for performance @@ -309,6 +309,4 @@ When debugging zenserver-test or other multi-process scenarios, use child proces # Create deployable ZIP bundle xmake bundle -# Update frontend ZIP after HTML changes -xmake updatefrontend ``` diff --git a/scripts/updatefrontend.lua b/scripts/updatefrontend.lua deleted file mode 100644 index ab37819d7..000000000 --- a/scripts/updatefrontend.lua +++ /dev/null @@ -1,111 +0,0 @@ --- Copyright Epic Games, Inc. All Rights Reserved. - --------------------------------------------------------------------------------- -local function _exec(cmd, ...) - local args = {} - for _, arg in pairs({...}) do - if arg then - table.insert(args, arg) - end - end - - print("--", cmd, table.unpack(args)) - local ret = os.execv(cmd, args) - print() - return ret -end - --------------------------------------------------------------------------------- -local function _zip(store_only, zip_path, ...) - -- Here's the rules; if len(...) is 1 and it is a dir then create a zip with - -- archive paths like this; - -- - -- glob(foo/bar/**) -> foo/bar/abc, foo/bar/dir/123 -> zip(abc, dir/123) - -- - -- Otherwise assume ... is file paths and add without leading directories; - -- - -- foo/abc, bar/123 -> zip(abc, 123) - - zip_path = path.absolute(zip_path) - os.tryrm(zip_path) - - local inputs = {...} - - local source_dir = nil - if #inputs == 1 and os.isdir(inputs[1]) then - source_dir = inputs[1] - end - - import("detect.tools.find_7z") - local cmd_7z = find_7z() - if cmd_7z then - input_paths = {} - if source_dir then - -- Suffixing a directory path with a "/." will have 7z set the path - -- for archived files relative to that directory. - input_paths = { path.join(source_dir, ".") } - else - for _, input_path in pairs(inputs) do - -- If there is a "/./" anywhere in file paths then 7z drops all - -- directory information and just archives the file by name - input_path = path.relative(input_path, ".") - if input_path:sub(2,2) ~= ":" then - input_path = "./"..input_path - end - table.insert(input_paths, input_path) - end - end - - compression_level = "-mx1" - if store_only then - compression_level = "-mx0" - end - - local ret = _exec(cmd_7z, "a", compression_level, zip_path, table.unpack(input_paths)) - if ret > 0 then - raise("Received error from 7z") - end - return - end - - print("7z not found, falling back to zip") - - import("detect.tools.find_zip") - zip_cmd = find_zip() - if zip_cmd then - local input_paths = inputs - local cwd = os.curdir() - if source_dir then - os.cd(source_dir) - input_paths = { "." } - end - - compression_level = "-1" - if store_only then - compression_level = "-0" - end - - local strip_leading_path = nil - if not source_dir then - strip_leading_path = "--junk-paths" - end - - local ret = _exec(zip_cmd, "-r", compression_level, strip_leading_path, zip_path, table.unpack(input_paths)) - if ret > 0 then - raise("Received error from zip") - end - - os.cd(cwd) - return - end - print("zip not found") - - raise("Unable to find a suitable zip tool") -end - --------------------------------------------------------------------------------- -function main() - local zip_path = "src/zenserver/frontend/html.zip" - local content_dir = "src/zenserver/frontend/html/" - _zip(true, zip_path, content_dir) -end diff --git a/src/transports/winsock/winsock.cpp b/src/transports/winsock/winsock.cpp index f98984726..c1f4f6abe 100644 --- a/src/transports/winsock/winsock.cpp +++ b/src/transports/winsock/winsock.cpp @@ -271,7 +271,7 @@ WinsockTransportPlugin::Initialize(TransportServer* ServerInterface) m_ServerInterface = ServerInterface; m_ListenSocket = socket(AF_INET6, SOCK_STREAM, 0); - if (m_ListenSocket == SOCKET_ERROR || m_ListenSocket == INVALID_SOCKET) + if (m_ListenSocket == INVALID_SOCKET) { throw std::system_error(std::error_code(WSAGetLastError(), std::system_category()), "socket creation failed in HTTP plugin server init"); @@ -302,7 +302,7 @@ WinsockTransportPlugin::Initialize(TransportServer* ServerInterface) do { - if (SOCKET ClientSocket = accept(m_ListenSocket, NULL, NULL); ClientSocket != SOCKET_ERROR) + if (SOCKET ClientSocket = accept(m_ListenSocket, NULL, NULL); ClientSocket != INVALID_SOCKET) { int Flag = 1; setsockopt(ClientSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&Flag, sizeof(Flag)); diff --git a/src/zen/cmds/admin_cmd.cpp b/src/zen/cmds/admin_cmd.cpp index 15e854796..034d430fd 100644 --- a/src/zen/cmds/admin_cmd.cpp +++ b/src/zen/cmds/admin_cmd.cpp @@ -17,7 +17,7 @@ namespace zen { ScrubCommand::ScrubCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "n", "dry", "Dry run (do not delete any data)", cxxopts::value(m_DryRun), "<bool>"); m_Options.add_option("", "", "no-gc", "Do not perform GC after scrub pass", cxxopts::value(m_NoGc), "<bool>"); m_Options.add_option("", "", "no-cas", "Do not scrub CAS stores", cxxopts::value(m_NoCas), "<bool>"); @@ -48,7 +48,7 @@ ScrubCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); HttpClient::KeyValueMap Params{{"skipdelete", ToString(m_DryRun)}, {"skipgc", ToString(m_NoGc)}, @@ -70,7 +70,7 @@ ScrubCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) GcCommand::GcCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "s", "smallobjects", @@ -258,7 +258,7 @@ GcCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) } Params.Entries.insert({"enablevalidation", m_EnableValidation ? "true" : "false"}); - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Post("/admin/gc"sv, HttpClient::Accept(HttpContentType::kJSON), Params)) { ZEN_CONSOLE("OK: {}", Response.ToText()); @@ -272,7 +272,7 @@ GcCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) GcStatusCommand::GcStatusCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "d", "details", "Show detailed GC report", cxxopts::value(m_Details)->default_value("false"), "<details>"); } @@ -297,7 +297,7 @@ GcStatusCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Get("/admin/gc"sv, HttpClient::Accept(HttpContentType::kJSON))) { ZEN_CONSOLE("OK: {}", Response.ToText()); @@ -311,7 +311,7 @@ GcStatusCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) GcStopCommand::GcStopCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); } GcStopCommand::~GcStopCommand() @@ -335,7 +335,7 @@ GcStopCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Post("/admin/gc-stop"sv, HttpClient::Accept(HttpContentType::kJSON))) { if (Response.StatusCode == HttpResponseCode::Accepted) @@ -358,7 +358,7 @@ GcStopCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) JobCommand::JobCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "j", "jobid", "Job id", cxxopts::value(m_JobId), "<jobid>"); m_Options.add_option("", "c", "cancel", "Cancel job id", cxxopts::value(m_Cancel), "<cancel>"); } @@ -384,7 +384,7 @@ JobCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (m_Cancel) { @@ -421,7 +421,7 @@ JobCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) LoggingCommand::LoggingCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "", "cache-write-log", "Enable cache write logging", cxxopts::value(m_CacheWriteLog), "<enable/disable>"); m_Options.add_option("", "", "cache-access-log", "Enable cache access logging", cxxopts::value(m_CacheAccessLog), "<enable/disable>"); m_Options @@ -467,7 +467,7 @@ LoggingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); HttpClient::KeyValueMap Parameters; @@ -564,7 +564,7 @@ LoggingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) FlushCommand::FlushCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); } FlushCommand::~FlushCommand() = default; @@ -586,7 +586,7 @@ FlushCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - zen::HttpClient Http(m_HostName); + zen::HttpClient Http = CreateHttpClient(m_HostName); if (zen::HttpClient::Response Response = Http.Post("/admin/flush"sv)) { diff --git a/src/zen/cmds/cache_cmd.cpp b/src/zen/cmds/cache_cmd.cpp index 2aa142973..a8c15f119 100644 --- a/src/zen/cmds/cache_cmd.cpp +++ b/src/zen/cmds/cache_cmd.cpp @@ -59,7 +59,7 @@ namespace { DropCommand::DropCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "n", "namespace", "Namespace name", cxxopts::value(m_NamespaceName), "<namespacename>"); m_Options.add_option("", "b", "bucket", "Bucket name", cxxopts::value(m_BucketName), "<bucketname>"); m_Options.parse_positional({"namespace", "bucket"}); @@ -105,7 +105,7 @@ DropCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) ZEN_CONSOLE("Dropping {}", DropDescription); - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Delete(Url)) { ZEN_CONSOLE("{}", Response.ToText()); @@ -119,7 +119,7 @@ DropCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) CacheInfoCommand::CacheInfoCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "n", "namespace", "Namespace name", cxxopts::value(m_NamespaceName), "<namespacename>"); m_Options.add_option("", "", @@ -196,7 +196,7 @@ CacheInfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) Parameters.Entries.insert({"bucketsize", "true"}); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Get(Url, HttpClient::Accept(ZenContentType::kJSON), Parameters)) { ZEN_CONSOLE("{}", Response.ToText()); @@ -210,7 +210,7 @@ CacheInfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) CacheStatsCommand::CacheStatsCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); } CacheStatsCommand::~CacheStatsCommand() = default; @@ -232,7 +232,7 @@ CacheStatsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Get("/stats/z$", HttpClient::Accept(ZenContentType::kJSON))) { ZEN_CONSOLE("{}", Response.ToText()); @@ -246,7 +246,7 @@ CacheStatsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv CacheDetailsCommand::CacheDetailsCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "c", "csv", "Info on csv format", cxxopts::value(m_CSV), "<csv>"); m_Options.add_option("", "d", "details", "Get detailed information about records", cxxopts::value(m_Details), "<details>"); m_Options.add_option("", @@ -329,7 +329,7 @@ CacheDetailsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** ar Url = "/z$/details$"; } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Get(Url, Headers, Parameters)) { ZEN_CONSOLE("{}", Response.ToText()); @@ -343,7 +343,7 @@ CacheDetailsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** ar CacheGenerateCommand::CacheGenerateCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options .add_option("", "n", "namespace", "Namespace to generate cache values/records for", cxxopts::value(m_Namespace), "<namespace>"); m_Options.add_option("", "b", "bucket", "Bucket name to generate cache values/records for", cxxopts::value(m_Bucket), "<bucket>"); @@ -431,7 +431,7 @@ CacheGenerateCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a std::uniform_int_distribution<uint64_t> KeyDistribution; - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); auto GeneratePutCacheValueRequest( [this, &KeyDistribution, &Generator](std::span<std::uint64_t> BatchSizes, uint64_t RequestIndex) -> CbPackage { @@ -586,7 +586,7 @@ CacheGenerateCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a CacheGetCommand::CacheGetCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options .add_option("", "n", "namespace", "Namespace to generate cache values/records for", cxxopts::value(m_Namespace), "<namespace>"); m_Options.add_option("", "b", "bucket", "Bucket name to generate cache values/records for", cxxopts::value(m_Bucket), "<bucket>"); @@ -656,7 +656,7 @@ CacheGetCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) } } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (!m_OutputPath.empty()) { diff --git a/src/zen/cmds/exec_cmd.cpp b/src/zen/cmds/exec_cmd.cpp index 42c7119e7..cbc153e07 100644 --- a/src/zen/cmds/exec_cmd.cpp +++ b/src/zen/cmds/exec_cmd.cpp @@ -44,7 +44,7 @@ namespace zen { ExecCommand::ExecCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName), "<hosturl>"); m_Options.add_option("", "", "log", "Action log directory", cxxopts::value(m_RecordingLogPath), "<path>"); m_Options.add_option("", "p", "path", "Recording path (directory or .actionlog file)", cxxopts::value(m_RecordingPath), "<path>"); m_Options.add_option("", "", "offset", "Recording replay start offset", cxxopts::value(m_Offset), "<offset>"); diff --git a/src/zen/cmds/info_cmd.cpp b/src/zen/cmds/info_cmd.cpp index 49ad022cf..9faad5691 100644 --- a/src/zen/cmds/info_cmd.cpp +++ b/src/zen/cmds/info_cmd.cpp @@ -14,7 +14,7 @@ namespace zen { InfoCommand::InfoCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); } InfoCommand::~InfoCommand() @@ -38,7 +38,7 @@ InfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Result = Http.Get("/admin/info", HttpClient::Accept(ZenContentType::kJSON))) { diff --git a/src/zen/cmds/projectstore_cmd.cpp b/src/zen/cmds/projectstore_cmd.cpp index c0780c7e8..d31c34fd0 100644 --- a/src/zen/cmds/projectstore_cmd.cpp +++ b/src/zen/cmds/projectstore_cmd.cpp @@ -507,7 +507,7 @@ namespace projectstore_impl { DropProjectCommand::DropProjectCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectName), "<projectid>"); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogName), "<oplogid>"); m_Options.add_option("", "", "dryrun", "Dry run - resolve arguments but do not drop", cxxopts::value(m_DryRun), "<dryrun>"); @@ -537,7 +537,7 @@ DropProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); m_ProjectName = ResolveProject(Http, m_ProjectName); if (m_ProjectName.empty()) @@ -598,7 +598,7 @@ DropProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg ProjectInfoCommand::ProjectInfoCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectName), "<projectid>"); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogName), "<oplogid>"); m_Options.parse_positional({"project", "oplog"}); @@ -632,7 +632,7 @@ ProjectInfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg throw OptionParseException("'--project' is required", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); std::string Url; if (m_ProjectName.empty()) @@ -684,7 +684,7 @@ ProjectInfoCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg CreateProjectCommand::CreateProjectCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectId), "<projectid>"); m_Options.add_option("", "", "rootdir", "Absolute path to root directory", cxxopts::value(m_RootDir), "<root>"); m_Options.add_option("", "", "enginedir", "Absolute path to engine root directory", cxxopts::value(m_EngineRootDir), "<engineroot>"); @@ -721,7 +721,7 @@ CreateProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a throw OptionParseException("'--project' is required", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); std::string Url = fmt::format("/prj/{}", m_ProjectId); @@ -756,7 +756,7 @@ CreateProjectCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a CreateOplogCommand::CreateOplogCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectId), "<projectid>"); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogId), "<oplogid>"); m_Options.add_option("", "", "gcpath", "Absolute path to oplog lifetime marker file", cxxopts::value(m_GcPath), "<path>"); @@ -791,8 +791,8 @@ CreateOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg throw OptionParseException("'--project' is required", m_Options.help()); } - HttpClient Http(m_HostName); - m_ProjectId = ResolveProject(Http, m_ProjectId); + HttpClient Http = CreateHttpClient(m_HostName); + m_ProjectId = ResolveProject(Http, m_ProjectId); if (m_ProjectId.empty()) { throw std::runtime_error("Project can not be found"); @@ -835,7 +835,7 @@ CreateOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg ExportOplogCommand::ExportOplogCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectName), "<projectid>"); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogName), "<oplogid>"); m_Options.add_option("", "", "maxblocksize", "Max size for bundled attachments", cxxopts::value(m_MaxBlockSize), "<blocksize>"); @@ -1022,8 +1022,8 @@ ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg throw OptionParseException("'--project' is required", m_Options.help()); } - HttpClient Http(m_HostName); - m_ProjectName = ResolveProject(Http, m_ProjectName); + HttpClient Http = CreateHttpClient(m_HostName); + m_ProjectName = ResolveProject(Http, m_ProjectName); if (m_ProjectName.empty()) { throw std::runtime_error("Project can not be found"); @@ -1368,7 +1368,7 @@ ExportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg ImportOplogCommand::ImportOplogCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectName), "<projectid>"); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogName), "<oplogid>"); m_Options.add_option("", @@ -1541,8 +1541,8 @@ ImportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg m_Options.help()); } - HttpClient Http(m_HostName); - m_ProjectName = ResolveProject(Http, m_ProjectName); + HttpClient Http = CreateHttpClient(m_HostName); + m_ProjectName = ResolveProject(Http, m_ProjectName); if (m_ProjectName.empty()) { throw std::runtime_error("Project can not be found"); @@ -1782,7 +1782,7 @@ ImportOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg SnapshotOplogCommand::SnapshotOplogCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectName), "<projectid>"); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogName), "<oplogid>"); @@ -1813,7 +1813,7 @@ SnapshotOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (m_ProjectName.empty()) { @@ -1851,7 +1851,7 @@ SnapshotOplogCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a ProjectStatsCommand::ProjectStatsCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); } ProjectStatsCommand::~ProjectStatsCommand() @@ -1876,7 +1876,7 @@ ProjectStatsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** ar throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Result = Http.Get("/stats/prj", HttpClient::Accept(ZenContentType::kJSON))) { ZEN_CONSOLE("{}", Result.ToText()); @@ -1892,7 +1892,7 @@ ProjectStatsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** ar ProjectOpDetailsCommand::ProjectOpDetailsCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "c", "csv", "Output in CSV format (default is JSon)", cxxopts::value(m_CSV), "<csv>"); m_Options.add_option("", "d", "details", "Detailed info on oplog", cxxopts::value(m_Details), "<details>"); m_Options.add_option("", "o", "opdetails", "Details info on oplog body", cxxopts::value(m_OpDetails), "<opdetails>"); @@ -1929,7 +1929,7 @@ ProjectOpDetailsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char* throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); m_ProjectName = ResolveProject(Http, m_ProjectName); if (m_ProjectName.empty()) @@ -1982,7 +1982,7 @@ ProjectOpDetailsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char* OplogMirrorCommand::OplogMirrorCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name to get info from", cxxopts::value(m_ProjectName), "<projectid>"); m_Options.add_option("", "l", "oplog", "Oplog name to get info from", cxxopts::value(m_OplogName), "<oplogid>"); m_Options.add_option("", "t", "target", "Target directory for mirror", cxxopts::value(m_MirrorRootPath), "<path>"); @@ -2045,7 +2045,7 @@ OplogMirrorCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); m_ProjectName = ResolveProject(Http, m_ProjectName); if (m_ProjectName.empty()) @@ -2283,7 +2283,7 @@ OplogMirrorCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** arg OplogValidateCommand::OplogValidateCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name to get info from", cxxopts::value(m_ProjectName), "<projectid>"); m_Options.add_option("", "l", "oplog", "Oplog name to get info from", cxxopts::value(m_OplogName), "<oplogid>"); @@ -2313,7 +2313,7 @@ OplogValidateCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** a throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); m_ProjectName = ResolveProject(Http, m_ProjectName); if (m_ProjectName.empty()) diff --git a/src/zen/cmds/rpcreplay_cmd.cpp b/src/zen/cmds/rpcreplay_cmd.cpp index 70e9e5300..3bf81a9df 100644 --- a/src/zen/cmds/rpcreplay_cmd.cpp +++ b/src/zen/cmds/rpcreplay_cmd.cpp @@ -32,7 +32,7 @@ using namespace std::literals; RpcStartRecordingCommand::RpcStartRecordingCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "path", "Recording file path", cxxopts::value(m_RecordingPath), "<path>"); m_Options.parse_positional("path"); @@ -61,7 +61,7 @@ RpcStartRecordingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char throw OptionParseException("'--path' is required", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Post("/z$/exec$/start-recording"sv, HttpClient::KeyValueMap{}, HttpClient::KeyValueMap({{"path", m_RecordingPath}}))) { @@ -78,7 +78,7 @@ RpcStartRecordingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char RpcStopRecordingCommand::RpcStopRecordingCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); } RpcStopRecordingCommand::~RpcStopRecordingCommand() = default; @@ -100,7 +100,7 @@ RpcStopRecordingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char* throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Post("/z$/exec$/stop-recording"sv)) { ZEN_CONSOLE("{}", Response.ToText()); @@ -116,7 +116,7 @@ RpcStopRecordingCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char* RpcReplayCommand::RpcReplayCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "path", "Recording file path", cxxopts::value(m_RecordingPath), "<path>"); m_Options.add_option("", "", "dry", "Do a dry run", cxxopts::value(m_DryRun), "<enable>"); m_Options.add_option("", @@ -223,7 +223,7 @@ RpcReplayCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) if (m_OnHost) { - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Response = Http.Post("/z$/exec$/replay-recording"sv, HttpClient::KeyValueMap{}, @@ -302,7 +302,7 @@ RpcReplayCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) } }); - HttpClient Http{m_HostName}; + HttpClient Http = CreateHttpClient(m_HostName); uint64_t EntryIndex = EntryOffset.fetch_add(m_Stride); while (EntryIndex < EntryCount) diff --git a/src/zen/cmds/serve_cmd.cpp b/src/zen/cmds/serve_cmd.cpp index 49389bcdf..03007a86c 100644 --- a/src/zen/cmds/serve_cmd.cpp +++ b/src/zen/cmds/serve_cmd.cpp @@ -21,7 +21,7 @@ using namespace std::literals; ServeCommand::ServeCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "project", "Project name", cxxopts::value(m_ProjectName), "<projectid>"); m_Options.add_option("", "o", "oplog", "Oplog name", cxxopts::value(m_OplogName), "<oplogid>"); m_Options.add_option("", "", "path", "Root path to directory", cxxopts::value(m_RootPath), "<rootpath>"); @@ -183,7 +183,7 @@ ServeCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) const std::string ProjectUri = fmt::format("/prj/{}", m_ProjectName); const std::string ProjectOplogUri = fmt::format("/prj/{}/oplog/{}", m_ProjectName, m_OplogName); - HttpClient Client(m_HostName); + HttpClient Client = CreateHttpClient(m_HostName); // Ensure project exists diff --git a/src/zen/cmds/status_cmd.cpp b/src/zen/cmds/status_cmd.cpp index c43f85052..6ed3c42e1 100644 --- a/src/zen/cmds/status_cmd.cpp +++ b/src/zen/cmds/status_cmd.cpp @@ -4,6 +4,7 @@ #include <zencore/compactbinary.h> #include <zencore/compactbinaryutil.h> +#include <zencore/filesystem.h> #include <zencore/fmtutils.h> #include <zencore/logging.h> #include <zencore/string.h> @@ -64,7 +65,7 @@ StatusCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) return; } - ZEN_CONSOLE("{:>5} {:>6} {:>24}", "port", "pid", "session"); + ZEN_CONSOLE("{:>5} {:>6} {:>24} {}", "port", "pid", "session", "socket"); State.Snapshot([&](const ZenServerState::ZenServerEntry& Entry) { bool MatchesAnyPort = (m_Port == 0) && (EffectivePort == 0); bool MatchesEffectivePort = (EffectivePort != 0) && (Entry.EffectiveListenPort.load() == EffectivePort); @@ -73,7 +74,22 @@ StatusCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { StringBuilder<25> SessionStringBuilder; Entry.GetSessionId().ToString(SessionStringBuilder); - ZEN_CONSOLE("{:>5} {:>6} {:>24}", Entry.EffectiveListenPort.load(), Entry.Pid.load(), SessionStringBuilder); + + std::string SocketPath; + if (Entry.HasInstanceInfo()) + { + ZenServerInstanceInfo Info; + if (Info.OpenReadOnly(Entry.GetSessionId())) + { + InstanceInfoData Data = Info.Read(); + if (!Data.UnixSocketPath.empty()) + { + SocketPath = PathToUtf8(Data.UnixSocketPath); + } + } + } + std::string PortStr = Entry.IsNoNetwork() ? std::string("-") : fmt::to_string(Entry.EffectiveListenPort.load()); + ZEN_CONSOLE("{:>5} {:>6} {:>24} {}", PortStr, Entry.Pid.load(), SessionStringBuilder, SocketPath); } }); } diff --git a/src/zen/cmds/top_cmd.cpp b/src/zen/cmds/top_cmd.cpp index b8a48ddb6..70f370bd0 100644 --- a/src/zen/cmds/top_cmd.cpp +++ b/src/zen/cmds/top_cmd.cpp @@ -109,13 +109,29 @@ TopCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { if ((n++ % HeaderPeriod) == 0) { - ZEN_CONSOLE("{:>5} {:>6} {:>24}", "port", "pid", "session"); + ZEN_CONSOLE("{:>5} {:>6} {:>24} {}", "port", "pid", "session", "socket"); } State.Snapshot([&](const ZenServerState::ZenServerEntry& Entry) { StringBuilder<25> SessionStringBuilder; Entry.GetSessionId().ToString(SessionStringBuilder); - ZEN_CONSOLE("{:>5} {:>6} {:>24}", Entry.EffectiveListenPort.load(), Entry.Pid.load(), SessionStringBuilder); + + std::string SocketPath; + if (Entry.HasInstanceInfo()) + { + ZenServerInstanceInfo Info; + if (Info.OpenReadOnly(Entry.GetSessionId())) + { + InstanceInfoData Data = Info.Read(); + if (!Data.UnixSocketPath.empty()) + { + SocketPath = PathToUtf8(Data.UnixSocketPath); + } + } + } + + std::string PortStr = Entry.IsNoNetwork() ? std::string("-") : fmt::to_string(Entry.EffectiveListenPort.load()); + ZEN_CONSOLE("{:>5} {:>6} {:>24} {}", PortStr, Entry.Pid.load(), SessionStringBuilder, SocketPath); }); zen::Sleep(1000); @@ -300,7 +316,21 @@ PsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) } State.Snapshot([&](const ZenServerState::ZenServerEntry& Entry) { - ZEN_CONSOLE("Port {} : pid {}", Entry.EffectiveListenPort.load(), Entry.Pid.load()); + std::string Extra; + if (Entry.HasInstanceInfo()) + { + ZenServerInstanceInfo Info; + if (Info.OpenReadOnly(Entry.GetSessionId())) + { + InstanceInfoData Data = Info.Read(); + if (!Data.UnixSocketPath.empty()) + { + Extra = fmt::format(" (unix: {})", Data.UnixSocketPath); + } + } + } + std::string PortStr = Entry.IsNoNetwork() ? std::string("-") : fmt::to_string(Entry.EffectiveListenPort.load()); + ZEN_CONSOLE("Port {} : pid {}{}", PortStr, Entry.Pid.load(), Extra); }); } diff --git a/src/zen/cmds/trace_cmd.cpp b/src/zen/cmds/trace_cmd.cpp index 41a30068c..54c0f080d 100644 --- a/src/zen/cmds/trace_cmd.cpp +++ b/src/zen/cmds/trace_cmd.cpp @@ -12,7 +12,7 @@ namespace zen { TraceCommand::TraceCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "s", "stop", "Stop tracing", cxxopts::value(m_Stop)->default_value("false"), "<stop>"); m_Options.add_option("", "", "host", "Start tracing to host", cxxopts::value(m_TraceHost), "<hostip>"); m_Options.add_option("", "", "file", "Start tracing to file", cxxopts::value(m_TraceFile), "<filepath>"); @@ -37,7 +37,7 @@ TraceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("Unable to resolve server specification", m_Options.help()); } - zen::HttpClient Http(m_HostName); + zen::HttpClient Http = CreateHttpClient(m_HostName); if (m_Stop) { diff --git a/src/zen/cmds/ui_cmd.cpp b/src/zen/cmds/ui_cmd.cpp index da06ce305..4846b4d18 100644 --- a/src/zen/cmds/ui_cmd.cpp +++ b/src/zen/cmds/ui_cmd.cpp @@ -50,7 +50,7 @@ UiCommand::UiCommand() { m_Options.add_options()("h,help", "Print help"); m_Options.add_options()("a,all", "Open dashboard for all running instances", cxxopts::value(m_All)->default_value("false")); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_option("", "p", "path", @@ -230,6 +230,11 @@ UiCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) throw OptionParseException("Unable to resolve server specification", m_Options.help()); } + if (IsUnixSocketSpec(m_HostName)) + { + throw std::runtime_error("Cannot open browser for a Unix domain socket connection"); + } + OpenBrowser(m_HostName); } diff --git a/src/zen/cmds/version_cmd.cpp b/src/zen/cmds/version_cmd.cpp index ed34d7011..0948de1bb 100644 --- a/src/zen/cmds/version_cmd.cpp +++ b/src/zen/cmds/version_cmd.cpp @@ -20,7 +20,7 @@ using namespace std::literals; VersionCommand::VersionCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName), "[hosturl]"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName), "[hosturl]"); m_Options.add_option("", "d", "detailed", "Detailed Version", cxxopts::value(m_DetailedVersion), "[detailedversion]"); m_Options.add_option("", "o", "output-path", "Path for output", cxxopts::value(m_OutputPath), "[outputpath]"); m_Options.parse_positional({"hosturl"}); @@ -57,7 +57,7 @@ VersionCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) ZEN_CONSOLE("Querying host {}", m_HostName); } - HttpClient Client(m_HostName, HttpClientSettings{.Timeout = std::chrono::milliseconds(5000)}); + HttpClient Client = CreateHttpClient(m_HostName, {.Timeout = std::chrono::milliseconds(5000)}); HttpClient::KeyValueMap Parameters; if (m_DetailedVersion) diff --git a/src/zen/cmds/vfs_cmd.cpp b/src/zen/cmds/vfs_cmd.cpp index 79ec20ce2..29ad8dc7c 100644 --- a/src/zen/cmds/vfs_cmd.cpp +++ b/src/zen/cmds/vfs_cmd.cpp @@ -18,7 +18,7 @@ using namespace std::literals; VfsCommand::VfsCommand() { m_Options.add_option("", "", "verb", "VFS management verb (mount, unmount, info)", cxxopts::value(m_Verb), "<verb>"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<url>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<url>"); m_Options.add_option("", "", "vfs-path", "Specify VFS mount point path", cxxopts::value(m_MountPath), "<path>"); m_Options.parse_positional({"verb", "vfs-path"}); @@ -45,7 +45,7 @@ VfsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) if (m_HostName.empty()) throw OptionParseException("Unable to resolve server specification", m_Options.help()); - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (m_Verb == "mount"sv) { diff --git a/src/zen/cmds/workspaces_cmd.cpp b/src/zen/cmds/workspaces_cmd.cpp index af265d898..220ef6a9e 100644 --- a/src/zen/cmds/workspaces_cmd.cpp +++ b/src/zen/cmds/workspaces_cmd.cpp @@ -86,7 +86,7 @@ namespace { WorkspaceCommand::WorkspaceCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_options()("system-dir", "Specify system root", cxxopts::value(m_SystemRootDir)); m_Options.add_option("", "v", "verb", "Verb for workspace - create, remove, info", cxxopts::value(m_Verb), "<verb>"); m_Options.parse_positional({"verb"}); @@ -182,7 +182,7 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { if (!m_HostName.empty()) { - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv)); @@ -254,7 +254,7 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { if (!m_HostName.empty()) { - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv)); @@ -275,7 +275,7 @@ WorkspaceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) WorkspaceShareCommand::WorkspaceShareCommand() { m_Options.add_options()("h,help", "Print help"); - m_Options.add_option("", "u", "hosturl", "Host URL", cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); + m_Options.add_option("", "u", "hosturl", kHostUrlHelp, cxxopts::value(m_HostName)->default_value(""), "<hosturl>"); m_Options.add_options()("system-dir", "Specify system root", cxxopts::value(m_SystemRootDir)); m_Options.add_option("", "v", "verb", "Verb for workspace - create, remove, info", cxxopts::value(m_Verb), "<verb>"); m_Options.parse_positional({"verb"}); @@ -475,7 +475,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** { if (!m_HostName.empty()) { - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv)); @@ -592,7 +592,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** { if (!m_HostName.empty()) { - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Result = Http.Get("/ws/refresh"); !Result) { ZEN_CONSOLE_WARN("Failed to refresh workspaces for host {}. Reason: '{}'", m_HostName, Result.ErrorMessage(""sv)); @@ -645,7 +645,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** throw OptionParseException("Unable to resolve server specification", SubOption->help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/files", GetShareIdentityUrl(m_FilesOptions)), {}, Params)) { ZEN_CONSOLE("{}: {}", Result, Result.ToText()); @@ -678,7 +678,7 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** throw OptionParseException("Unable to resolve server specification", SubOption->help()); } - HttpClient Http(m_HostName); + HttpClient Http = CreateHttpClient(m_HostName); if (HttpClient::Response Result = Http.Get(fmt::format("/ws/{}/entries", GetShareIdentityUrl(m_EntriesOptions)), {}, Params)) { ZEN_CONSOLE("{}: {}", Result, Result.ToText()); @@ -753,8 +753,8 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** throw OptionParseException("'--chunk' is required", SubOption->help()); } - HttpClient Http(m_HostName); - m_ChunkId = ChunksToOidStrings(Http, m_WorkspaceId, m_ShareId, std::vector<std::string>{m_ChunkId})[0]; + HttpClient Http = CreateHttpClient(m_HostName); + m_ChunkId = ChunksToOidStrings(Http, m_WorkspaceId, m_ShareId, std::vector<std::string>{m_ChunkId})[0]; HttpClient::KeyValueMap Params; if (m_Offset != 0) @@ -794,8 +794,8 @@ WorkspaceShareCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** throw OptionParseException("'--chunks' is required", SubOption->help()); } - HttpClient Http(m_HostName); - m_ChunkIds = ChunksToOidStrings(Http, m_WorkspaceId, m_ShareId, m_ChunkIds); + HttpClient Http = CreateHttpClient(m_HostName); + m_ChunkIds = ChunksToOidStrings(Http, m_WorkspaceId, m_ShareId, m_ChunkIds); std::vector<RequestChunkEntry> ChunkRequests; ChunkRequests.resize(m_ChunkIds.size()); diff --git a/src/zen/zen.cpp b/src/zen/zen.cpp index cb9704ab6..ee9628740 100644 --- a/src/zen/zen.cpp +++ b/src/zen/zen.cpp @@ -369,9 +369,31 @@ GetReturnCodeFromHttpResult(const HttpClientError& Ex) } } +bool +ZenCmdBase::IsUnixSocketSpec(std::string_view Spec) +{ + return Spec.starts_with("unix://"); +} + +HttpClient +ZenCmdBase::CreateHttpClient(const std::string& HostSpec, HttpClientSettings Settings) +{ + if (IsUnixSocketSpec(HostSpec)) + { + Settings.UnixSocketPath = HostSpec.substr(7); // strip "unix://" + return HttpClient("http://localhost", Settings); + } + return HttpClient(HostSpec, Settings); +} + std::string ZenCmdBase::ResolveTargetHostSpec(const std::string& InHostSpec, uint16_t& OutEffectivePort) { + if (IsUnixSocketSpec(InHostSpec)) + { + return InHostSpec; // pass through as-is; parsed later in CreateHttpClient + } + if (InHostSpec.empty()) { // If no host is specified then look to see if we have an instance @@ -386,8 +408,30 @@ ZenCmdBase::ResolveTargetHostSpec(const std::string& InHostSpec, uint16_t& OutEf Servers.Snapshot([&](const zen::ZenServerState::ZenServerEntry& Entry) { if (ResolvedSpec.empty()) { - ResolvedSpec = fmt::format("http://localhost:{}", Entry.EffectiveListenPort.load()); OutEffectivePort = Entry.EffectiveListenPort; + + // Check for per-instance info (e.g. UDS path) + if (Entry.HasInstanceInfo()) + { + ZenServerInstanceInfo Info; + if (Info.OpenReadOnly(Entry.GetSessionId())) + { + InstanceInfoData Data = Info.Read(); + if (!Data.UnixSocketPath.empty()) + { + ResolvedSpec = "unix://" + PathToUtf8(Data.UnixSocketPath); + return; + } + } + } + + // Skip servers with --no-network since TCP is not reachable + if (Entry.IsNoNetwork()) + { + return; + } + + ResolvedSpec = fmt::format("http://localhost:{}", Entry.EffectiveListenPort.load()); } }); @@ -685,7 +729,7 @@ main(int argc, char** argv) Options.add_options()("c, command", "Sub command", cxxopts::value<std::string>(SubCommand)); Options.add_options()("httpclient", "Select HTTP client implementation (e.g. 'curl', 'cpr')", - cxxopts::value<std::string>(GlobalOptions.HttpClientBackend)->default_value("cpr")); + cxxopts::value<std::string>(GlobalOptions.HttpClientBackend)->default_value("curl")); int CoreLimit = 0; diff --git a/src/zen/zen.h b/src/zen/zen.h index 3cc06eea6..05ce32d0a 100644 --- a/src/zen/zen.h +++ b/src/zen/zen.h @@ -5,6 +5,7 @@ #include <zencore/except.h> #include <zencore/timer.h> #include <zencore/zencore.h> +#include <zenhttp/httpclient.h> #include <zenutil/config/commandlineoptions.h> #include <zenutil/config/loggingconfig.h> @@ -68,6 +69,11 @@ public: static std::string ResolveTargetHostSpec(const std::string& InHostSpec); static std::string ResolveTargetHostSpec(const std::string& InHostSpec, uint16_t& OutEffectivePort); + static bool IsUnixSocketSpec(std::string_view Spec); + static HttpClient CreateHttpClient(const std::string& HostSpec, HttpClientSettings Settings = {}); + + static constexpr const char* kHostUrlHelp = "Host URL or unix:///path/to/socket"; + static void LogExecutableVersionAndPid(); }; diff --git a/src/zencore/include/zencore/logbase.h b/src/zencore/include/zencore/logbase.h index ad2ab218d..046e96db3 100644 --- a/src/zencore/include/zencore/logbase.h +++ b/src/zencore/include/zencore/logbase.h @@ -101,7 +101,7 @@ struct LoggerRef inline logging::Logger* operator->() const; inline logging::Logger& operator*() const; - bool ShouldLog(logging::LogLevel Level) const { return m_Logger->ShouldLog(Level); } + bool ShouldLog(logging::LogLevel Level) const { return m_Logger && m_Logger->ShouldLog(Level); } void SetLogLevel(logging::LogLevel NewLogLevel) { m_Logger->SetLevel(NewLogLevel); } logging::LogLevel GetLogLevel() { return m_Logger->GetLevel(); } diff --git a/src/zencore/include/zencore/string.h b/src/zencore/include/zencore/string.h index 4deca63ed..7b46f0e38 100644 --- a/src/zencore/include/zencore/string.h +++ b/src/zencore/include/zencore/string.h @@ -331,7 +331,7 @@ public: return AppendAscii(Str); } -#if defined(__clang__) && !defined(__APPLE__) +#if defined(__clang__) && !defined(__APPLE__) && !defined(_MSC_VER) /* UE Toolchain Clang has different types for int64_t and long long so an override is needed here. Without it, Clang can't disambiguate integer overloads */ inline StringBuilderImpl& operator<<(long long n) @@ -953,6 +953,24 @@ StrCaseCompare(const char* Lhs, const char* Rhs, int64_t Length = -1) #endif } +inline int32_t +StrCaseCompare(std::string_view Lhs, std::string_view Rhs) +{ + int32_t Result = StrCaseCompare(Lhs.data(), Rhs.data(), std::min(Lhs.size(), Rhs.size())); + if (Result == 0) + { + if (Lhs.size() < Rhs.size()) + { + return -1; + } + else if (Lhs.size() > Rhs.size()) + { + return 1; + } + } + return Result; +} + /** * @brief * Helper function to implement case sensitive spaceship operator for strings. diff --git a/src/zencore/memtrack/callstacktrace.cpp b/src/zencore/memtrack/callstacktrace.cpp index 4a7068568..013c51535 100644 --- a/src/zencore/memtrack/callstacktrace.cpp +++ b/src/zencore/memtrack/callstacktrace.cpp @@ -912,92 +912,100 @@ FBacktracer::GetBacktraceId(void* AddressOfReturnAddress) # else // UE_CALLSTACK_TRACE_USE_UNWIND_TABLES -namespace zen { +//////////////////////////////////////////////////////////////////////////////// +class FBacktracer +{ +public: + FBacktracer(FMalloc* InMalloc); + ~FBacktracer(); + static FBacktracer* Get(); + inline uint32_t GetBacktraceId(void* AddressOfReturnAddress); + uint32_t GetBacktraceId(uint64_t ReturnAddress); + void AddModule(uintptr_t Base, const char16_t* Name) {} + void RemoveModule(uintptr_t Base) {} - //////////////////////////////////////////////////////////////////////////////// - class FBacktracer - { - public: - FBacktracer(FMalloc* InMalloc); - ~FBacktracer(); - static FBacktracer* Get(); - inline uint32_t GetBacktraceId(void* AddressOfReturnAddress); - uint32_t GetBacktraceId(uint64_t ReturnAddress); - void AddModule(uintptr_t Base, const char16_t* Name) {} - void RemoveModule(uintptr_t Base) {} - - private: - static FBacktracer* Instance; - FMalloc* Malloc; - FCallstackTracer CallstackTracer; - }; +private: + static FBacktracer* Instance; + FMalloc* Malloc; + FCallstackTracer CallstackTracer; +}; - //////////////////////////////////////////////////////////////////////////////// - FBacktracer* FBacktracer::Instance = nullptr; +//////////////////////////////////////////////////////////////////////////////// +FBacktracer* FBacktracer::Instance = nullptr; - //////////////////////////////////////////////////////////////////////////////// - FBacktracer::FBacktracer(FMalloc* InMalloc) : Malloc(InMalloc), CallstackTracer(InMalloc) { Instance = this; } +//////////////////////////////////////////////////////////////////////////////// +FBacktracer::FBacktracer(FMalloc* InMalloc) : Malloc(InMalloc), CallstackTracer(InMalloc) +{ + Instance = this; +} - //////////////////////////////////////////////////////////////////////////////// - FBacktracer::~FBacktracer() {} +//////////////////////////////////////////////////////////////////////////////// +FBacktracer::~FBacktracer() +{ +} - //////////////////////////////////////////////////////////////////////////////// - FBacktracer* FBacktracer::Get() { return Instance; } +//////////////////////////////////////////////////////////////////////////////// +FBacktracer* +FBacktracer::Get() +{ + return Instance; +} - //////////////////////////////////////////////////////////////////////////////// - uint32_t FBacktracer::GetBacktraceId(void* AddressOfReturnAddress) - { - const uint64_t ReturnAddress = *(uint64_t*)AddressOfReturnAddress; - return GetBacktraceId(ReturnAddress); - } +//////////////////////////////////////////////////////////////////////////////// +uint32_t +FBacktracer::GetBacktraceId(void* AddressOfReturnAddress) +{ + const uint64_t ReturnAddress = *(uint64_t*)AddressOfReturnAddress; + return GetBacktraceId(ReturnAddress); +} - //////////////////////////////////////////////////////////////////////////////// - uint32_t FBacktracer::GetBacktraceId(uint64_t ReturnAddress) - { +//////////////////////////////////////////////////////////////////////////////// +uint32_t +FBacktracer::GetBacktraceId(uint64_t ReturnAddress) +{ # if !UE_BUILD_SHIPPING - uint64_t StackFrames[256]; - int32_t NumStackFrames = FPlatformStackWalk::CaptureStackBackTrace(StackFrames, UE_ARRAY_COUNT(StackFrames)); - if (NumStackFrames > 0) + uint64_t StackFrames[256]; + int32_t NumStackFrames = FPlatformStackWalk::CaptureStackBackTrace(StackFrames, UE_ARRAY_COUNT(StackFrames)); + if (NumStackFrames > 0) + { + FCallstackTracer::FBacktraceEntry BacktraceEntry; + uint64_t BacktraceId = 0; + uint32_t FrameIdx = 0; + bool bUseAddress = false; + for (int32_t Index = 0; Index < NumStackFrames; Index++) { - FCallstackTracer::FBacktraceEntry BacktraceEntry; - uint64_t BacktraceId = 0; - uint32_t FrameIdx = 0; - bool bUseAddress = false; - for (int32_t Index = 0; Index < NumStackFrames; Index++) + if (!bUseAddress) { - if (!bUseAddress) + // start using backtrace only after ReturnAddress + if (StackFrames[Index] == (uint64_t)ReturnAddress) { - // start using backtrace only after ReturnAddress - if (StackFrames[Index] == (uint64_t)ReturnAddress) - { - bUseAddress = true; - } - } - if (bUseAddress || NumStackFrames == 1) - { - uint64_t RetAddr = StackFrames[Index]; - StackFrames[FrameIdx++] = RetAddr; - - // This is a simple order-dependent LCG. Should be sufficient enough - BacktraceId += RetAddr; - BacktraceId *= 0x30be8efa499c249dull; + bUseAddress = true; } } + if (bUseAddress || NumStackFrames == 1) + { + uint64_t RetAddr = StackFrames[Index]; + StackFrames[FrameIdx++] = RetAddr; - // Save the collected id - BacktraceEntry.Hash = BacktraceId; - BacktraceEntry.FrameCount = FrameIdx; - BacktraceEntry.Frames = StackFrames; - - // Add to queue to be processed. This might block until there is room in the - // queue (i.e. the processing thread has caught up processing). - return CallstackTracer.AddCallstack(BacktraceEntry); + // This is a simple order-dependent LCG. Should be sufficient enough + BacktraceId += RetAddr; + BacktraceId *= 0x30be8efa499c249dull; + } } -# endif - return 0; + // Save the collected id + BacktraceEntry.Hash = BacktraceId; + BacktraceEntry.FrameCount = FrameIdx; + BacktraceEntry.Frames = StackFrames; + + // Add to queue to be processed. This might block until there is room in the + // queue (i.e. the processing thread has caught up processing). + return CallstackTracer.AddCallstack(BacktraceEntry); } +# endif + return 0; +} } # endif // UE_CALLSTACK_TRACE_USE_UNWIND_TABLES @@ -1047,7 +1055,7 @@ CallstackTrace_GetCurrentId() # if PLATFORM_USE_CALLSTACK_ADDRESS_POINTER return Instance->GetBacktraceId(StackAddress); # else - return Instance->GetBacktraceId((uint64_t)StackAddress); + return Instance->GetBacktraceId((uint64_t)StackAddress); # endif } diff --git a/src/zencore/string.cpp b/src/zencore/string.cpp index ed0ba6f46..358722b0b 100644 --- a/src/zencore/string.cpp +++ b/src/zencore/string.cpp @@ -1181,6 +1181,17 @@ TEST_CASE("string") CHECK(StrCaseCompare("BBr", "Bar", 2) > 0); } + SUBCASE("StrCaseCompare") + { + CHECK(StrCaseCompare("foo"sv, "FoO"sv) == 0); + CHECK(StrCaseCompare("foo"sv, "FoOz"sv) < 0); + CHECK(StrCaseCompare("fooo"sv, "FoO"sv) > 0); + CHECK(StrCaseCompare("Bar"sv, "bAs"sv) < 0); + CHECK(StrCaseCompare("bAr"sv, "Bas"sv) < 0); + CHECK(StrCaseCompare("BBr"sv, "Bar"sv) > 0); + CHECK(StrCaseCompare("Bbr"sv, "BAr"sv) > 0); + } + SUBCASE("ForEachStrTok") { const auto Tokens = "here,is,my,different,tokens"sv; diff --git a/src/zenhttp/clients/httpclientcpr.cpp b/src/zenhttp/clients/httpclientcpr.cpp index a0f5cc38f..a52b8f74b 100644 --- a/src/zenhttp/clients/httpclientcpr.cpp +++ b/src/zenhttp/clients/httpclientcpr.cpp @@ -7,6 +7,7 @@ #include <zencore/compactbinarypackage.h> #include <zencore/compactbinaryutil.h> #include <zencore/compress.h> +#include <zencore/filesystem.h> #include <zencore/iobuffer.h> #include <zencore/iohash.h> #include <zencore/session.h> @@ -513,7 +514,7 @@ CprHttpClient::AllocSession(const std::string_view BaseUrl, if (!ConnectionSettings.UnixSocketPath.empty()) { - CprSession->SetUnixSocket(cpr::UnixSocket(ConnectionSettings.UnixSocketPath)); + CprSession->SetUnixSocket(cpr::UnixSocket(PathToUtf8(ConnectionSettings.UnixSocketPath))); } if (ConnectionSettings.InsecureSsl || !ConnectionSettings.CaBundlePath.empty()) 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); diff --git a/src/zenhttp/clients/httpclientcurl.h b/src/zenhttp/clients/httpclientcurl.h index 871877863..b7fa52e6c 100644 --- a/src/zenhttp/clients/httpclientcurl.h +++ b/src/zenhttp/clients/httpclientcurl.h @@ -75,40 +75,39 @@ private: struct Session { Session(CurlHttpClient* InOuter, CURL* InHandle) : Outer(InOuter), Handle(InHandle) {} - ~Session() { Outer->ReleaseSession(Handle); } + ~Session(); CURL* Get() const { return Handle; } + // Takes ownership of the curl_slist and sets it on the handle. + // The list is freed automatically when the Session is destroyed. + void SetHeaders(curl_slist* Headers); + + // Low-level perform: executes the request and collects status/timing. CurlResult Perform(); + // Sets up standard write+header callbacks, performs the request, and + // moves the collected body and headers into the returned CurlResult. + CurlResult PerformWithResponseCallbacks(); + LoggerRef Log() { return Outer->Log(); } private: CurlHttpClient* Outer; CURL* Handle; + curl_slist* HeaderList = nullptr; Session(Session&&) = delete; Session& operator=(Session&&) = delete; }; - Session AllocSession(std::string_view BaseUrl, - std::string_view Url, - const HttpClientSettings& ConnectionSettings, - const KeyValueMap& AdditionalHeader, - const KeyValueMap& Parameters, - std::string_view SessionId, - std::optional<HttpClientAccessToken> AccessToken); + Session AllocSession(std::string_view ResourcePath, const KeyValueMap& Parameters); RwLock m_SessionLock; std::vector<CURL*> m_Sessions; void ReleaseSession(CURL* Handle); - struct RetryResult - { - CurlResult Result; - }; - CurlResult DoWithRetry(std::string_view SessionId, std::function<CurlResult()>&& Func, std::unique_ptr<detail::TempPayloadFile>& PayloadFile); diff --git a/src/zenhttp/clients/httpwsclient.cpp b/src/zenhttp/clients/httpwsclient.cpp index 2d566ae86..fbae9f5fe 100644 --- a/src/zenhttp/clients/httpwsclient.cpp +++ b/src/zenhttp/clients/httpwsclient.cpp @@ -5,6 +5,8 @@ #include "../servers/wsframecodec.h" #include <zencore/base64.h> +#include <zencore/filesystem.h> +#include <zencore/fmtutils.h> #include <zencore/logging.h> #include <zencore/string.h> @@ -155,7 +157,7 @@ struct HttpWsClient::Impl } }); - asio::local::stream_protocol::endpoint Endpoint(m_Settings.UnixSocketPath); + asio::local::stream_protocol::endpoint Endpoint(PathToUtf8(m_Settings.UnixSocketPath)); m_UnixSocket->async_connect(Endpoint, [this](const asio::error_code& Ec) { if (Ec) { diff --git a/src/zenhttp/httpclient.cpp b/src/zenhttp/httpclient.cpp index deeeb6c85..9f49802a0 100644 --- a/src/zenhttp/httpclient.cpp +++ b/src/zenhttp/httpclient.cpp @@ -36,15 +36,17 @@ namespace zen { +#if ZEN_WITH_CPR extern HttpClientBase* CreateCprHttpClient(std::string_view BaseUri, const HttpClientSettings& ConnectionSettings, std::function<bool()>&& CheckIfAbortFunction); +#endif extern HttpClientBase* CreateCurlHttpClient(std::string_view BaseUri, const HttpClientSettings& ConnectionSettings, std::function<bool()>&& CheckIfAbortFunction); -static HttpClientBackend g_DefaultHttpClientBackend = HttpClientBackend::kCpr; +static HttpClientBackend g_DefaultHttpClientBackend = HttpClientBackend::kCurl; void SetDefaultHttpClientBackend(HttpClientBackend Backend) @@ -55,11 +57,14 @@ SetDefaultHttpClientBackend(HttpClientBackend Backend) void SetDefaultHttpClientBackend(std::string_view Backend) { +#if ZEN_WITH_CPR if (Backend == "cpr") { g_DefaultHttpClientBackend = HttpClientBackend::kCpr; } - else if (Backend == "curl") + else +#endif + if (Backend == "curl") { g_DefaultHttpClientBackend = HttpClientBackend::kCurl; } @@ -363,13 +368,15 @@ HttpClient::HttpClient(std::string_view BaseUri, const HttpClientSettings& Conne switch (EffectiveBackend) { - case HttpClientBackend::kCurl: - m_Inner = CreateCurlHttpClient(BaseUri, ConnectionSettings, std::move(CheckIfAbortFunction)); - break; +#if ZEN_WITH_CPR case HttpClientBackend::kCpr: - default: m_Inner = CreateCprHttpClient(BaseUri, ConnectionSettings, std::move(CheckIfAbortFunction)); break; +#endif + case HttpClientBackend::kCurl: + default: + m_Inner = CreateCurlHttpClient(BaseUri, ConnectionSettings, std::move(CheckIfAbortFunction)); + break; } } diff --git a/src/zenhttp/httpserver.cpp b/src/zenhttp/httpserver.cpp index 6ba0ca563..672467f56 100644 --- a/src/zenhttp/httpserver.cpp +++ b/src/zenhttp/httpserver.cpp @@ -2,6 +2,8 @@ #include <zenhttp/httpserver.h> +#include <zencore/filesystem.h> + #include "servers/httpasio.h" #include "servers/httpmulti.h" #include "servers/httpnull.h" @@ -1157,7 +1159,7 @@ CreateHttpServerClass(const std::string_view ServerClass, const HttpServerConfig ZEN_INFO("using asio HTTP server implementation") return CreateHttpAsioServer(AsioConfig { .ThreadCount = Config.ThreadCount, .ForceLoopback = Config.ForceLoopback, .IsDedicatedServer = Config.IsDedicatedServer, - .NoNetwork = Config.NoNetwork, .UnixSocketPath = Config.UnixSocketPath, + .NoNetwork = Config.NoNetwork, .UnixSocketPath = PathToUtf8(Config.UnixSocketPath), #if ZEN_USE_OPENSSL .HttpsPort = Config.HttpsPort, .CertFile = Config.CertFile, .KeyFile = Config.KeyFile, #endif diff --git a/src/zenhttp/include/zenhttp/cprutils.h b/src/zenhttp/include/zenhttp/cprutils.h index c252a5d99..3cfe652c5 100644 --- a/src/zenhttp/include/zenhttp/cprutils.h +++ b/src/zenhttp/include/zenhttp/cprutils.h @@ -2,17 +2,19 @@ #pragma once -#include <zencore/compactbinary.h> -#include <zencore/compactbinaryvalidation.h> -#include <zencore/iobuffer.h> -#include <zencore/string.h> -#include <zenhttp/formatters.h> -#include <zenhttp/httpclient.h> -#include <zenhttp/httpcommon.h> +#if ZEN_WITH_CPR + +# include <zencore/compactbinary.h> +# include <zencore/compactbinaryvalidation.h> +# include <zencore/iobuffer.h> +# include <zencore/string.h> +# include <zenhttp/formatters.h> +# include <zenhttp/httpclient.h> +# include <zenhttp/httpcommon.h> ZEN_THIRD_PARTY_INCLUDES_START -#include <cpr/response.h> -#include <fmt/format.h> +# include <cpr/response.h> +# include <fmt/format.h> ZEN_THIRD_PARTY_INCLUDES_END template<> @@ -92,3 +94,5 @@ struct fmt::formatter<cpr::Response> } } }; + +#endif // ZEN_WITH_CPR diff --git a/src/zenhttp/include/zenhttp/httpclient.h b/src/zenhttp/include/zenhttp/httpclient.h index 03c98af7e..e878c900f 100644 --- a/src/zenhttp/include/zenhttp/httpclient.h +++ b/src/zenhttp/include/zenhttp/httpclient.h @@ -10,6 +10,7 @@ #include <zencore/uid.h> #include <zenhttp/httpcommon.h> +#include <filesystem> #include <functional> #include <optional> #include <unordered_map> @@ -51,7 +52,9 @@ enum class HttpClientErrorCode : int enum class HttpClientBackend : uint8_t { kDefault, +#if ZEN_WITH_CPR kCpr, +#endif kCurl, }; @@ -91,7 +94,7 @@ struct HttpClientSettings /// 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; + std::filesystem::path UnixSocketPath; /// Disable HTTP keep-alive by closing the connection after each request. /// Useful for testing per-connection overhead. @@ -174,11 +177,14 @@ class HttpClientBase; class HttpClient { public: - HttpClient(std::string_view BaseUri, - const HttpClientSettings& Connectionsettings = {}, - std::function<bool()>&& CheckIfAbortFunction = {}); + explicit HttpClient(std::string_view BaseUri, + const HttpClientSettings& Connectionsettings = {}, + std::function<bool()>&& CheckIfAbortFunction = {}); ~HttpClient(); + HttpClient(const HttpClient&) = delete; + HttpClient& operator=(const HttpClient&) = delete; + struct ErrorContext { HttpClientErrorCode ErrorCode; diff --git a/src/zenhttp/include/zenhttp/httpserver.h b/src/zenhttp/include/zenhttp/httpserver.h index 627e7921f..77feb6568 100644 --- a/src/zenhttp/include/zenhttp/httpserver.h +++ b/src/zenhttp/include/zenhttp/httpserver.h @@ -15,6 +15,7 @@ #include <zentelemetry/stats.h> +#include <filesystem> #include <functional> #include <gsl/gsl-lite.hpp> #include <list> @@ -329,7 +330,7 @@ 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) + std::filesystem::path UnixSocketPath; // Unix domain socket path (empty = disabled) bool NoNetwork = false; // Disable TCP/HTTPS listeners; only accept connections via UnixSocketPath int HttpsPort = 0; // HTTPS listen port (0 = disabled, ASIO backend) std::string CertFile; // PEM certificate chain file path diff --git a/src/zenhttp/include/zenhttp/httpwsclient.h b/src/zenhttp/include/zenhttp/httpwsclient.h index 34d338b1d..2ca9b7ab1 100644 --- a/src/zenhttp/include/zenhttp/httpwsclient.h +++ b/src/zenhttp/include/zenhttp/httpwsclient.h @@ -46,7 +46,7 @@ struct HttpWsClientSettings /// 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; + std::filesystem::path UnixSocketPath; }; /** diff --git a/src/zenhttp/servers/httpplugin.cpp b/src/zenhttp/servers/httpplugin.cpp index 4bf8c61bb..a1bb719c8 100644 --- a/src/zenhttp/servers/httpplugin.cpp +++ b/src/zenhttp/servers/httpplugin.cpp @@ -147,7 +147,7 @@ public: HttpPluginServerRequest& operator=(const HttpPluginServerRequest&) = delete; // As this is plugin transport connection used for specialized connections we assume it is not a machine local connection - virtual bool IsLocalMachineRequest() const /* override*/ { return false; } + bool IsLocalMachineRequest() const override { return false; } virtual std::string_view GetAuthorizationHeader() const override; virtual Oid ParseSessionId() const override; virtual uint32_t ParseRequestId() const override; diff --git a/src/zenhttp/xmake.lua b/src/zenhttp/xmake.lua index 9b461662e..b4c65ea96 100644 --- a/src/zenhttp/xmake.lua +++ b/src/zenhttp/xmake.lua @@ -8,7 +8,12 @@ target('zenhttp') add_files("servers/httpsys.cpp", {unity_ignored=true}) add_files("servers/wshttpsys.cpp", {unity_ignored=true}) add_includedirs("include", {public=true}) - add_deps("zencore", "zentelemetry", "transport-sdk", "asio", "cpr") + add_deps("zencore", "zentelemetry", "transport-sdk", "asio") + if has_config("zencpr") then + add_deps("cpr") + else + remove_files("clients/httpclientcpr.cpp") + end add_packages("http_parser", "json11") add_options("httpsys") diff --git a/src/zenserver/config/config.cpp b/src/zenserver/config/config.cpp index c550b174c..60ae93853 100644 --- a/src/zenserver/config/config.cpp +++ b/src/zenserver/config/config.cpp @@ -201,6 +201,7 @@ struct ZenServerCmdLineOptions std::string DataDir; std::string BaseSnapshotDir; std::string SecurityConfigPath; + std::string UnixSocketPath; std::string PortStr; ZenLoggingCmdLineOptions LoggingOptions; @@ -320,7 +321,7 @@ ZenServerCmdLineOptions::AddCliOptions(cxxopts::Options& options, ZenServerConfi "", "unix-socket", "Unix domain socket path to listen on (in addition to TCP)", - cxxopts::value<std::string>(ServerOptions.HttpConfig.UnixSocketPath), + cxxopts::value<std::string>(UnixSocketPath), "<path>"); options.add_option("network", @@ -413,7 +414,7 @@ ZenServerCmdLineOptions::AddCliOptions(cxxopts::Options& options, ZenServerConfi "", "httpclient", "Select HTTP client implementation (e.g. 'curl', 'cpr')", - cxxopts::value<std::string>(ServerOptions.HttpClient.Backend)->default_value("cpr"), + cxxopts::value<std::string>(ServerOptions.HttpClient.Backend)->default_value("curl"), "<http client>"); options.add_option("network", @@ -480,6 +481,11 @@ ZenServerCmdLineOptions::ApplyOptions(cxxopts::Options& options, ZenServerConfig ServerOptions.BaseSnapshotDir = MakeSafeAbsolutePath(BaseSnapshotDir); ServerOptions.SecurityConfigPath = MakeSafeAbsolutePath(SecurityConfigPath); + if (!UnixSocketPath.empty()) + { + ServerOptions.HttpConfig.UnixSocketPath = MakeSafeAbsolutePath(UnixSocketPath); + } + if (PortStr != "auto") { int Port = 0; diff --git a/src/zenserver/frontend/html.zip b/src/zenserver/frontend/html.zip Binary files differdeleted file mode 100644 index 58778a592..000000000 --- a/src/zenserver/frontend/html.zip +++ /dev/null diff --git a/src/zenserver/xmake.lua b/src/zenserver/xmake.lua index 4f1568c73..f49ed0001 100644 --- a/src/zenserver/xmake.lua +++ b/src/zenserver/xmake.lua @@ -12,12 +12,14 @@ target("zenserver") "zenremotestore", "zenstore", "zentelemetry", - "zenutil", - "zenvfs") + "zenutil") + if is_plat("windows") then + add_deps("zenvfs") + end add_headerfiles("**.h") add_rules("utils.bin2c", {extensions = {".zip"}}) add_files("**.cpp") - add_files("frontend/*.zip") + add_files("frontend/html.zip") add_files("zenserver.cpp", {unity_ignored = true }) add_includedirs(".") @@ -73,7 +75,45 @@ target("zenserver") add_ldflags("-framework SystemConfiguration") end - -- to work around some unfortunate Ctrl-C behaviour on Linux/Mac due to + on_load(function(target) + local html_dir = path.join(os.projectdir(), "src/zenserver/frontend/html") + local zip_path = path.join(os.projectdir(), "src/zenserver/frontend/html.zip") + + -- Check if zip needs regeneration + local need_update = not os.isfile(zip_path) + if not need_update then + local zip_mtime = os.mtime(zip_path) + for _, file in ipairs(os.files(path.join(html_dir, "**"))) do + if os.mtime(file) > zip_mtime then + need_update = true + break + end + end + end + + if need_update then + print("Regenerating frontend zip...") + os.tryrm(zip_path) + + import("detect.tools.find_7z") + local cmd_7z = find_7z() + if cmd_7z then + os.execv(cmd_7z, {"a", "-mx0", zip_path, path.join(html_dir, ".")}) + else + import("detect.tools.find_zip") + local zip_cmd = find_zip() + if zip_cmd then + local oldir = os.cd(html_dir) + os.execv(zip_cmd, {"-r", "-0", zip_path, "."}) + os.cd(oldir) + else + raise("Unable to find a suitable zip tool (need 7z or zip)") + end + end + end + end) + + -- to work around some unfortunate Ctrl-C behaviour on Linux/Mac due to -- our use of setsid() at startup we pass in `--no-detach` to zenserver -- ensure that it recieves signals when the user requests termination on_run(function(target) diff --git a/src/zenserver/zenserver.cpp b/src/zenserver/zenserver.cpp index 8283f0cbe..519176ffe 100644 --- a/src/zenserver/zenserver.cpp +++ b/src/zenserver/zenserver.cpp @@ -726,6 +726,20 @@ ZenServerMain::Run() Entry = ServerState.Register(m_ServerOptions.BasePort); + // Publish per-instance extended info (e.g. UDS path) via a small shared memory + // section keyed by SessionId so clients can discover it during Snapshot() enumeration. + { + InstanceInfoData InstanceData; + InstanceData.UnixSocketPath = m_ServerOptions.HttpConfig.UnixSocketPath; + m_InstanceInfo.Create(GetSessionId(), InstanceData); + Entry->SignalHasInstanceInfo(); + } + + if (m_ServerOptions.HttpConfig.NoNetwork) + { + Entry->SignalNoNetwork(); + } + if (m_ServerOptions.OwnerPid) { // We are adding a sponsor process to our own entry, can't wait for pick since the code is not run until later @@ -786,7 +800,8 @@ ZenServerMain::MakeLockData(bool IsReady) .EffectiveListenPort = gsl::narrow<uint16_t>(m_ServerOptions.BasePort), .Ready = IsReady, .DataDir = m_ServerOptions.DataDir, - .ExecutablePath = GetRunningExecutablePath()}); + .ExecutablePath = GetRunningExecutablePath(), + .UnixSocketPath = m_ServerOptions.HttpConfig.UnixSocketPath}); }; } // namespace zen diff --git a/src/zenserver/zenserver.h b/src/zenserver/zenserver.h index 374184aa9..830f36e54 100644 --- a/src/zenserver/zenserver.h +++ b/src/zenserver/zenserver.h @@ -148,8 +148,9 @@ public: ZenServerMain& operator=(const ZenServerMain&) = delete; protected: - ZenServerConfig& m_ServerOptions; - LockFile m_LockFile; + ZenServerConfig& m_ServerOptions; + LockFile m_LockFile; + ZenServerInstanceInfo m_InstanceInfo; virtual void InitializeLogging(); virtual void DoRun(ZenServerState::ZenServerEntry* Entry) = 0; diff --git a/src/zenstore/xmake.lua b/src/zenstore/xmake.lua index ea8155e94..94c2b51ca 100644 --- a/src/zenstore/xmake.lua +++ b/src/zenstore/xmake.lua @@ -6,6 +6,11 @@ target('zenstore') add_headerfiles("**.h") add_files("**.cpp") add_includedirs("include", {public=true}) - add_deps("zencore", "zentelemetry", "zenutil", "zenvfs") + add_deps("zencore", "zentelemetry", "zenutil") + if is_plat("windows") then + add_deps("zenvfs") + else + add_includedirs("$(projectdir)/src/zenvfs/include", {public=true}) + end add_deps("robin-map") add_packages("eastl", {public=true}); diff --git a/src/zenutil/include/zenutil/zenserverprocess.h b/src/zenutil/include/zenutil/zenserverprocess.h index 1b8750628..2f76f0d6c 100644 --- a/src/zenutil/include/zenutil/zenserverprocess.h +++ b/src/zenutil/include/zenutil/zenserverprocess.h @@ -224,8 +224,10 @@ public: enum class FlagsEnum : uint16_t { - kShutdownPlease = 1 << 0, - kIsReady = 1 << 1, + kShutdownPlease = 1 << 0, + kIsReady = 1 << 1, + kHasInstanceInfo = 1 << 2, + kNoNetwork = 1 << 3, }; FRIEND_ENUM_CLASS_FLAGS(FlagsEnum); @@ -236,6 +238,10 @@ public: bool IsShutdownRequested() const; void SignalReady(); bool IsReady() const; + void SignalHasInstanceInfo(); + bool HasInstanceInfo() const; + void SignalNoNetwork(); + bool IsNoNetwork() const; bool AddSponsorProcess(uint32_t Pid, uint64_t Timeout = 0); }; @@ -258,6 +264,51 @@ private: bool m_IsReadOnly = true; }; +/** Per-instance extended data published via a small shared memory section keyed by SessionId. + + Servers create a writable section; clients open it read-only during Snapshot() + enumeration to discover fields that don't fit in the fixed-size ZenServerEntry + (e.g. Unix domain socket path). + + SessionId is preferred over PID for naming because it is unique per server + instance lifetime, avoiding issues with PID reuse on crash/restart. + */ + +struct InstanceInfoData +{ + std::filesystem::path UnixSocketPath; + // Extensible: add more per-instance fields here in the future +}; + +class ZenServerInstanceInfo +{ +public: + ZenServerInstanceInfo(); + ~ZenServerInstanceInfo(); + + ZenServerInstanceInfo(const ZenServerInstanceInfo&) = delete; + ZenServerInstanceInfo& operator=(const ZenServerInstanceInfo&) = delete; + + /// Server-side: create read-write, populate with data + void Create(const Oid& SessionId, const InstanceInfoData& Data); + + /// Client-side: open read-only by SessionId, returns false if not found + [[nodiscard]] bool OpenReadOnly(const Oid& SessionId); + + /// Read the data (valid after Create or successful OpenReadOnly) + [[nodiscard]] InstanceInfoData Read() const; + + bool IsValid() const { return m_Data != nullptr; } + +private: + static std::string MakeName(const Oid& SessionId); + + void* m_hMapFile = nullptr; + uint8_t* m_Data = nullptr; + bool m_IsOwner = false; + Oid m_SessionId; // for POSIX cleanup (shm_unlink) +}; + struct LockFileInfo { int32_t Pid; @@ -266,6 +317,7 @@ struct LockFileInfo bool Ready; std::filesystem::path DataDir; std::filesystem::path ExecutablePath; + std::filesystem::path UnixSocketPath; }; CbObject MakeLockFilePayload(const LockFileInfo& Info); diff --git a/src/zenutil/workerpools.cpp b/src/zenutil/workerpools.cpp index 1bab39b2a..25f961f77 100644 --- a/src/zenutil/workerpools.cpp +++ b/src/zenutil/workerpools.cpp @@ -25,9 +25,9 @@ namespace { struct WorkerPool { - std::unique_ptr<WorkerThreadPool> Pool; const int TreadCount; const std::string_view Name; + std::unique_ptr<WorkerThreadPool> Pool; }; WorkerPool BurstLargeWorkerPool = {.TreadCount = LargeWorkerThreadPoolTreadCount, .Name = "large"}; diff --git a/src/zenutil/zenserverprocess.cpp b/src/zenutil/zenserverprocess.cpp index b9c50be4f..ac614f779 100644 --- a/src/zenutil/zenserverprocess.cpp +++ b/src/zenutil/zenserverprocess.cpp @@ -449,6 +449,30 @@ ZenServerState::ZenServerEntry::IsReady() const return (Flags.load() & static_cast<uint16_t>(FlagsEnum::kIsReady)) != 0; } +void +ZenServerState::ZenServerEntry::SignalHasInstanceInfo() +{ + Flags |= uint16_t(FlagsEnum::kHasInstanceInfo); +} + +bool +ZenServerState::ZenServerEntry::HasInstanceInfo() const +{ + return (Flags.load() & static_cast<uint16_t>(FlagsEnum::kHasInstanceInfo)) != 0; +} + +void +ZenServerState::ZenServerEntry::SignalNoNetwork() +{ + Flags |= uint16_t(FlagsEnum::kNoNetwork); +} + +bool +ZenServerState::ZenServerEntry::IsNoNetwork() const +{ + return (Flags.load() & static_cast<uint16_t>(FlagsEnum::kNoNetwork)) != 0; +} + bool ZenServerState::ZenServerEntry::AddSponsorProcess(uint32_t PidToAdd, uint64_t Timeout) { @@ -492,6 +516,222 @@ ZenServerState::ZenServerEntry::AddSponsorProcess(uint32_t PidToAdd, uint64_t Ti } ////////////////////////////////////////////////////////////////////////// +// ZenServerInstanceInfo +////////////////////////////////////////////////////////////////////////// + +static constexpr size_t kInstanceInfoSize = 4096; + +ZenServerInstanceInfo::ZenServerInstanceInfo() = default; + +ZenServerInstanceInfo::~ZenServerInstanceInfo() +{ +#if ZEN_PLATFORM_WINDOWS + if (m_Data) + { + UnmapViewOfFile(m_Data); + } + if (m_hMapFile) + { + CloseHandle(m_hMapFile); + } +#else + if (m_Data != nullptr) + { + munmap(m_Data, kInstanceInfoSize); + } + if (m_hMapFile != nullptr) + { + int Fd = int(intptr_t(m_hMapFile)); + close(Fd); + } + if (m_IsOwner) + { + std::string Name = MakeName(m_SessionId); + shm_unlink(Name.c_str()); + } +#endif + m_Data = nullptr; +} + +std::string +ZenServerInstanceInfo::MakeName(const Oid& SessionId) +{ +#if ZEN_PLATFORM_WINDOWS + return fmt::format("Global\\ZenInstance_{}", SessionId); +#else + // macOS limits shm_open names to ~31 chars (PSHMNAMLEN), so keep this short. + // "/ZenI_" (6) + 24 hex = 30 chars, within the limit. + return fmt::format("/ZenI_{}", SessionId); +#endif +} + +void +ZenServerInstanceInfo::Create(const Oid& SessionId, const InstanceInfoData& Data) +{ + m_SessionId = SessionId; + m_IsOwner = true; + + // Serialize the data to compact binary + CbObjectWriter Cbo; + if (!Data.UnixSocketPath.empty()) + { + Cbo << "unix_socket" << PathToUtf8(Data.UnixSocketPath); + } + CbObject Payload = Cbo.Save(); + + MemoryView PayloadView = Payload.GetView(); + uint32_t PayloadSize = gsl::narrow<uint32_t>(PayloadView.GetSize()); + + std::string Name = MakeName(SessionId); + +#if ZEN_PLATFORM_WINDOWS + zenutil::AnyUserSecurityAttributes Attrs; + + std::wstring WideName(Name.begin(), Name.end()); + + HANDLE hMap = + CreateFileMappingW(INVALID_HANDLE_VALUE, Attrs.Attributes(), PAGE_READWRITE, 0, DWORD(kInstanceInfoSize), WideName.c_str()); + + if (hMap == NULL) + { + // Fall back to Local namespace + std::string LocalName = fmt::format("Local\\ZenInstance_{}", SessionId); + std::wstring WideLocalName(LocalName.begin(), LocalName.end()); + hMap = CreateFileMappingW(INVALID_HANDLE_VALUE, + Attrs.Attributes(), + PAGE_READWRITE, + 0, + DWORD(kInstanceInfoSize), + WideLocalName.c_str()); + } + + if (hMap == NULL) + { + ThrowLastError("Could not create instance info shared memory"); + } + + void* pBuf = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, DWORD(kInstanceInfoSize)); + if (pBuf == NULL) + { + CloseHandle(hMap); + ThrowLastError("Could not map instance info shared memory"); + } +#else + int Fd = shm_open(Name.c_str(), O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0666); + if (Fd < 0) + { + ThrowLastError("Could not create instance info shared memory"); + } + fchmod(Fd, 0666); + + if (ftruncate(Fd, kInstanceInfoSize) < 0) + { + close(Fd); + shm_unlink(Name.c_str()); + ThrowLastError("Could not resize instance info shared memory"); + } + + void* pBuf = mmap(nullptr, kInstanceInfoSize, PROT_READ | PROT_WRITE, MAP_SHARED, Fd, 0); + if (pBuf == MAP_FAILED) + { + close(Fd); + shm_unlink(Name.c_str()); + ThrowLastError("Could not map instance info shared memory"); + } + + void* hMap = reinterpret_cast<void*>(intptr_t(Fd)); +#endif + + m_hMapFile = hMap; + m_Data = reinterpret_cast<uint8_t*>(pBuf); + + // Write payload: [uint32_t size][compact binary bytes] + memcpy(m_Data, &PayloadSize, sizeof PayloadSize); + if (PayloadSize > 0) + { + memcpy(m_Data + sizeof(uint32_t), PayloadView.GetData(), PayloadSize); + } +} + +bool +ZenServerInstanceInfo::OpenReadOnly(const Oid& SessionId) +{ + m_SessionId = SessionId; + + std::string Name = MakeName(SessionId); + +#if ZEN_PLATFORM_WINDOWS + std::wstring WideName(Name.begin(), Name.end()); + + HANDLE hMap = OpenFileMappingW(FILE_MAP_READ, FALSE, WideName.c_str()); + if (hMap == NULL) + { + // Fall back to Local namespace + std::string LocalName = fmt::format("Local\\ZenInstance_{}", SessionId); + std::wstring WideLocalName(LocalName.begin(), LocalName.end()); + hMap = OpenFileMappingW(FILE_MAP_READ, FALSE, WideLocalName.c_str()); + } + + if (hMap == NULL) + { + return false; + } + + void* pBuf = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, DWORD(kInstanceInfoSize)); + if (pBuf == NULL) + { + CloseHandle(hMap); + return false; + } +#else + int Fd = shm_open(Name.c_str(), O_RDONLY | O_CLOEXEC, 0666); + if (Fd < 0) + { + return false; + } + + void* pBuf = mmap(nullptr, kInstanceInfoSize, PROT_READ, MAP_SHARED, Fd, 0); + if (pBuf == MAP_FAILED) + { + close(Fd); + return false; + } + + void* hMap = reinterpret_cast<void*>(intptr_t(Fd)); +#endif + + m_hMapFile = hMap; + m_Data = reinterpret_cast<uint8_t*>(pBuf); + m_IsOwner = false; + + return true; +} + +InstanceInfoData +ZenServerInstanceInfo::Read() const +{ + InstanceInfoData Result; + + if (m_Data == nullptr) + { + return Result; + } + + uint32_t PayloadSize = 0; + memcpy(&PayloadSize, m_Data, sizeof PayloadSize); + + if (PayloadSize == 0 || PayloadSize > kInstanceInfoSize - sizeof(uint32_t)) + { + return Result; + } + + CbObject Payload = CbObject::Clone(m_Data + sizeof(uint32_t)); + Result.UnixSocketPath = Payload["unix_socket"].AsU8String(); + + return Result; +} + +////////////////////////////////////////////////////////////////////////// std::atomic<int> ZenServerTestCounter{0}; @@ -1234,6 +1474,10 @@ MakeLockFilePayload(const LockFileInfo& Info) CbObjectWriter Cbo; Cbo << "pid" << Info.Pid << "data" << PathToUtf8(Info.DataDir) << "port" << Info.EffectiveListenPort << "session_id" << Info.SessionId << "ready" << Info.Ready << "executable" << PathToUtf8(Info.ExecutablePath); + if (!Info.UnixSocketPath.empty()) + { + Cbo << "unix_socket" << PathToUtf8(Info.UnixSocketPath); + } return Cbo.Save(); } LockFileInfo @@ -1246,6 +1490,7 @@ ReadLockFilePayload(const CbObject& Payload) Info.Ready = Payload["ready"].AsBool(); Info.DataDir = Payload["data"].AsU8String(); Info.ExecutablePath = Payload["executable"].AsU8String(); + Info.UnixSocketPath = Payload["unix_socket"].AsU8String(); return Info; } @@ -1275,7 +1520,7 @@ ValidateLockFileInfo(const LockFileInfo& Info, std::string& OutReason) OutReason = fmt::format("session id ({}) is not valid", Info.SessionId); return false; } - if (Info.EffectiveListenPort == 0) + if (Info.EffectiveListenPort == 0 && Info.UnixSocketPath.empty()) { OutReason = fmt::format("listen port ({}) is not valid", Info.EffectiveListenPort); return false; diff --git a/thirdparty/xmake.lua b/thirdparty/xmake.lua index 1fb5acad7..59233649a 100644 --- a/thirdparty/xmake.lua +++ b/thirdparty/xmake.lua @@ -37,7 +37,7 @@ target('rpmalloc') set_group('thirdparty') set_languages("c17", "cxx20") if is_os("windows") then - add_cflags("/experimental:c11atomics", {force=true}) + add_cflags("/experimental:c11atomics", {force=true, tools="cl"}) end add_defines("RPMALLOC_FIRST_CLASS_HEAPS=1", "ENABLE_STATISTICS=1", "ENABLE_OVERRIDE=0") add_files("rpmalloc/rpmalloc.c") @@ -79,7 +79,7 @@ target("blake3") add_includedirs("blake3/c", {public=true}) if is_os("windows") then - add_cflags("/experimental:c11atomics") + add_cflags("/experimental:c11atomics", {tools="cl"}) add_cflags("/wd4245", {force = true}) -- conversion from 'type1' to 'type2', possible loss of data elseif is_os("macosx") then add_cflags("-Wno-unused-function") @@ -135,6 +135,9 @@ target("fmt") set_kind("static") set_group("thirdparty") set_warnings("allextra") + if is_plat("windows") then + add_cxxflags("/wd4834", {force=true}) -- C4834: discarding return value of [[nodiscard]] function + end add_files("fmt/src/format.cc", "fmt/src/os.cc") add_headerfiles("fmt/include/**.h") add_includedirs("fmt/include", {public=true}) @@ -192,6 +192,30 @@ if is_os("linux") or is_os("macosx") then add_cxxflags("-Wno-vla-cxx-extension") end +if get_config("toolchain") == "clang-cl" then + add_cxxflags("-Wno-unknown-warning-option", {force=true}) + add_cxxflags("-Wno-cast-function-type-mismatch", {force=true}) + add_cxxflags("-Wno-delete-non-abstract-non-virtual-dtor", {force=true}) + add_cxxflags("-Wno-format", {force=true}) + add_cxxflags("-Wno-implicit-fallthrough", {force=true}) + add_cxxflags("-Wno-inconsistent-missing-override", {force=true}) + add_cxxflags("-Wno-missing-field-initializers", {force=true}) + add_cxxflags("-Wno-parentheses-equality", {force=true}) + add_cxxflags("-Wno-reorder-ctor", {force=true}) + add_cxxflags("-Wno-sign-compare", {force=true}) + add_cxxflags("-Wno-strict-aliasing", {force=true}) + add_cxxflags("-Wno-switch", {force=true}) + add_cxxflags("-Wno-unused-but-set-variable", {force=true}) + add_cxxflags("-Wno-unused-lambda-capture", {force=true}) + add_cxxflags("-Wno-unused-parameter", {force=true}) + add_cxxflags("-Wno-unused-private-field", {force=true}) + add_cxxflags("-Wno-unused-value", {force=true}) + add_cxxflags("-Wno-unused-variable", {force=true}) + add_cxxflags("-Wno-vla-cxx-extension", {force=true}) + add_cflags("-Wno-unknown-warning-option", {force=true}) + add_cflags("-Wno-unused-command-line-argument", {force=true}) +end + if is_os("macosx") then -- silence warnings about -Wno-vla-cxx-extension since to my knowledge we can't -- detect the clang version used in Xcode and only recent versions contain this flag @@ -289,6 +313,13 @@ option("zentrace") option_end() add_define_by_config("ZEN_WITH_TRACE", "zentrace") +option("zencpr") + set_default(true) + set_showmenu(true) + set_description("Enable CPR HTTP client backend") +option_end() +add_define_by_config("ZEN_WITH_CPR", "zencpr") + set_warnings("allextra", "error") set_languages("cxx20") @@ -333,7 +364,9 @@ end includes("src/zenstore", "src/zenstore-test") includes("src/zentelemetry", "src/zentelemetry-test") includes("src/zenutil", "src/zenutil-test") -includes("src/zenvfs") +if is_plat("windows") then + includes("src/zenvfs") +end includes("src/zenserver", "src/zenserver-test") includes("src/zen") includes("src/zentest-appstub") @@ -371,16 +404,6 @@ task("kill") end end) -task("updatefrontend") - set_menu { - usage = "xmake updatefrontend", - description = "Create Zip of the frontend/html folder for bundling with zenserver executable", - } - on_run(function() - import("scripts.updatefrontend") - updatefrontend() - end) - task("precommit") set_menu { usage = "xmake precommit", @@ -524,7 +547,7 @@ task("test") if config.get("mode") ~= "debug" or config.get("plat") ~= plat or config.get("arch") ~= arch then local toolchain_flag = config.get("toolchain") and ("--toolchain=" .. config.get("toolchain")) or "" local sdk_flag = config.get("sdk") and ("--sdk=" .. config.get("sdk")) or "" - os.exec("xmake config -c -m debug -p %s -a %s %s %s", plat, arch, toolchain_flag, sdk_flag) + os.exec("xmake config -y -c -m debug -p %s -a %s %s %s", plat, arch, toolchain_flag, sdk_flag) end -- Build targets we're going to run |