refactor again

This commit is contained in:
Aaron Kimbre 2025-01-15 13:48:49 -06:00
parent 72ae55981b
commit 5b8fe2cba0
3 changed files with 106 additions and 101 deletions

View File

@ -76,7 +76,9 @@ int main(int argc, char** argv) {
Game::assetManager = new AssetManager(clientPath); Game::assetManager = new AssetManager(clientPath);
} catch (std::runtime_error& ex) { } catch (std::runtime_error& ex) {
LOG("Got an error while setting up assets: %s", ex.what()); LOG("Got an error while setting up assets: %s", ex.what());
delete Game::server;
delete Game::logger;
delete Game::config;
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -88,9 +90,21 @@ int main(int argc, char** argv) {
Database::Destroy("ChatServer"); Database::Destroy("ChatServer");
delete Game::server; delete Game::server;
delete Game::logger; delete Game::logger;
delete Game::config;
return EXIT_FAILURE; return EXIT_FAILURE;
} }
bool web_server_enabled = Game::config->GetValue("web_server_enabled") == "1";
ChatWebAPI chatwebapi;
if (web_server_enabled && !chatwebapi.Startup()){
LOG("Failed to start web server, shutting down.");
Database::Destroy("ChatServer");
delete Game::server;
delete Game::logger;
delete Game::config;
return EXIT_FAILURE;
};
//Find out the master's IP: //Find out the master's IP:
std::string masterIP; std::string masterIP;
uint32_t masterPort = 1000; uint32_t masterPort = 1000;
@ -124,11 +138,6 @@ int main(int argc, char** argv) {
uint32_t framesSinceMasterDisconnect = 0; uint32_t framesSinceMasterDisconnect = 0;
uint32_t framesSinceLastSQLPing = 0; uint32_t framesSinceLastSQLPing = 0;
bool web_server_enabled = Game::config->GetValue("web_server_enabled") == "1";
ChatWebAPI chatwebapi;
if (web_server_enabled) chatwebapi.Listen();
auto lastTime = std::chrono::high_resolution_clock::now(); auto lastTime = std::chrono::high_resolution_clock::now();
Game::logger->Flush(); // once immediately before main loop Game::logger->Flush(); // once immediately before main loop

View File

@ -8,13 +8,13 @@
#include "dServer.h" #include "dServer.h"
#include "dConfig.h" #include "dConfig.h"
#include "PlayerContainer.h" #include "PlayerContainer.h"
#include "GeneralUtils.h"
#include "JSONUtils.h" #include "JSONUtils.h"
#include "GeneralUtils.h"
#include "eHTTPMethod.h" #include "eHTTPMethod.h"
#include "eHTTPStatusCode.h"
#include "magic_enum.hpp" #include "magic_enum.hpp"
#include "ChatPackets.h" #include "ChatPackets.h"
#include "StringifiedEnum.h" #include "StringifiedEnum.h"
#include "Database.h"
#ifdef DARKFLAME_PLATFORM_WIN32 #ifdef DARKFLAME_PLATFORM_WIN32
#pragma push_macro("DELETE") #pragma push_macro("DELETE")
@ -27,16 +27,18 @@ typedef struct mg_connection mg_connection;
typedef struct mg_http_message mg_http_message; typedef struct mg_http_message mg_http_message;
namespace { namespace {
const std::string root_path = "/api/v1/";
const char* json_content_type = "Content-Type: application/json\r\n"; const char* json_content_type = "Content-Type: application/json\r\n";
std::map<std::pair<eHTTPMethod, std::string>, WebAPIHTTPRoute> Routes {};
} }
struct HTTPReply { bool ValidateAuthentication(const mg_http_message* http_msg) {
eHTTPStatusCode status = eHTTPStatusCode::NOT_FOUND; // TO DO: This is just a placeholder for now
std::string message = "{\"error\":\"Not Found\"}"; // use tokens or something at a later point if we want to implement authentication
}; // bit using the listen bind address to limit external access is good enough to start with
return true;
}
bool CheckValidJSON(std::optional<json> data, HTTPReply& reply) { bool ValidateJSON(std::optional<json> data, HTTPReply& reply) {
if (!data) { if (!data) {
reply.status = eHTTPStatusCode::BAD_REQUEST; reply.status = eHTTPStatusCode::BAD_REQUEST;
reply.message = "{\"error\":\"Invalid JSON\"}"; reply.message = "{\"error\":\"Invalid JSON\"}";
@ -45,40 +47,21 @@ bool CheckValidJSON(std::optional<json> data, HTTPReply& reply) {
return true; return true;
} }
void HandlePlayersRequest(HTTPReply& reply) { void HandlePlayersRequest(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();
} }
void HandleTeamsRequest(HTTPReply& reply) { void HandleTeamsRequest(HTTPReply& reply, std::string body) {
const json data = Game::playerContainer.GetTeamContainer(); const json data = Game::playerContainer.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();
} }
void HandleInvalidRoute(HTTPReply& reply) { void HandleAnnounceRequest(HTTPReply& reply, std::string body) {
reply.status = eHTTPStatusCode::NOT_FOUND; auto data = GeneralUtils::TryParse<json>(body);
reply.message = "{\"error\":\"Invalid Route\"}"; if (!ValidateJSON(data, reply)) return;
}
void HandleGET(HTTPReply& reply, const ChatWebAPI::eRoute& route , const std::string& body) {
switch (route) {
case ChatWebAPI::eRoute::PLAYERS:
HandlePlayersRequest(reply);
break;
case ChatWebAPI::eRoute::TEAMS:
HandleTeamsRequest(reply);
break;
case ChatWebAPI::eRoute::INVALID:
default:
HandleInvalidRoute(reply);
break;
}
}
void HandleAnnounceRequest(HTTPReply& reply, const std::optional<json>& data) {
if (!CheckValidJSON(data, reply)) return;
const auto& good_data = data.value(); const auto& good_data = data.value();
auto check = JSONUtils::CheckRequiredData(good_data, { "title", "message" }); auto check = JSONUtils::CheckRequiredData(good_data, { "title", "message" });
@ -97,18 +80,9 @@ void HandleAnnounceRequest(HTTPReply& reply, const std::optional<json>& data) {
} }
} }
void HandlePOST(HTTPReply& reply, const ChatWebAPI::eRoute& route , const std::string& body) { void HandleInvalidRoute(HTTPReply& reply) {
auto data = GeneralUtils::TryParse<json>(body); reply.status = eHTTPStatusCode::NOT_FOUND;
switch (route) { reply.message = "{\"error\":\"Invalid Route\"}";
case ChatWebAPI::eRoute::ANNOUNCE:{
HandleAnnounceRequest(reply, data.value());
break;
}
case ChatWebAPI::eRoute::INVALID:
default:
HandleInvalidRoute(reply);
break;
}
} }
void HandleHTTPMessage(mg_connection* connection, const mg_http_message* http_msg) { void HandleHTTPMessage(mg_connection* connection, const mg_http_message* http_msg) {
@ -117,7 +91,7 @@ void HandleHTTPMessage(mg_connection* connection, const mg_http_message* http_ms
if (!http_msg) { if (!http_msg) {
reply.status = eHTTPStatusCode::BAD_REQUEST; reply.status = eHTTPStatusCode::BAD_REQUEST;
reply.message = "{\"error\":\"Invalid Request\"}"; reply.message = "{\"error\":\"Invalid Request\"}";
} else { } else if (ValidateAuthentication(http_msg)) {
// convert method from cstring to std string // convert method from cstring to std string
std::string method_string(http_msg->method.buf, http_msg->method.len); std::string method_string(http_msg->method.buf, http_msg->method.len);
@ -126,39 +100,21 @@ void HandleHTTPMessage(mg_connection* connection, const mg_http_message* http_ms
// convert uri from cstring to std string // convert uri from cstring to std string
std::string uri(http_msg->uri.buf, http_msg->uri.len); std::string uri(http_msg->uri.buf, http_msg->uri.len);
// check for root path std::transform(uri.begin(), uri.end(), uri.begin(), ::tolower);
if (uri.find(root_path) == 0) {
// remove root path from uri
uri.erase(0, root_path.length());
// convert uri to uppercase
std::transform(uri.begin(), uri.end(), uri.begin(), ::toupper);
// convert uri string to route enum
ChatWebAPI::eRoute route = magic_enum::enum_cast<ChatWebAPI::eRoute>(uri).value_or(ChatWebAPI::eRoute::INVALID);
// convert body from cstring to std string // convert body from cstring to std string
std::string body(http_msg->body.buf, http_msg->body.len); std::string body(http_msg->body.buf, http_msg->body.len);
switch (method) {
case eHTTPMethod::GET: const auto routeItr = Routes.find({method, uri});
HandleGET(reply, route, body);
break; if (routeItr != Routes.end()) {
case eHTTPMethod::POST: const auto& [_, route] = *routeItr;
HandlePOST(reply, route, body); route.handle(reply, body);
break; } else HandleInvalidRoute(reply);
case eHTTPMethod::PUT: } else {
case eHTTPMethod::DELETE: reply.status = eHTTPStatusCode::UNAUTHORIZED;
case eHTTPMethod::HEAD: reply.message = "{\"error\":\"Unauthorized\"}";
case eHTTPMethod::CONNECT:
case eHTTPMethod::OPTIONS:
case eHTTPMethod::TRACE:
case eHTTPMethod::PATCH:
case eHTTPMethod::INVALID:
default:
reply.status = eHTTPStatusCode::METHOD_NOT_ALLOWED;
reply.message = "{\"error\":\"Invalid Method\"}";
break;
}
}
} }
mg_http_reply(connection, static_cast<int>(reply.status), json_content_type, reply.message.c_str()); mg_http_reply(connection, static_cast<int>(reply.status), json_content_type, reply.message.c_str());
} }
@ -174,9 +130,14 @@ void HandleRequests(mg_connection* connection, int request, void* request_data)
} }
} }
#ifdef DARKFLAME_PLATFORM_WIN32 void ChatWebAPI::RegisterHTTPRoutes(WebAPIHTTPRoute route) {
#pragma pop_macro("DELETE") auto [_, success] = Routes.try_emplace({ route.method, route.path }, route);
#endif if (!success) {
LOG_DEBUG("Failed to register route %s", route.path.c_str());
} else {
LOG_DEBUG("Registered route %s", route.path.c_str());
}
}
ChatWebAPI::ChatWebAPI() { ChatWebAPI::ChatWebAPI() {
mg_log_set(MG_LL_NONE); mg_log_set(MG_LL_NONE);
@ -187,7 +148,7 @@ ChatWebAPI::~ChatWebAPI() {
mg_mgr_free(&mgr); mg_mgr_free(&mgr);
} }
void ChatWebAPI::Listen() { bool ChatWebAPI::Startup() {
// make listen address // make listen address
std::string listen_ip = Game::config->GetValue("web_server_listen_ip"); std::string listen_ip = Game::config->GetValue("web_server_listen_ip");
if (listen_ip == "localhost") listen_ip = "127.0.0.1"; if (listen_ip == "localhost") listen_ip = "127.0.0.1";
@ -198,10 +159,38 @@ void ChatWebAPI::Listen() {
// Create HTTP listener // Create HTTP listener
if (!mg_http_listen(&mgr, listen_address.c_str(), HandleRequests, NULL)) { if (!mg_http_listen(&mgr, listen_address.c_str(), HandleRequests, NULL)) {
LOG("Failed to create web server listener"); LOG("Failed to create web server listener on %s", listen_port.c_str());
return false;
} }
// Register routes
// API v1 routes
std::string v1_route = "/api/v1/";
RegisterHTTPRoutes({
.path = v1_route + "players",
.method = eHTTPMethod::GET,
.handle = HandlePlayersRequest
});
RegisterHTTPRoutes({
.path = v1_route + "teams",
.method = eHTTPMethod::GET,
.handle = HandleTeamsRequest
});
RegisterHTTPRoutes({
.path = v1_route + "announce",
.method = eHTTPMethod::POST,
.handle = HandleAnnounceRequest
});
return true;
} }
void ChatWebAPI::ReceiveRequests() { void ChatWebAPI::ReceiveRequests() {
mg_mgr_poll(&mgr, 15); mg_mgr_poll(&mgr, 15);
} }
#ifdef DARKFLAME_PLATFORM_WIN32
#pragma pop_macro("DELETE")
#endif

View File

@ -1,29 +1,36 @@
#ifndef CHATWEBAPI_H #ifndef __CHATWEBAPI_H__
#define CHATWEBAPI_H #define __CHATWEBAPI_H__
#include <string>
#include <functional>
#include "mongoose.h" #include "mongoose.h"
#include "eHTTPStatusCode.h"
enum class eHTTPMethod;
typedef struct mg_mgr mg_mgr; typedef struct mg_mgr mg_mgr;
struct HTTPReply {
eHTTPStatusCode status = eHTTPStatusCode::NOT_FOUND;
std::string message = "{\"error\":\"Not Found\"}";
};
struct WebAPIHTTPRoute {
std::string path;
eHTTPMethod method;
std::function<void(HTTPReply&, const std::string&)> handle;
};
class ChatWebAPI { class ChatWebAPI {
public: public:
ChatWebAPI(); ChatWebAPI();
~ChatWebAPI(); ~ChatWebAPI();
void ReceiveRequests(); void ReceiveRequests();
void Listen(); void RegisterHTTPRoutes(WebAPIHTTPRoute route);
enum class eRoute { bool Startup();
// GET
PLAYERS,
TEAMS,
// POST
ANNOUNCE,
// INVALID
INVALID
};
private: private:
mg_mgr mgr; mg_mgr mgr;
}; };
#endif #endif // __CHATWEBAPI_H__