aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/transports/transport-sdk/include/transportplugin.h42
-rw-r--r--src/transports/winsock/winsock.cpp16
-rw-r--r--src/zenhttp/httpserver.cpp177
-rw-r--r--src/zenhttp/include/zenhttp/httpserver.h15
-rw-r--r--src/zenhttp/transports/dlltransport.cpp81
-rw-r--r--src/zenserver/config.cpp72
-rw-r--r--src/zenserver/config.h1
7 files changed, 295 insertions, 109 deletions
diff --git a/src/transports/transport-sdk/include/transportplugin.h b/src/transports/transport-sdk/include/transportplugin.h
index 4347868e6..a78a758bc 100644
--- a/src/transports/transport-sdk/include/transportplugin.h
+++ b/src/transports/transport-sdk/include/transportplugin.h
@@ -17,10 +17,14 @@
namespace zen {
+// Current API version, value will be incremented to represent breaking changes
+static const uint32_t kTransportApiVersion = 1;
+
class TransportConnection;
class TransportPlugin;
class TransportServerConnection;
class TransportServer;
+class TransportLogger;
/*************************************************************************
@@ -60,6 +64,29 @@ public:
virtual TransportServerConnection* CreateConnectionHandler(TransportConnection* Connection) = 0;
};
+/** Logger interface
+
+ There will be one instance of this provided by the system to the transport plugin
+
+ The plugin can use this to log messages back to zen server
+
+ */
+class TransportLogger
+{
+public:
+ enum class LogLevel : uint32_t
+ {
+ Trace = 0,
+ Debug = 1,
+ Info = 2,
+ Warn = 3,
+ Err = 4,
+ Critical = 5,
+ };
+
+ virtual void LogMessage(LogLevel Level, const char* Message) = 0;
+};
+
/*************************************************************************
The following interfaces are to be implemented by transport plugins.
@@ -116,7 +143,18 @@ public:
extern "C"
{
- DLL_TRANSPORT_API zen::TransportPlugin* CreateTransportPlugin();
+ /** Provide information about plugin version
+
+ Fills out API version (kTransportApiVersion) plugin was built against.
+ Fills out plugin own version ever increasing version number,
+ a copy of plugin with higher version will be used.
+ */
+ DLL_TRANSPORT_API void GetTransportPluginVersion(uint32_t* OutApiVersion, uint32_t* OutPluginVersion);
+
+ // Return nullptr if requested api version mismatches api version plugin was built against
+ DLL_TRANSPORT_API zen::TransportPlugin* CreateTransportPlugin(zen::TransportLogger* Logger);
}
-typedef zen::TransportPlugin* (*PfnCreateTransportPlugin)();
+typedef void (*PfnGetTransportPluginVersion)(uint32_t* OutApiVersion, uint32_t* OutPluginVersion);
+
+typedef zen::TransportPlugin* (*PfnCreateTransportPlugin)(zen::TransportLogger* Logger);
diff --git a/src/transports/winsock/winsock.cpp b/src/transports/winsock/winsock.cpp
index 1c3ee909a..f98984726 100644
--- a/src/transports/winsock/winsock.cpp
+++ b/src/transports/winsock/winsock.cpp
@@ -364,8 +364,22 @@ WinsockTransportPlugin::IsAvailable()
//////////////////////////////////////////////////////////////////////////
+void
+GetTransportPluginVersion(uint32_t* OutApiVersion, uint32_t* OutPluginVersion)
+{
+ if (OutApiVersion != nullptr)
+ {
+ *OutApiVersion = kTransportApiVersion;
+ }
+
+ if (OutPluginVersion != nullptr)
+ {
+ *OutPluginVersion = 1;
+ }
+}
+
TransportPlugin*
-CreateTransportPlugin()
+CreateTransportPlugin([[maybe_unused]] TransportLogger* Logger)
{
return new WinsockTransportPlugin;
}
diff --git a/src/zenhttp/httpserver.cpp b/src/zenhttp/httpserver.cpp
index 27a09f339..d12f89a92 100644
--- a/src/zenhttp/httpserver.cpp
+++ b/src/zenhttp/httpserver.cpp
@@ -787,120 +787,115 @@ HttpRpcHandler::AddRpc(std::string_view RpcId, std::function<void(CbObject& RpcA
//////////////////////////////////////////////////////////////////////////
-enum class HttpServerClass
-{
- kHttpAsio,
- kHttpSys,
- kHttpPlugin,
- kHttpMulti,
- kHttpNull
-};
-
Ref<HttpServer>
-CreateHttpServerClass(HttpServerClass Class, const HttpServerConfig& Config)
+CreateHttpServerClass(const std::string_view ServerClass, const HttpServerConfig& Config)
{
- switch (Class)
+ if (ServerClass == "asio"sv)
{
- default:
- case HttpServerClass::kHttpAsio:
- ZEN_INFO("using asio HTTP server implementation");
- return CreateHttpAsioServer(Config.ForceLoopback, Config.ThreadCount);
-
- case HttpServerClass::kHttpMulti:
- {
- ZEN_INFO("using multi HTTP server implementation");
- Ref<HttpMultiServer> Server{new HttpMultiServer()};
-
- // This is hardcoded for now, but should be configurable in the future
- Server->AddServer(CreateHttpServerClass(HttpServerClass::kHttpSys, Config));
- Server->AddServer(CreateHttpServerClass(HttpServerClass::kHttpPlugin, Config));
+ ZEN_INFO("using asio HTTP server implementation")
+ return CreateHttpAsioServer(Config.ForceLoopback, Config.ThreadCount);
+ }
+#if ZEN_WITH_HTTPSYS
+ else if (ServerClass == "httpsys"sv)
+ {
+ ZEN_INFO("using http.sys server implementation")
+ return Ref<HttpServer>(CreateHttpSysServer({.ThreadCount = Config.ThreadCount,
+ .AsyncWorkThreadCount = Config.HttpSys.AsyncWorkThreadCount,
+ .IsAsyncResponseEnabled = Config.HttpSys.IsAsyncResponseEnabled,
+ .IsRequestLoggingEnabled = Config.HttpSys.IsRequestLoggingEnabled,
+ .IsDedicatedServer = Config.IsDedicatedServer,
+ .ForceLoopback = Config.ForceLoopback}));
+ }
+#endif
+ else if (ServerClass == "null"sv)
+ {
+ ZEN_INFO("using null HTTP server implementation")
+ return Ref<HttpServer>(new HttpNullServer);
+ }
+ else
+ {
+ ZEN_WARN("unknown HTTP server implementation '{}', falling back to default", ServerClass)
- return Server;
- }
+#if ZEN_WITH_HTTPSYS
+ return CreateHttpServerClass("httpsys"sv, Config);
+#else
+ return CreateHttpServerClass("asio"sv, Config);
+#endif
+ }
+}
#if ZEN_WITH_PLUGINS
- case HttpServerClass::kHttpPlugin:
- {
- ZEN_INFO("using plugin HTTP server implementation");
- Ref<HttpPluginServer> Server{CreateHttpPluginServer()};
-
- // This is hardcoded for now, but should be configurable in the future
+Ref<HttpServer>
+CreateHttpServerPlugin(const HttpServerPluginConfig& PluginConfig)
+{
+ const std::string& PluginName = PluginConfig.PluginName;
-# if 0
- Ref<TransportPlugin> WinsockPlugin{CreateSocketTransportPlugin()};
- WinsockPlugin->Configure("port", "8558");
- Server->AddPlugin(WinsockPlugin);
-# endif
+ ZEN_INFO("using '{}' plugin HTTP server implementation", PluginName)
+ Ref<HttpPluginServer> Server{CreateHttpPluginServer()};
+ if (PluginName.starts_with("builtin:"sv))
+ {
# if 0
- Ref<TransportPlugin> AsioPlugin{CreateAsioTransportPlugin()};
- AsioPlugin->Configure("port", "8558");
- Server->AddPlugin(AsioPlugin);
-# endif
-
-# if 1
- Ref<DllTransportPlugin> DllPlugin{CreateDllTransportPlugin()};
- DllPlugin->LoadDll("winsock");
- DllPlugin->ConfigureDll("winsock", "port", "8558");
- Server->AddPlugin(DllPlugin);
-# endif
+ Ref<TransportPlugin> Plugin = {};
+ if (PluginName == "builtin:winsock"sv)
+ {
+ Plugin = CreateSocketTransportPlugin();
+ }
+ else if (PluginName == "builtin:asio"sv)
+ {
+ Plugin = CreateAsioTransportPlugin();
+ }
- return Server;
+ if (!Plugin.IsNull())
+ {
+ for (const std::pair<std::string, std::string>& Option : PluginConfig.PluginOptions)
+ {
+ Plugin->Configure(Option.first.c_str(), Option.second.c_str());
}
-#endif
-
-#if ZEN_WITH_HTTPSYS
- case HttpServerClass::kHttpSys:
- ZEN_INFO("using http.sys server implementation");
- return Ref<HttpServer>(CreateHttpSysServer({.ThreadCount = Config.ThreadCount,
- .AsyncWorkThreadCount = Config.HttpSys.AsyncWorkThreadCount,
- .IsAsyncResponseEnabled = Config.HttpSys.IsAsyncResponseEnabled,
- .IsRequestLoggingEnabled = Config.HttpSys.IsRequestLoggingEnabled,
- .IsDedicatedServer = Config.IsDedicatedServer,
- .ForceLoopback = Config.ForceLoopback}));
-#endif
-
- case HttpServerClass::kHttpNull:
- ZEN_INFO("using null HTTP server implementation");
- return Ref<HttpServer>(new HttpNullServer);
+ Server->AddPlugin(Plugin);
+ }
+# endif
}
+ else
+ {
+ Ref<DllTransportPlugin> DllPlugin{CreateDllTransportPlugin()};
+ DllPlugin->LoadDll(PluginName);
+ for (const std::pair<std::string, std::string>& Option : PluginConfig.PluginOptions)
+ {
+ DllPlugin->ConfigureDll(PluginName, Option.first.c_str(), Option.second.c_str());
+ }
+ Server->AddPlugin(DllPlugin);
+ }
+
+ return Server;
}
+#endif
Ref<HttpServer>
CreateHttpServer(const HttpServerConfig& Config)
{
using namespace std::literals;
- HttpServerClass Class = HttpServerClass::kHttpNull;
-
-#if ZEN_WITH_HTTPSYS
- Class = HttpServerClass::kHttpSys;
-#else
- Class = HttpServerClass::kHttpAsio;
-#endif
-
- if (Config.ServerClass == "asio"sv)
- {
- Class = HttpServerClass::kHttpAsio;
- }
- else if (Config.ServerClass == "httpsys"sv)
- {
- Class = HttpServerClass::kHttpSys;
- }
- else if (Config.ServerClass == "plugin"sv)
- {
- Class = HttpServerClass::kHttpPlugin;
- }
- else if (Config.ServerClass == "null"sv)
+#if ZEN_WITH_PLUGINS
+ if (Config.PluginConfigs.empty())
{
- Class = HttpServerClass::kHttpNull;
+ return CreateHttpServerClass(Config.ServerClass, Config);
}
- else if (Config.ServerClass == "multi"sv)
+ else
{
- Class = HttpServerClass::kHttpMulti;
- }
+ Ref<HttpMultiServer> Server{new HttpMultiServer()};
+ Server->AddServer(CreateHttpServerClass(Config.ServerClass, Config));
- return CreateHttpServerClass(Class, Config);
+ for (const HttpServerPluginConfig& PluginConfig : Config.PluginConfigs)
+ {
+ Server->AddServer(CreateHttpServerPlugin(PluginConfig));
+ }
+
+ return Server;
+ }
+#else
+ return CreateHttpServerClass(Config.ServerClass, Config);
+#endif
}
//////////////////////////////////////////////////////////////////////////
diff --git a/src/zenhttp/include/zenhttp/httpserver.h b/src/zenhttp/include/zenhttp/httpserver.h
index 217455dba..03e547bf3 100644
--- a/src/zenhttp/include/zenhttp/httpserver.h
+++ b/src/zenhttp/include/zenhttp/httpserver.h
@@ -184,12 +184,19 @@ public:
virtual void Close() = 0;
};
+struct HttpServerPluginConfig
+{
+ std::string PluginName;
+ std::vector<std::pair<std::string, std::string>> PluginOptions;
+};
+
struct HttpServerConfig
{
- bool IsDedicatedServer = false; // Should be set to true for shared servers
- std::string ServerClass; // Choice of HTTP server implementation
- bool ForceLoopback = false;
- unsigned int ThreadCount = 0;
+ bool IsDedicatedServer = false; // Should be set to true for shared servers
+ std::string ServerClass; // Choice of HTTP server implementation
+ std::vector<HttpServerPluginConfig> PluginConfigs;
+ bool ForceLoopback = false;
+ unsigned int ThreadCount = 0;
struct
{
diff --git a/src/zenhttp/transports/dlltransport.cpp b/src/zenhttp/transports/dlltransport.cpp
index e09e62ec5..dd05f6f0c 100644
--- a/src/zenhttp/transports/dlltransport.cpp
+++ b/src/zenhttp/transports/dlltransport.cpp
@@ -21,18 +21,31 @@ namespace zen {
//////////////////////////////////////////////////////////////////////////
+class DllTransportLogger : public TransportLogger, public RefCounted
+{
+public:
+ DllTransportLogger(std::string_view PluginName);
+ virtual ~DllTransportLogger() = default;
+
+ void LogMessage(LogLevel Level, const char* Message) override;
+
+private:
+ std::string m_PluginName;
+};
+
struct LoadedDll
{
std::string Name;
std::filesystem::path LoadedFromPath;
+ DllTransportLogger* Logger = nullptr;
Ref<TransportPlugin> Plugin;
};
class DllTransportPluginImpl : public DllTransportPlugin, RefCounted
{
public:
- DllTransportPluginImpl();
- ~DllTransportPluginImpl();
+ DllTransportPluginImpl() = default;
+ ~DllTransportPluginImpl() = default;
virtual uint32_t AddRef() const override;
virtual uint32_t Release() const override;
@@ -51,12 +64,27 @@ private:
std::vector<LoadedDll> m_Transports;
};
-DllTransportPluginImpl::DllTransportPluginImpl()
+DllTransportLogger::DllTransportLogger(std::string_view PluginName) : m_PluginName(PluginName)
{
}
-DllTransportPluginImpl::~DllTransportPluginImpl()
+void
+DllTransportLogger::LogMessage(LogLevel PluginLogLevel, const char* Message)
{
+ logging::level::LogLevel Level;
+ // clang-format off
+ switch (PluginLogLevel)
+ {
+ case LogLevel::Trace: Level = logging::level::Trace; break;
+ case LogLevel::Debug: Level = logging::level::Debug; break;
+ case LogLevel::Info: Level = logging::level::Info; break;
+ case LogLevel::Warn: Level = logging::level::Warn; break;
+ case LogLevel::Err: Level = logging::level::Err; break;
+ case LogLevel::Critical: Level = logging::level::Critical; break;
+ default: Level = logging::level::Off; break;
+ }
+ // clang-format on
+ ZEN_LOG(Log(), Level, "[{}] {}", m_PluginName, Message)
}
uint32_t
@@ -109,6 +137,7 @@ DllTransportPluginImpl::Shutdown()
try
{
Transport.Plugin->Shutdown();
+ Transport.Logger->Release();
}
catch (const std::exception&)
{
@@ -148,8 +177,15 @@ DllTransportPluginImpl::LoadDll(std::string_view Name)
{
RwLock::ExclusiveLockScope _(m_Lock);
- ExtendableStringBuilder<128> DllPath;
- DllPath << Name << ".dll";
+ ExtendableStringBuilder<1024> DllPath;
+ DllPath << Name;
+ if (!Name.ends_with(".dll"))
+ {
+ DllPath << ".dll";
+ }
+
+ std::string FileName = std::filesystem::path(DllPath.c_str()).filename().replace_extension().string();
+
HMODULE DllHandle = LoadLibraryA(DllPath.c_str());
if (!DllHandle)
@@ -159,24 +195,47 @@ DllTransportPluginImpl::LoadDll(std::string_view Name)
throw std::system_error(Ec, fmt::format("failed to load transport DLL from '{}'", DllPath));
}
- TransportPlugin* CreateTransportPlugin();
+ PfnGetTransportPluginVersion GetVersion = (PfnGetTransportPluginVersion)GetProcAddress(DllHandle, "GetTransportPluginVersion");
+ PfnCreateTransportPlugin CreatePlugin = (PfnCreateTransportPlugin)GetProcAddress(DllHandle, "CreateTransportPlugin");
- PfnCreateTransportPlugin CreatePlugin = (PfnCreateTransportPlugin)GetProcAddress(DllHandle, "CreateTransportPlugin");
+ uint32_t APIVersion = 0;
+ uint32_t PluginVersion = 0;
- if (!CreatePlugin)
+ if (GetVersion)
+ {
+ GetVersion(&APIVersion, &PluginVersion);
+ }
+
+ const bool bValidApiVersion = APIVersion == kTransportApiVersion;
+
+ if (!GetVersion || !CreatePlugin || !bValidApiVersion)
{
std::error_code Ec = MakeErrorCodeFromLastError();
FreeLibrary(DllHandle);
- throw std::system_error(Ec, fmt::format("API mismatch detected in transport DLL loaded from '{}'", DllPath));
+ if (GetVersion && !bValidApiVersion)
+ {
+ throw std::system_error(
+ Ec,
+ fmt::format("invalid API version '{}' detected in transport DLL loaded from '{}', supported API version '{}'",
+ APIVersion,
+ DllPath,
+ kTransportApiVersion));
+ }
+ else
+ {
+ throw std::system_error(Ec, fmt::format("API mismatch detected in transport DLL loaded from '{}'", DllPath));
+ }
}
LoadedDll NewDll;
NewDll.Name = Name;
NewDll.LoadedFromPath = DllPath.c_str();
- NewDll.Plugin = CreatePlugin();
+ NewDll.Logger = new DllTransportLogger(FileName);
+ NewDll.Logger->AddRef();
+ NewDll.Plugin = CreatePlugin(NewDll.Logger);
m_Transports.emplace_back(std::move(NewDll));
}
diff --git a/src/zenserver/config.cpp b/src/zenserver/config.cpp
index fecc1043a..9a12719c0 100644
--- a/src/zenserver/config.cpp
+++ b/src/zenserver/config.cpp
@@ -22,6 +22,7 @@ ZEN_THIRD_PARTY_INCLUDES_START
#include <fmt/ranges.h>
#include <zencore/logging.h>
#include <cxxopts.hpp>
+#include <json11.hpp>
#include <sol/sol.hpp>
ZEN_THIRD_PARTY_INCLUDES_END
@@ -349,6 +350,7 @@ ParseConfigFile(const std::filesystem::path& Path,
LuaOptions.AddOption("server.datadir"sv, ServerOptions.DataDir, "data-dir"sv);
LuaOptions.AddOption("server.contentdir"sv, ServerOptions.ContentDir, "content-dir"sv);
LuaOptions.AddOption("server.abslog"sv, ServerOptions.AbsLogFile, "abslog"sv);
+ LuaOptions.AddOption("server.pluginsconfigfile"sv, ServerOptions.PluginsConfigFile, "plugins-config"sv);
LuaOptions.AddOption("server.debug"sv, ServerOptions.IsDebug, "debug"sv);
LuaOptions.AddOption("server.clean"sv, ServerOptions.IsCleanStart, "clean"sv);
LuaOptions.AddOption("server.noconsole"sv, ServerOptions.NoConsoleOutput, "quiet"sv);
@@ -515,6 +517,68 @@ ParseConfigFile(const std::filesystem::path& Path,
}
void
+ParsePluginsConfigFile(const std::filesystem::path& Path, ZenServerOptions& ServerOptions, int BasePort)
+{
+ using namespace std::literals;
+
+ IoBuffer Body = IoBufferBuilder::MakeFromFile(Path);
+ std::string JsonText(reinterpret_cast<const char*>(Body.GetData()), Body.GetSize());
+ std::string JsonError;
+ json11::Json PluginsInfo = json11::Json::parse(JsonText, JsonError);
+ if (!JsonError.empty())
+ {
+ throw std::runtime_error(fmt::format("failed parsing json file '{}'. Reason: '{}'", Path, JsonError));
+ }
+ for (const json11::Json& PluginInfo : PluginsInfo.array_items())
+ {
+ if (!PluginInfo.is_object())
+ {
+ throw std::runtime_error(fmt::format("the json file '{}' does not contain a valid plugin definition, object expected, got '{}'",
+ Path,
+ PluginInfo.dump()));
+ }
+
+ HttpServerPluginConfig Config = {};
+
+ bool bNeedsPort = true;
+
+ for (const std::pair<const std::string, json11::Json>& Items : PluginInfo.object_items())
+ {
+ if (!Items.second.is_string())
+ {
+ throw std::runtime_error(
+ fmt::format("the json file '{}' does not contain a valid plugins definition, string expected, got '{}'",
+ Path,
+ Items.second.dump()));
+ }
+
+ const std::string& Name = Items.first;
+ const std::string& Value = Items.second.string_value();
+
+ if (Name == "name"sv)
+ Config.PluginName = Value;
+ else
+ {
+ Config.PluginOptions.push_back({Name, Value});
+
+ if (Name == "port"sv)
+ {
+ bNeedsPort = false;
+ }
+ }
+ }
+
+ // add a default base port in case if json config didn't provide one
+ if (bNeedsPort)
+ {
+ Config.PluginOptions.push_back({"port", std::to_string(BasePort)});
+ }
+
+ ServerOptions.HttpServerConfig.PluginConfigs.push_back(Config);
+ }
+}
+
+void
ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions)
{
const char* DefaultHttp = "asio";
@@ -546,6 +610,7 @@ ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions)
std::string ContentDir;
std::string AbsLogFile;
std::string ConfigFile;
+ std::string PluginsConfigFile;
std::string OutputConfigFile;
std::string BaseSnapshotDir;
@@ -573,6 +638,7 @@ ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions)
"Exit immediately after initialization is complete",
cxxopts::value<bool>(ServerOptions.IsPowerCycle));
options.add_options()("config", "Path to Lua config file", cxxopts::value<std::string>(ConfigFile));
+ options.add_options()("plugins-config", "Path to plugins config file", cxxopts::value<std::string>(PluginsConfigFile));
options.add_options()("write-config", "Path to output Lua config file", cxxopts::value<std::string>(OutputConfigFile));
options.add_options()("no-sentry",
"Disable Sentry crash handler",
@@ -1096,6 +1162,7 @@ ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions)
ServerOptions.ContentDir = MakeSafeAbsolutePath(ContentDir);
ServerOptions.AbsLogFile = MakeSafeAbsolutePath(AbsLogFile);
ServerOptions.ConfigFile = MakeSafeAbsolutePath(ConfigFile);
+ ServerOptions.PluginsConfigFile = MakeSafeAbsolutePath(PluginsConfigFile);
ServerOptions.UpstreamCacheConfig.CachePolicy = ParseUpstreamCachePolicy(UpstreamCachePolicyOptions);
if (!BaseSnapshotDir.empty())
@@ -1129,6 +1196,11 @@ ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions)
ParseConfigFile(ServerOptions.DataDir / "zen_cfg.lua", ServerOptions, Result, OutputConfigFile);
}
+ if (!ServerOptions.PluginsConfigFile.empty())
+ {
+ ParsePluginsConfigFile(ServerOptions.PluginsConfigFile, ServerOptions, ServerOptions.BasePort);
+ }
+
ValidateOptions(ServerOptions);
}
catch (const zen::OptionParseException& e)
diff --git a/src/zenserver/config.h b/src/zenserver/config.h
index 6bd7aa357..bd277cd88 100644
--- a/src/zenserver/config.h
+++ b/src/zenserver/config.h
@@ -165,6 +165,7 @@ struct ZenServerOptions
std::filesystem::path ContentDir; // Root directory for serving frontend content (experimental)
std::filesystem::path AbsLogFile; // Absolute path to main log file
std::filesystem::path ConfigFile; // Path to Lua config file
+ std::filesystem::path PluginsConfigFile; // Path to plugins config file
std::filesystem::path BaseSnapshotDir; // Path to server state snapshot (will be copied into data dir on start)
std::string ChildId; // Id assigned by parent process (used for lifetime management)
std::string LogId; // Id for tagging log output