aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--CLAUDE.md4
-rw-r--r--scripts/updatefrontend.lua111
-rw-r--r--src/transports/winsock/winsock.cpp4
-rw-r--r--src/zen/cmds/admin_cmd.cpp28
-rw-r--r--src/zen/cmds/cache_cmd.cpp24
-rw-r--r--src/zen/cmds/exec_cmd.cpp2
-rw-r--r--src/zen/cmds/info_cmd.cpp4
-rw-r--r--src/zen/cmds/projectstore_cmd.cpp50
-rw-r--r--src/zen/cmds/rpcreplay_cmd.cpp14
-rw-r--r--src/zen/cmds/serve_cmd.cpp4
-rw-r--r--src/zen/cmds/status_cmd.cpp20
-rw-r--r--src/zen/cmds/top_cmd.cpp36
-rw-r--r--src/zen/cmds/trace_cmd.cpp4
-rw-r--r--src/zen/cmds/ui_cmd.cpp7
-rw-r--r--src/zen/cmds/version_cmd.cpp4
-rw-r--r--src/zen/cmds/vfs_cmd.cpp4
-rw-r--r--src/zen/cmds/workspaces_cmd.cpp24
-rw-r--r--src/zen/zen.cpp48
-rw-r--r--src/zen/zen.h6
-rw-r--r--src/zencore/include/zencore/logbase.h2
-rw-r--r--src/zencore/include/zencore/string.h20
-rw-r--r--src/zencore/memtrack/callstacktrace.cpp146
-rw-r--r--src/zencore/string.cpp11
-rw-r--r--src/zenhttp/clients/httpclientcpr.cpp3
-rw-r--r--src/zenhttp/clients/httpclientcurl.cpp1100
-rw-r--r--src/zenhttp/clients/httpclientcurl.h25
-rw-r--r--src/zenhttp/clients/httpwsclient.cpp4
-rw-r--r--src/zenhttp/httpclient.cpp19
-rw-r--r--src/zenhttp/httpserver.cpp4
-rw-r--r--src/zenhttp/include/zenhttp/cprutils.h22
-rw-r--r--src/zenhttp/include/zenhttp/httpclient.h14
-rw-r--r--src/zenhttp/include/zenhttp/httpserver.h3
-rw-r--r--src/zenhttp/include/zenhttp/httpwsclient.h2
-rw-r--r--src/zenhttp/servers/httpplugin.cpp2
-rw-r--r--src/zenhttp/xmake.lua7
-rw-r--r--src/zenserver/config/config.cpp10
-rw-r--r--src/zenserver/frontend/html.zipbin431365 -> 0 bytes
-rw-r--r--src/zenserver/xmake.lua48
-rw-r--r--src/zenserver/zenserver.cpp17
-rw-r--r--src/zenserver/zenserver.h5
-rw-r--r--src/zenstore/xmake.lua7
-rw-r--r--src/zenutil/include/zenutil/zenserverprocess.h56
-rw-r--r--src/zenutil/workerpools.cpp2
-rw-r--r--src/zenutil/zenserverprocess.cpp247
-rw-r--r--thirdparty/xmake.lua7
-rw-r--r--xmake.lua47
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
diff --git a/CLAUDE.md b/CLAUDE.md
index 32c4b3d40..45a9bddce 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -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
deleted file mode 100644
index 58778a592..000000000
--- a/src/zenserver/frontend/html.zip
+++ /dev/null
Binary files differ
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})
diff --git a/xmake.lua b/xmake.lua
index 216ad8fb2..61e5301d4 100644
--- a/xmake.lua
+++ b/xmake.lua
@@ -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