This commit is contained in:
Aaron Kimbrell
2025-10-06 19:15:55 -05:00
parent 62ac65c520
commit 5453d163a3
9 changed files with 258 additions and 2 deletions

View File

@@ -322,6 +322,7 @@ endif()
add_subdirectory(dWorldServer) add_subdirectory(dWorldServer)
add_subdirectory(dAuthServer) add_subdirectory(dAuthServer)
add_subdirectory(dChatServer) add_subdirectory(dChatServer)
add_subdirectory(dDashboardServer)
add_subdirectory(dMasterServer) # Add MasterServer last so it can rely on the other binaries add_subdirectory(dMasterServer) # Add MasterServer last so it can rely on the other binaries
target_precompile_headers( target_precompile_headers(

View File

@@ -26,12 +26,14 @@ void HandleHTTPPlayersRequest(HTTPReply& reply, std::string body) {
const json data = Game::playerContainer; const json data = Game::playerContainer;
reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK; reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK;
reply.message = data.empty() ? "{\"error\":\"No Players Online\"}" : data.dump(); reply.message = data.empty() ? "{\"error\":\"No Players Online\"}" : data.dump();
reply.contentType = ContentType::JSON;
} }
void HandleHTTPTeamsRequest(HTTPReply& reply, std::string body) { void HandleHTTPTeamsRequest(HTTPReply& reply, std::string body) {
const json data = TeamContainer::GetTeamContainer(); const json data = TeamContainer::GetTeamContainer();
reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK; reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK;
reply.message = data.empty() ? "{\"error\":\"No Teams Online\"}" : data.dump(); reply.message = data.empty() ? "{\"error\":\"No Teams Online\"}" : data.dump();
reply.contentType = ContentType::JSON;
} }
void HandleHTTPAnnounceRequest(HTTPReply& reply, std::string body) { void HandleHTTPAnnounceRequest(HTTPReply& reply, std::string body) {
@@ -39,6 +41,7 @@ void HandleHTTPAnnounceRequest(HTTPReply& reply, std::string body) {
if (!data) { if (!data) {
reply.status = eHTTPStatusCode::BAD_REQUEST; reply.status = eHTTPStatusCode::BAD_REQUEST;
reply.message = "{\"error\":\"Invalid JSON\"}"; reply.message = "{\"error\":\"Invalid JSON\"}";
reply.contentType = ContentType::JSON;
return; return;
} }
@@ -47,6 +50,7 @@ void HandleHTTPAnnounceRequest(HTTPReply& reply, std::string body) {
if (!check.empty()) { if (!check.empty()) {
reply.status = eHTTPStatusCode::BAD_REQUEST; reply.status = eHTTPStatusCode::BAD_REQUEST;
reply.message = check; reply.message = check;
reply.contentType = ContentType::JSON;
} else { } else {
ChatPackets::Announcement announcement; ChatPackets::Announcement announcement;
@@ -56,6 +60,7 @@ void HandleHTTPAnnounceRequest(HTTPReply& reply, std::string body) {
reply.status = eHTTPStatusCode::OK; reply.status = eHTTPStatusCode::OK;
reply.message = "{\"status\":\"Announcement Sent\"}"; reply.message = "{\"status\":\"Announcement Sent\"}";
reply.contentType = ContentType::JSON;
} }
} }

View File

@@ -0,0 +1,8 @@
set(DDASHBOARDSERVER_SOURCES
"DashboardWeb.cpp"
)
add_executable(DashboardServer "DashboardServer.cpp" "DashboardWeb.cpp")
target_link_libraries(DashboardServer ${COMMON_LIBRARIES} dServer dWeb inja)
target_include_directories(DashboardServer PRIVATE ${PROJECT_SOURCE_DIR}/dServer ${PROJECT_SOURCE_DIR}/dWeb)
add_compile_definitions(DashboardServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}\"")

View File

@@ -0,0 +1,182 @@
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
//DLU Includes:
#include "dCommonVars.h"
#include "dServer.h"
#include "Logger.h"
#include "Database.h"
#include "dConfig.h"
#include "Diagnostics.h"
#include "AssetManager.h"
#include "BinaryPathFinder.h"
#include "ServiceType.h"
#include "StringifiedEnum.h"
#include "Game.h"
#include "Server.h"
//RakNet includes:
#include "RakNetDefines.h"
#include "MessageIdentifiers.h"
#include "DashboardWeb.h"
namespace Game {
Logger* logger = nullptr;
dServer* server = nullptr;
dConfig* config = nullptr;
AssetManager* assetManager = nullptr;
Game::signal_t lastSignal = 0;
std::mt19937 randomEngine;
}
int main(int argc, char** argv) {
constexpr uint32_t dashboardFramerate = mediumFramerate;
constexpr uint32_t dashboardFrameDelta = mediumFrameDelta;
Diagnostics::SetProcessName("Dashboard");
Diagnostics::SetProcessFileName(argv[0]);
Diagnostics::Initialize();
std::signal(SIGINT, Game::OnSignal);
std::signal(SIGTERM, Game::OnSignal);
Game::config = new dConfig("dashboardconfig.ini");
//Create all the objects we need to run our service:
Server::SetupLogger("DashboardServer");
if (!Game::logger) return EXIT_FAILURE;
Game::config->LogSettings();
//Read our config:
LOG("Starting Dashboard server...");
LOG("Version: %s", PROJECT_VERSION);
LOG("Compiled on: %s", __TIMESTAMP__);
try {
std::string clientPathStr = Game::config->GetValue("client_location");
if (clientPathStr.empty()) clientPathStr = "./res";
std::filesystem::path clientPath = std::filesystem::path(clientPathStr);
if (clientPath.is_relative()) {
clientPath = BinaryPathFinder::GetBinaryDir() / clientPath;
}
Game::assetManager = new AssetManager(clientPath);
} catch (std::runtime_error& ex) {
LOG("Got an error while setting up assets: %s", ex.what());
delete Game::logger;
delete Game::config;
return EXIT_FAILURE;
}
//Connect to the Database
try {
Database::Connect();
} catch (std::exception& ex) {
LOG("Got an error while connecting to the database: %s", ex.what());
Database::Destroy("DashboardServer");
delete Game::logger;
delete Game::config;
return EXIT_FAILURE;
}
// setup the chat api web server
const uint32_t web_server_port = GeneralUtils::TryParse<uint32_t>(Game::config->GetValue("web_server_port")).value_or(80);
if (!Game::web.Startup("localhost", web_server_port)) {
// if we want the web server and it fails to start, exit
LOG("Failed to start web server, shutting down.");
Database::Destroy("DashboardServer");
delete Game::logger;
delete Game::config;
return EXIT_FAILURE;
}
DashboardWeb::RegisterRoutes();
//Find out the master's IP:
std::string masterIP;
uint32_t masterPort = 1000;
std::string masterPassword;
auto masterInfo = Database::Get()->GetMasterInfo();
if (masterInfo) {
masterIP = masterInfo->ip;
masterPort = masterInfo->port;
masterPassword = masterInfo->password;
}
//It's safe to pass 'localhost' here, as the IP is only used as the external IP.
std::string ourIP = "localhost";
const uint32_t maxClients = GeneralUtils::TryParse<uint32_t>(Game::config->GetValue("max_clients")).value_or(999);
const uint32_t ourPort = GeneralUtils::TryParse<uint32_t>(Game::config->GetValue("dashboard_server_port")).value_or(2006);
const auto externalIPString = Game::config->GetValue("external_ip");
if (!externalIPString.empty()) ourIP = externalIPString;
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServiceType::COMMON, Game::config, &Game::lastSignal, masterPassword);
Game::randomEngine = std::mt19937(time(0));
//Run it until server gets a kill message from Master:
auto t = std::chrono::high_resolution_clock::now();
Packet* packet = nullptr;
constexpr uint32_t logFlushTime = 30 * dashboardFramerate; // 30 seconds in frames
constexpr uint32_t sqlPingTime = 10 * 60 * dashboardFramerate; // 10 minutes in frames
uint32_t framesSinceLastFlush = 0;
uint32_t framesSinceMasterDisconnect = 0;
uint32_t framesSinceLastSQLPing = 0;
auto lastTime = std::chrono::high_resolution_clock::now();
Game::logger->Flush(); // once immediately before main loop
while (!Game::ShouldShutdown()) {
//Check if we're still connected to master:
if (!Game::server->GetIsConnectedToMaster()) {
framesSinceMasterDisconnect++;
if (framesSinceMasterDisconnect >= dashboardFramerate)
break; //Exit our loop, shut down.
} else framesSinceMasterDisconnect = 0;
const auto currentTime = std::chrono::high_resolution_clock::now();
const float deltaTime = std::chrono::duration<float>(currentTime - lastTime).count();
lastTime = currentTime;
// Check and handle web requests:
Game::web.ReceiveRequests();
//Push our log every 30s:
if (framesSinceLastFlush >= logFlushTime) {
Game::logger->Flush();
framesSinceLastFlush = 0;
} else framesSinceLastFlush++;
//Every 10 min we ping our sql server to keep it alive hopefully:
if (framesSinceLastSQLPing >= sqlPingTime) {
//Find out the master's IP for absolutely no reason:
std::string masterIP;
uint32_t masterPort;
auto masterInfo = Database::Get()->GetMasterInfo();
if (masterInfo) {
masterIP = masterInfo->ip;
masterPort = masterInfo->port;
}
framesSinceLastSQLPing = 0;
} else framesSinceLastSQLPing++;
//Sleep our thread since auth can afford to.
t += std::chrono::milliseconds(dashboardFrameDelta); //Chat can run at a lower "fps"
std::this_thread::sleep_until(t);
}
//Delete our objects here:
Database::Destroy("DashboardServer");
delete Game::server;
delete Game::logger;
delete Game::config;
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,11 @@
#include "DashboardWeb.h"
#include <inja/inja.hpp>
// default home page
namespace DashboardWeb {
void RegisterRoutes() {
}
}

View File

@@ -0,0 +1,14 @@
#ifndef __DASHBOARDWEB_H__
#define __DASHBOARDWEB_H__
#include <string>
#include <functional>
#include "Web.h"
namespace DashboardWeb {
void RegisterRoutes();
};
#endif // __DASHBOARDWEB_H__

View File

@@ -13,7 +13,6 @@ namespace Game {
} }
namespace { namespace {
const char* jsonContentType = "Content-Type: application/json\r\n";
const std::string wsSubscribed = "{\"status\":\"subscribed\"}"; const std::string wsSubscribed = "{\"status\":\"subscribed\"}";
const std::string wsUnsubscribed = "{\"status\":\"unsubscribed\"}"; const std::string wsUnsubscribed = "{\"status\":\"unsubscribed\"}";
std::map<std::pair<eHTTPMethod, std::string>, HTTPRoute> g_HTTPRoutes; std::map<std::pair<eHTTPMethod, std::string>, HTTPRoute> g_HTTPRoutes;
@@ -73,7 +72,7 @@ void HandleHTTPMessage(mg_connection* connection, const mg_http_message* http_ms
reply.status = eHTTPStatusCode::UNAUTHORIZED; reply.status = eHTTPStatusCode::UNAUTHORIZED;
reply.message = "{\"error\":\"Unauthorized\"}"; reply.message = "{\"error\":\"Unauthorized\"}";
} }
mg_http_reply(connection, static_cast<int>(reply.status), jsonContentType, reply.message.c_str()); mg_http_reply(connection, static_cast<int>(reply.status), reply.contentType.c_str(), reply.message.c_str());
} }

View File

@@ -20,10 +20,36 @@ enum class eHTTPMethod;
// Forward declaration for mongoose manager // Forward declaration for mongoose manager
typedef struct mg_mgr mg_mgr; typedef struct mg_mgr mg_mgr;
namespace ContentType {
const std::string JSON = "Content-Type: application/json\r\n";
const std::string HTML = "Content-Type: text/html\r\n";
const std::string PLAIN = "Content-Type: text/plain\r\n";
const std::string CSS = "Content-Type: text/css\r\n";
const std::string JAVASCRIPT = "Content-Type: application/javascript\r\n";
const std::string ICO = "Content-Type: image/x-icon\r\n";
const std::string PNG = "Content-Type: image/png\r\n";
const std::string SVG = "Content-Type: image/svg+xml\r\n";
const std::string JPG = "Content-Type: image/jpeg\r\n";
const std::string GIF = "Content-Type: image/gif\r\n";
const std::string WEBP = "Content-Type: image/webp\r\n";
const std::string MP4 = "Content-Type: video/mp4\r\n";
const std::string OGG = "Content-Type: audio/ogg\r\n";
const std::string MP3 = "Content-Type: audio/mpeg\r\n";
const std::string BINARY = "Content-Type: application/octet-stream\r\n";
const std::string FORM = "Content-Type: application/x-www-form-urlencoded\r\n";
const std::string MULTIPART = "Content-Type: multipart/form-data\r\n";
const std::string ZIP = "Content-Type: application/zip\r\n";
const std::string PDF = "Content-Type: application/pdf\r\n";
const std::string XML = "Content-Type: application/xml\r\n";
const std::string CSV = "Content-Type: text/csv\r\n";
const std::string YAML = "Content-Type: application/x-yaml\r\n";
}
// For passing HTTP messages between functions // For passing HTTP messages between functions
struct HTTPReply { struct HTTPReply {
eHTTPStatusCode status = eHTTPStatusCode::NOT_FOUND; eHTTPStatusCode status = eHTTPStatusCode::NOT_FOUND;
std::string message = "{\"error\":\"Not Found\"}"; std::string message = "{\"error\":\"Not Found\"}";
std::string contentType = ContentType::JSON;
}; };
// HTTP route structure // HTTP route structure

View File

@@ -77,6 +77,16 @@ FetchContent_Declare(
FetchContent_MakeAvailable(glm) FetchContent_MakeAvailable(glm)
FetchContent_Declare(
inja
GIT_REPOSITORY https://github.com/pantor/inja.git
GIT_TAG b2276440be8334aeba9cd5d628c2731d0f6a5809 #refs/tags/v3.4.0
GIT_PROGRESS TRUE
GIT_SHALLOW 1
)
FetchContent_MakeAvailable(inja)
add_subdirectory(MD5) add_subdirectory(MD5)
add_subdirectory(mongoose) add_subdirectory(mongoose)