diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index b501157b..d136ed56 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -76,7 +76,9 @@ int main(int argc, char** argv) { Game::assetManager = new AssetManager(clientPath); } catch (std::runtime_error& ex) { LOG("Got an error while setting up assets: %s", ex.what()); - + delete Game::server; + delete Game::logger; + delete Game::config; return EXIT_FAILURE; } @@ -88,9 +90,21 @@ int main(int argc, char** argv) { Database::Destroy("ChatServer"); delete Game::server; delete Game::logger; + delete Game::config; 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: std::string masterIP; uint32_t masterPort = 1000; @@ -124,11 +138,6 @@ int main(int argc, char** argv) { uint32_t framesSinceMasterDisconnect = 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(); Game::logger->Flush(); // once immediately before main loop diff --git a/dChatServer/ChatWebAPI.cpp b/dChatServer/ChatWebAPI.cpp index 0b87bece..36911095 100644 --- a/dChatServer/ChatWebAPI.cpp +++ b/dChatServer/ChatWebAPI.cpp @@ -8,13 +8,13 @@ #include "dServer.h" #include "dConfig.h" #include "PlayerContainer.h" -#include "GeneralUtils.h" #include "JSONUtils.h" +#include "GeneralUtils.h" #include "eHTTPMethod.h" -#include "eHTTPStatusCode.h" #include "magic_enum.hpp" #include "ChatPackets.h" #include "StringifiedEnum.h" +#include "Database.h" #ifdef DARKFLAME_PLATFORM_WIN32 #pragma push_macro("DELETE") @@ -27,16 +27,18 @@ typedef struct mg_connection mg_connection; typedef struct mg_http_message mg_http_message; namespace { - const std::string root_path = "/api/v1/"; const char* json_content_type = "Content-Type: application/json\r\n"; + std::map, WebAPIHTTPRoute> Routes {}; } -struct HTTPReply { - eHTTPStatusCode status = eHTTPStatusCode::NOT_FOUND; - std::string message = "{\"error\":\"Not Found\"}"; -}; +bool ValidateAuthentication(const mg_http_message* http_msg) { + // TO DO: This is just a placeholder for now + // 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 data, HTTPReply& reply) { +bool ValidateJSON(std::optional data, HTTPReply& reply) { if (!data) { reply.status = eHTTPStatusCode::BAD_REQUEST; reply.message = "{\"error\":\"Invalid JSON\"}"; @@ -45,40 +47,21 @@ bool CheckValidJSON(std::optional data, HTTPReply& reply) { return true; } -void HandlePlayersRequest(HTTPReply& reply) { +void HandlePlayersRequest(HTTPReply& reply, std::string body) { const json data = Game::playerContainer; reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK; 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(); reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK; reply.message = data.empty() ? "{\"error\":\"No Teams Online\"}" : data.dump(); } -void HandleInvalidRoute(HTTPReply& reply) { - reply.status = eHTTPStatusCode::NOT_FOUND; - reply.message = "{\"error\":\"Invalid Route\"}"; -} - -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& data) { - if (!CheckValidJSON(data, reply)) return; +void HandleAnnounceRequest(HTTPReply& reply, std::string body) { + auto data = GeneralUtils::TryParse(body); + if (!ValidateJSON(data, reply)) return; const auto& good_data = data.value(); auto check = JSONUtils::CheckRequiredData(good_data, { "title", "message" }); @@ -97,18 +80,9 @@ void HandleAnnounceRequest(HTTPReply& reply, const std::optional& data) { } } -void HandlePOST(HTTPReply& reply, const ChatWebAPI::eRoute& route , const std::string& body) { - auto data = GeneralUtils::TryParse(body); - switch (route) { - case ChatWebAPI::eRoute::ANNOUNCE:{ - HandleAnnounceRequest(reply, data.value()); - break; - } - case ChatWebAPI::eRoute::INVALID: - default: - HandleInvalidRoute(reply); - break; - } +void HandleInvalidRoute(HTTPReply& reply) { + reply.status = eHTTPStatusCode::NOT_FOUND; + reply.message = "{\"error\":\"Invalid Route\"}"; } 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) { reply.status = eHTTPStatusCode::BAD_REQUEST; reply.message = "{\"error\":\"Invalid Request\"}"; - } else { + } else if (ValidateAuthentication(http_msg)) { // convert method from cstring to std string 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 std::string uri(http_msg->uri.buf, http_msg->uri.len); - // check for root path - 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(uri).value_or(ChatWebAPI::eRoute::INVALID); + std::transform(uri.begin(), uri.end(), uri.begin(), ::tolower); - // convert body from cstring to std string - std::string body(http_msg->body.buf, http_msg->body.len); + // convert body from cstring to std string + std::string body(http_msg->body.buf, http_msg->body.len); - switch (method) { - case eHTTPMethod::GET: - HandleGET(reply, route, body); - break; - case eHTTPMethod::POST: - HandlePOST(reply, route, body); - break; - case eHTTPMethod::PUT: - case eHTTPMethod::DELETE: - case eHTTPMethod::HEAD: - 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; - } - } + + const auto routeItr = Routes.find({method, uri}); + + if (routeItr != Routes.end()) { + const auto& [_, route] = *routeItr; + route.handle(reply, body); + } else HandleInvalidRoute(reply); + } else { + reply.status = eHTTPStatusCode::UNAUTHORIZED; + reply.message = "{\"error\":\"Unauthorized\"}"; } mg_http_reply(connection, static_cast(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 -#pragma pop_macro("DELETE") -#endif +void ChatWebAPI::RegisterHTTPRoutes(WebAPIHTTPRoute route) { + auto [_, success] = Routes.try_emplace({ route.method, route.path }, route); + 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() { mg_log_set(MG_LL_NONE); @@ -187,7 +148,7 @@ ChatWebAPI::~ChatWebAPI() { mg_mgr_free(&mgr); } -void ChatWebAPI::Listen() { +bool ChatWebAPI::Startup() { // make listen address std::string listen_ip = Game::config->GetValue("web_server_listen_ip"); if (listen_ip == "localhost") listen_ip = "127.0.0.1"; @@ -198,10 +159,38 @@ void ChatWebAPI::Listen() { // Create HTTP listener 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() { mg_mgr_poll(&mgr, 15); } + +#ifdef DARKFLAME_PLATFORM_WIN32 +#pragma pop_macro("DELETE") +#endif diff --git a/dChatServer/ChatWebAPI.h b/dChatServer/ChatWebAPI.h index c8e4c04c..c5626298 100644 --- a/dChatServer/ChatWebAPI.h +++ b/dChatServer/ChatWebAPI.h @@ -1,29 +1,36 @@ -#ifndef CHATWEBAPI_H -#define CHATWEBAPI_H +#ifndef __CHATWEBAPI_H__ +#define __CHATWEBAPI_H__ +#include +#include #include "mongoose.h" +#include "eHTTPStatusCode.h" + +enum class eHTTPMethod; 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 handle; +}; + class ChatWebAPI { public: ChatWebAPI(); ~ChatWebAPI(); void ReceiveRequests(); - void Listen(); - enum class eRoute { - // GET - PLAYERS, - TEAMS, - // POST - ANNOUNCE, - // INVALID - INVALID - }; + void RegisterHTTPRoutes(WebAPIHTTPRoute route); + bool Startup(); private: mg_mgr mgr; - }; -#endif +#endif // __CHATWEBAPI_H__