diff --git a/engine/server/application/CentralServer/src/shared/CentralServer.cpp b/engine/server/application/CentralServer/src/shared/CentralServer.cpp index 4bf21889..74d6903a 100755 --- a/engine/server/application/CentralServer/src/shared/CentralServer.cpp +++ b/engine/server/application/CentralServer/src/shared/CentralServer.cpp @@ -123,9 +123,10 @@ #include -// Trying todo something here ... #include "webAPI.h" +using namespace StellaBellum; + namespace CentralServerNamespace { bool gs_connectionServersPublic=false; @@ -2782,15 +2783,14 @@ void CentralServer::update() // update the webAPI if specified int webUpdateIntervalSeconds = ConfigCentralServer::getWebUpdateIntervalSeconds(); - std::string updateURL = std::string(ConfigCentralServer::getMetricsDataURL()); // assuming that every 5th frame is ~1 second, we can multiply and then check - if ( !(updateURL.empty()) && webUpdateIntervalSeconds && (++apiLoopCount > (webUpdateIntervalSeconds*1000)) ) + if (webUpdateIntervalSeconds && (++apiLoopCount > (webUpdateIntervalSeconds*1000)) ) { apiLoopCount = 0; // update the web api - sendMetricsToWebAPI(updateURL); + sendMetricsToWebAPI(); } if ( ConfigCentralServer::getAuctionEnabled() ) // allow auctions? @@ -2860,15 +2860,48 @@ void CentralServer::sendPopulationUpdateToLoginServer() sendToAllLoginServers(upm); } -// TODO: for sending metrics and such, would it make more sense to pass an object instead of a huge assed string as below? -void CentralServer::sendMetricsToWebAPI(std::string updateURL) +void CentralServer::sendMetricsToWebAPI() { - std::ostringstream postBuf; + // create the object + webAPI api = webAPI::webAPI(std::string(ConfigCentralServer::getMetricsDataURL())); - postBuf << "totalPlayerCount=" << m_totalPlayerCount << "&totalGameServers=" << m_gameServers.size() - 1 << "&totalPlanetServers=" << m_planetServers.size() << "&isPublic=" << getIsClusterPublic() << "&isLocked=" << getIsClusterLocked() << "&isSecret=" << getIsClusterSecret() << "&preloadFinished=" << getClusterStartupTime() << "&databasebacklogged=" << isDatabaseBacklogged() << "&totalTutorialSceneCount=" << m_totalTutorialSceneCount << "&totalFalconSceneCount=" << m_totalFalconSceneCount; - - webAPI::statusMessage response = webAPI::simplePost(updateURL, std::string(postBuf.str())); - WARNING(response.status, ("Error sending stats: %s", response.retVal.c_str())); + // add our data + api.addJsonData("totalPlayerCount", m_totalPlayerCount); + api.addJsonData("totalGameServers", (m_gameServers.size() - 1)); + api.addJsonData("totalPlanetServers", m_planetServers.size()); + api.addJsonData("isPublic", getIsClusterPublic()); + api.addJsonData("isLocked", getIsClusterLocked()); + api.addJsonData("isSecret", getIsClusterSecret()); + api.addJsonData("preloadFinished", getClusterStartupTime()); + api.addJsonData("databasebacklogged", isDatabaseBacklogged()); + api.addJsonData("totalTutorialSceneCount", getClusterStartupTime()); + api.addJsonData("totalFalconSceneCount", isDatabaseBacklogged()); + +#ifdef _DEBUG + if (api.submit()) { + std::string status = api.getRespValue("status"); + + if (status.empty() || status != "success") + { + std::string message = api.getRespValue("message"); + + if (message.empty()) + { + message = "No message returned."; + } + + WARNING(true, ("Error sending stats: %s", message.c_str())); + } + + WARNING(true, ("Success sending server stats to API.")); + } + else + { + WARNING(true, ("Error sending stats.")); + } +#else + api.submit(); +#endif } //----------------------------------------------------------------------- diff --git a/engine/server/application/CentralServer/src/shared/CentralServer.h b/engine/server/application/CentralServer/src/shared/CentralServer.h index a2da8ddb..22e6a7de 100755 --- a/engine/server/application/CentralServer/src/shared/CentralServer.h +++ b/engine/server/application/CentralServer/src/shared/CentralServer.h @@ -182,7 +182,7 @@ private: size_t getGameServerCount (void) const; void update(); void sendPopulationUpdateToLoginServer(); - void sendMetricsToWebAPI(std::string updateURL); + void sendMetricsToWebAPI(); ConnectionServerConnection * getConnectionServerForAccount(StationId suid); void addToAccountConnectionMap(StationId suid, ConnectionServerConnection * cconn, uint32 subscriptionBits); void removeFromAccountConnectionMap(int connectionServerConnectionId); diff --git a/engine/server/application/LoginServer/src/shared/ClientConnection.cpp b/engine/server/application/LoginServer/src/shared/ClientConnection.cpp index 32b37a53..680f49ce 100755 --- a/engine/server/application/LoginServer/src/shared/ClientConnection.cpp +++ b/engine/server/application/LoginServer/src/shared/ClientConnection.cpp @@ -23,9 +23,12 @@ #include "sharedNetworkMessages/GenericValueTypeMessage.h" #include "sharedNetworkMessages/LoginEnumCluster.h" -#include "webAPI.h" #include +#include "webAPI.h" + +using namespace StellaBellum; + //----------------------------------------------------------------------- ClientConnection::ClientConnection(UdpConnectionMT * u, TcpClient * t) : @@ -179,22 +182,38 @@ void ClientConnection::validateClient(const std::string & id, const std::string if (!authURL.empty()) { - std::ostringstream postBuf; - postBuf << "user_name=" << id << "&user_password=" << key << "&ip=" << getRemoteAddress(); + // create the object + webAPI api = webAPI::webAPI(authURL); - const webAPI::statusMessage response = webAPI::simplePost(authURL, std::string(postBuf.str()), "username"); + // add our data + api.addJsonData("user_name", id); + api.addJsonData("user_password", key); + api.addJsonData("ip", getRemoteAddress()); - // true indicates that we logged in successfully via the api - // since both cases rely on response.retVal which will never be empty - // ...we needn't check if it's empty/not - if (response.status) + if (api.submit()) { - authOK = true; - uname = response.retVal; + std::string status = api.getRespValue("status"); + uname = api.getRespValue("username"); + + if (!status.empty() && status == "success" && !uname.empty()) + { + authOK = true; + } + else + { + std::string msg = api.getRespValue("message"); + if (msg.empty()) + { + msg = "Invalid username or password."; + } + + ErrorMessage err("Login Failed", msg); + this->send(err, true); + } } else { - ErrorMessage err("Login Failed", response.retVal); + ErrorMessage err("Login Failed", "Could not connect to remote."); this->send(err, true); } } diff --git a/external/3rd/library/webAPI/webAPI.cpp b/external/3rd/library/webAPI/webAPI.cpp index 7efd13f5..cb13f530 100644 --- a/external/3rd/library/webAPI/webAPI.cpp +++ b/external/3rd/library/webAPI/webAPI.cpp @@ -1,4 +1,6 @@ /* +Version: 1.3 + This code is just a simple wrapper around nlohmann's wonderful json lib (https://github.com/nlohmann/json) and libcurl. While originally included directly, we have come to realize that we may require web API functionality elsewhere in the future. @@ -15,64 +17,102 @@ License: what's a license? we're a bunch of dirty pirates! #include "webAPI.h" -using namespace std; +using namespace StellaBellum; -/* -endpoint: URI -data: get/post -messageSlot: key for fetching a verbose user friendly message -statusSlot: slot containing the success/fail value -statusVal: the expected value in the case of success -*/ -webAPI::statusMessage webAPI::simplePost(const string &endpoint, const string &data, const string &slotName, const string &messageSlot, const string &statusSlot, const string &statusVal) +webAPI::webAPI(std::string endpoint, std::string userAgent) : uri(endpoint), userAgent(userAgent) {} +webAPI::~webAPI() { - // declare our output and go ahead and attempt to get data from remote - nlohmann::json response = request(endpoint, data); - - // if we got data back... - if (response.count(statusSlot) && response[statusSlot].get() == statusVal && response.count(slotName)) - { - return {true, response[slotName].get()}; - - } - else //default message is an error, the other end always assumes "success" or the specified slot - { - if (response.count(messageSlot)) - { - return {false, response[messageSlot].get()}; - } - } - - return {false, "Message not provided by remote."}; + requestData.clear(); + responseData.clear(); } -// this can be broken out to separate the json bits later if we need raw or other http type requests -// all it does is fetch via get or post, and if the status is 200 returns the json, else error json -nlohmann::json webAPI::request(const string &endpoint, const string &data, const int &reqType) +bool webAPI::setEndpoint(std::string endpoint) { - nlohmann::json response; + uri = endpoint; + + return true; +} - if (!endpoint.empty()) //data is allowed to be an empty string if we're doing a normal GET +std::string webAPI::getRaw() +{ + return sResponse; +} + +bool webAPI::setData(std::string &data) +{ + if (!data.empty()) + { + sRequest = data; + + return true; + } + + return false; +} + +bool webAPI::submit(const int &reqType, const int &getPost, const int &respType) +{ + + if (reqType == 0) // json request + { + if (!requestData.is_null()) + { + // serialize our data into sRequest + sRequest = requestData.dump(); + + // clear our the object for next time + requestData.clear(); + } + else + { + return false; + } + } + + if (fetch(getPost, respType) && !(sResponse.empty())) + { + return true; + } + + sResponse.clear(); + + return false; +} + +bool webAPI::fetch(const int &getPost, const int &mimeType) // 0 for json 1 for string +{ + if (!uri.empty()) //data is allowed to be an empty string if we're doing a normal GET { CURL *curl = curl_easy_init(); // start up curl if (curl) { - string readBuffer; // container for the remote response - long http_code = 0; // we get this after performing the get or post - + std::string readBuffer; // container for the remote response + struct curl_slist *slist = nullptr; + + // set the content type + if (mimeType == 0) + { + slist = curl_slist_append(slist, "Content-Type: application/json"); + } + else + { + slist = curl_slist_append(slist, "Content-Type: application/x-www-form-urlencoded"); + } + + curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent.c_str()); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback); // place the data into readBuffer using writeCallback curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); // specify readBuffer as the container for data - - switch (reqType) + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); + + switch (getPost) { - case 1: - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); - curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str()); + case 0: + curl_easy_setopt(curl, CURLOPT_URL, std::string(uri + "?" + sRequest).c_str()); break; - case 2: - std::string getURI = endpoint + "?" + data; - curl_easy_setopt(curl, CURLOPT_URL, getURI.c_str()); + case 1: + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, sRequest.c_str()); + curl_easy_setopt(curl, CURLOPT_URL, uri.c_str()); break; // want to do a put, or whatever other type? feel free to add here } @@ -80,53 +120,68 @@ nlohmann::json webAPI::request(const string &endpoint, const string &data, const CURLcode res = curl_easy_perform(curl); // make the request! char *contentType; - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); //get status code - curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &contentType); - - if (res == CURLE_OK && http_code == 200 && !(readBuffer.empty())) // check it all out and parse - { - // if we ever use anything other than json we can break the stuff below output - // into a separate function... - try - { - response = nlohmann::json::parse(readBuffer); - } - catch (string &e) - { - response["message"] = e; - response["status"] = "failure"; - } - catch (...) - { - response["message"] = "JSON parse error for endpoint."; - response["status"] = "failure"; - } - } - else - { - response["message"] = "Error fetching data from remote."; - response["status"] = "failure"; - } + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode); //get status code + curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &contentType); // get response mime type + + curl_slist_free_all(slist); curl_easy_cleanup(curl); // always wipe our butt - } - else //default err messages below - { - response["message"] = "Failed to initialize cURL."; - response["status"] = "failure"; + + if (res == CURLE_OK && statusCode == 200 && !(readBuffer.empty())) // check it all out and parse + { + sResponse = readBuffer; + + if (std::string(contentType) == "application/json") + { + processJSON(); + } + else + { + responseData.clear(); + } + + return true; + } } } - else - { - response["message"] = "Invalid endpoint URL."; - response["status"] = "failure"; - } - - return response; + + sResponse.clear(); + responseData.clear(); + + return false; } // This is used by curl to grab the response and put it into a var size_t webAPI::writeCallback(void *contents, size_t size, size_t nmemb, void *userp) { - ((string*)userp)->append((char*)contents, size * nmemb); + ((std::string*)userp)->append((char*)contents, size * nmemb); return size * nmemb; } + +bool webAPI::processJSON() +{ + if (!(sResponse.empty())) // check it all out and parse + { + try + { + responseData = nlohmann::json::parse(sResponse); + return true; + } + catch (std::string &e) + { + responseData["message"] = e; + responseData["status"] = "failure"; + } + catch (...) + { + responseData["message"] = "JSON parse error for endpoint."; + responseData["status"] = "failure"; + } + } + else + { + responseData["message"] = "Error fetching data from remote."; + responseData["status"] = "failure"; + } + + return false; +} diff --git a/external/3rd/library/webAPI/webAPI.h b/external/3rd/library/webAPI/webAPI.h index 84566ed2..ffb9650d 100644 --- a/external/3rd/library/webAPI/webAPI.h +++ b/external/3rd/library/webAPI/webAPI.h @@ -1,17 +1,19 @@ /* -This code is just a simple wrapper around nlohmann's wonderful json lib -(https://github.com/nlohmann/json) and libcurl. While originally included directly, -we have come to realize that we may require web API functionality elsewhere in the future. - -As such, and in an effort to keep the code clean, we've broken it out into this simple little -namespace/lib that is easy to include. Just make sure to link against curl when including, and -make all the cmake modifications required to properly use it. - -(c) stellabellum/swgilluminati (combined crews), written by DA with help from DC -based on the original prototype by parz1val - -License: what's a license? we're a bunch of dirty pirates! -*/ + * Version: 1.3 + * + * This code is just a simple wrapper around nlohmann's wonderful json lib + * (https://github.com/nlohmann/json) and libcurl. While originally included directly, + * we have come to realize that we may require web API functionality elsewhere in the future. + * + * As such, and in an effort to keep the code clean, we've broken it out into this simple little + * namespace/lib that is easy to include. Just make sure to link against curl when including, and + * make all the cmake modifications required to properly use it. + * + * (c) stellabellum/swgilluminati (combined crews), written by DA with help from DC + * based on the original prototype by parz1val + * + * License: what's a license? we're a bunch of dirty pirates! + */ #ifndef webAPI_H #define webAPI_H @@ -24,20 +26,81 @@ License: what's a license? we're a bunch of dirty pirates! #include #endif -namespace webAPI +namespace StellaBellum { - - struct statusMessage + class webAPI { - bool status; - std::string retVal; + public: + // useragent + std::string userAgent; + + // constructor - can setup with the endpoint from the start + webAPI(std::string endpoint, std::string userAgent = "StellaBellum webAPI"); + ~webAPI(); + + // submits the request - respType 0 is json, else raw, get = 0 post = 1 + bool submit(const int &reqType = 0, const int &getPost = 1, const int &respType = 0); + + // set the endpoint after object creation...or change the target if needed + bool setEndpoint(std::string endpoint); + + // get raw response + std::string getRaw(); + + // set a standard request string + bool setData(std::string &data); // all or nothing + + // set json key and value for request + template bool addJsonData(std::string key, T value) + { + if (!key.empty()) + { + requestData[key] = value; + return true; + } + + return false; + } + + // get json response slot + template T getRespValue(std::string slot) + { + if (!responseData.is_null() && !slot.empty() && responseData.count(slot)) + { + return responseData[slot].get(); + } + + return nullptr; + } + + private: + // json request data - object is serialized before sending, used with above setter template + nlohmann::json requestData; + + // json response, stored so we can use the getter template above + nlohmann::json responseData; + + // raw response + std::string sResponse; + + // raw request string + std::string sRequest; + + // API endpoint + std::string uri; + + // fetcher - returns raw response direct from remote - 0 for get, 1 for post, 0 for json mime, 1 for standard + bool fetch(const int &getPost = 1, const int &mimeType = 0); + + // cURL writeback callback + static size_t writeCallback(void *contents, size_t size, size_t nmemb, void *userp); + + // json processor - string to json + bool processJSON(); + + protected: + // http response code (200, 404, etc) + long statusCode; }; - - using namespace std; - - statusMessage simplePost(const string &endpoint, const string &data, const string &slotName = "success", const string &messageSlot ="message", const string &statusSlot = "status", const string &statusVal = "success"); - nlohmann::json request(const string &endpoint, const string &data, const int &reqType = 1); // 1 for post, 0 for get - size_t writeCallback(void *contents, size_t size, size_t nmemb, void *userp); -}; - +} #endif