From beb15fce41526ec7b7ce4eeeda328e201e54f0e1 Mon Sep 17 00:00:00 2001 From: seefoe Date: Mon, 30 Apr 2018 13:56:48 -0400 Subject: [PATCH] Updated webAPI, fixed several bugs in SB webAPI and in CentralServer sendMetrics --- .../src/shared/CentralServer.cpp | 27 +- .../src/shared/ClientConnection.cpp | 29 +- external/3rd/library/webAPI/webAPI.cpp | 292 +++++++++++------- external/3rd/library/webAPI/webAPI.h | 139 +++++++-- 4 files changed, 332 insertions(+), 155 deletions(-) diff --git a/engine/server/application/CentralServer/src/shared/CentralServer.cpp b/engine/server/application/CentralServer/src/shared/CentralServer.cpp index 949cd705..0b531b54 100755 --- a/engine/server/application/CentralServer/src/shared/CentralServer.cpp +++ b/engine/server/application/CentralServer/src/shared/CentralServer.cpp @@ -2843,7 +2843,7 @@ void CentralServer::run(void) void CentralServer::update() { static int loopCount = 0; - static int apiLoopCount = 0; + static uint32 apiLastTrick = 0; static int shutdownCheckLoopCount = 0; m_curTime = static_cast(time(0)); @@ -2860,13 +2860,12 @@ void CentralServer::update() // update the webAPI if specified int webUpdateIntervalSeconds = ConfigCentralServer::getWebUpdateIntervalSeconds(); - - // assuming that every 5th frame is ~1 second, we can multiply and then check - if (webUpdateIntervalSeconds && (++apiLoopCount > (webUpdateIntervalSeconds*1000)) ) + if (webUpdateIntervalSeconds && m_curTime - apiLastTrick >= static_cast(webUpdateIntervalSeconds)) { - apiLoopCount = 0; - - // update the web api +#ifdef _DEBUG + WARNING(true, ("Sending web metrics since last tick was %d seconds ago", (m_curTime - apiLastTrick))); +#endif + apiLastTrick = m_curTime; sendMetricsToWebAPI(); } @@ -2939,18 +2938,20 @@ void CentralServer::sendPopulationUpdateToLoginServer() void CentralServer::sendMetricsToWebAPI() { - /*static const std::string metricsURL(ConfigCentralServer::getMetricsDataURL()); + static const std::string metricsURL(ConfigCentralServer::getMetricsDataURL()); if (!metricsURL.empty()) { - // create the object - webAPI api(metricsURL); + StellaBellum::webAPI api(metricsURL); - // add our data + api.addJsonData("clusterName", ConfigCentralServer::getClusterName()); api.addJsonData("totalPlayerCount", m_totalPlayerCount); - api.addJsonData("totalGameServers", (m_gameServers.size() - 1)); + api.addJsonData("totalGameServers", m_gameServers.size()); api.addJsonData("totalPlanetServers", m_planetServers.size()); api.addJsonData("totalTutorialSceneCount", m_totalTutorialSceneCount); + api.addJsonData("lastLoadingStateTime", m_lastLoadingStateTime); + api.addJsonData("clusterStartupTime", m_clusterStartupTime); + api.addJsonData("timeClusterWentIntoLoadingState", m_timeClusterWentIntoLoadingState); #ifdef _DEBUG if (api.submit()) { @@ -2977,7 +2978,7 @@ void CentralServer::sendMetricsToWebAPI() #else api.submit(); #endif - }*/ + } } //----------------------------------------------------------------------- diff --git a/engine/server/application/LoginServer/src/shared/ClientConnection.cpp b/engine/server/application/LoginServer/src/shared/ClientConnection.cpp index 2bffc108..857357c4 100755 --- a/engine/server/application/LoginServer/src/shared/ClientConnection.cpp +++ b/engine/server/application/LoginServer/src/shared/ClientConnection.cpp @@ -177,21 +177,26 @@ void ClientConnection::validateClient(const std::string & id, const std::string if (!authURL.empty()) { - std::ostringstream postBuf; - postBuf << "user_name=" << trimmedId << "&user_password=" << trimmedKey << "&stationID=" << suid << "&ip=" << getRemoteAddress(); - - std::string response = webAPI::simplePost(authURL, std::string(postBuf.str()), ""); - - if (response == "success") - { - authOK = 1; + StellaBellum::webAPI api(authURL); + + api.addJsonData("user_name", trimmedId); + api.addJsonData("user_password", trimmedKey); + api.addJsonData("ip", getRemoteAddress()); + + if (api.submit()) { + std::string msg(api.getString("message")); + + if(msg == "success") { + authOK = 1; + } else { + ErrorMessage err("Login Message", msg); + this->send(err, true); + } } - else - { - ErrorMessage err("Login Failed", response); + else { + ErrorMessage err("Login Failed", "request failed"); this->send(err, true); } - } else { diff --git a/external/3rd/library/webAPI/webAPI.cpp b/external/3rd/library/webAPI/webAPI.cpp index b9637b03..5931d3b6 100644 --- a/external/3rd/library/webAPI/webAPI.cpp +++ b/external/3rd/library/webAPI/webAPI.cpp @@ -1,123 +1,207 @@ /* -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.75 + * + * 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) DarthArgus + * based on the original prototype by parz1val + * + * License: LGPL, don't be a dick please + */ #include "webAPI.h" -using namespace std; +using namespace StellaBellum; -// if status == success, returns "success", or slotName's contents if specified... -// otherwise returns the "message" if no success -string webAPI::simplePost(string endpoint, string data, string slotName) -{ - // declare our output and go ahead and attempt to get data from remote - nlohmann::json response = request(endpoint, data, 1); - string output; +webAPI::webAPI(std::string endpoint, std::string userAgent) : uri(endpoint), userAgent(userAgent), statusCode(0) {} - // if we got data back... - if (response.count("status") && response["status"].get() == "success") - { - // use custom slot if specified (not "") - if (!(slotName.empty()) && response.count(slotName)) - { - output = response[slotName].get(); - } - else - { - output = "success"; - } - } - else //default message is an error, the other end always assumes "success" or the specified slot - { - if (response.count("message")) - { - output = response["message"].get(); - } - else - { - output = "Message not provided by remote."; - } - } - - return output; +webAPI::~webAPI() { + 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(string endpoint, string data, int reqType) +bool webAPI::setEndpoint(const std::string endpoint) { + uri = endpoint; + + return true; +} + +std::string webAPI::getRaw() { + return sResponse; +} + +bool webAPI::setData(std::string &data) { + if (!data.empty()) { + sRequest = data; + + return true; + } + + return false; +} + +std::string webAPI::getString(const std::string &slot) { + if (!responseData.empty() && !slot.empty() && responseData.count(slot) && !responseData[slot].is_null()) { + return responseData[slot].get(); + } + + return std::string(""); +} + +std::unordered_map webAPI::getStringMap(const std::string &slot) { + std::unordered_map ret = std::unordered_map(); + + if (!responseData.empty() && !slot.empty() && responseData.count(slot) && !responseData[slot].is_null()) { + + nlohmann::json j = responseData[slot]; + + for (nlohmann::json::iterator it = j.begin(); it != j.end(); ++it) { + int k = std::stoi(it.key()); + std::string val = it.value(); + + ret.insert({k, val}); + } + } + + return ret; +} + +bool webAPI::submit(const int &reqType, const int &getPost, const int &respType) { + if (reqType == DTYPE::JSON) // json request + { + if (!requestData.empty()) { + // serialize our data into sRequest + sRequest = requestData.dump(); + + // clear our the object for next time + requestData.clear(); + } + } + + 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 { - nlohmann::json response; + bool fetchStatus = false; - if (!endpoint.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 (!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) + if (curl) { + std::string readBuffer = ""; // container for the remote response + struct curl_slist *slist = nullptr; + + // set the content type + if (mimeType == DTYPE::JSON) { + slist = curl_slist_append(slist, "Accept: application/json"); + slist = curl_slist_append(slist, "Content-Type: application/json"); + } else { + slist = curl_slist_append(slist, "Content-Type: application/x-www-form-urlencoded"); + } + + slist = curl_slist_append(slist, "charsets: utf-8"); + + CURLcode res = curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent.c_str()); + res = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback); // place the data into readBuffer using writeCallback + res = curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); // specify readBuffer as the container for data + res = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); + + switch (getPost) { + case HTTP::GET: + res = curl_easy_setopt(curl, CURLOPT_URL, std::string(uri + "?" + sRequest).c_str()); + break; + case HTTP::POST: + res = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, sRequest.c_str()); + res = curl_easy_setopt(curl, CURLOPT_URL, uri.c_str()); + break; + // want to do a put, or whatever other type? feel free to add here + } + + // I suggest leaving VERIFYPEER = 0 because system SSL stores tend to be outdated + //if (uri.find(vxENCRYPT("stellabellum").decrypt()) != std::string::npos) { + // the public one will verify but since this is pinned we don't care about the CA + // to grab/generate, see https://curl.haxx.se/libcurl/c/CURLOPT_PINNEDPUBLICKEY.html + // under the PUBLIC KEY EXTRACTION heading + res = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + + // if you want to pin to your own cert or cloudflares, learn how and use the below + // res = curl_easy_setopt(curl, CURLOPT_PINNEDPUBLICKEY, vxENCRYPT("sha256//YOURKEYHERE").decrypt()); + //} + + if (res == CURLE_OK) { + res = curl_easy_perform(curl); // make the request! + } + + if (res == CURLE_OK) { + char *contentType; + + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &statusCode); //get status code + curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &contentType); // get response mime type + + std::string conType(contentType); + + if (statusCode == 200 && !(readBuffer.empty())) // check it all out and parse { - string readBuffer; // container for the remote response - long http_code = 0; // we get this after performing the get or post + sResponse = readBuffer; + if (conType.find("application/json") != std::string::npos) { + fetchStatus = processJSON(); + } else { + responseData.clear(); + fetchStatus = true; + } + } + } - curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str()); // endpoint is always specified by caller - 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 + curl_slist_free_all(slist); + curl_easy_cleanup(curl); // always wipe our butt + } + } - if (reqType == 1) - { - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); - } + if (!fetchStatus) { + sResponse.clear(); + responseData.clear(); + } - CURLcode res = curl_easy_perform(curl); // make the request! - - curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &http_code); //get status code - - if (res == CURLE_OK && http_code == 200 && !(readBuffer.empty())) // check it all out and parse - { - 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_cleanup(curl); // always wipe our butt - } - else //default err messages below - { - response["message"] = "Failed to initialize cURL."; - response["status"] = "failure"; - } - } - else - { - response["message"] = "Invalid endpoint URL."; - response["status"] = "failure"; - } - - return response; + return fetchStatus; } // 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); - return size * nmemb; +size_t webAPI::writeCallback(void *contents, size_t size, size_t nmemb, void *userp) { + ((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; +} \ No newline at end of file diff --git a/external/3rd/library/webAPI/webAPI.h b/external/3rd/library/webAPI/webAPI.h index 51e60a2c..c45f99aa 100644 --- a/external/3rd/library/webAPI/webAPI.h +++ b/external/3rd/library/webAPI/webAPI.h @@ -1,35 +1,122 @@ /* -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.75 + * + * 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) DarthArgus + * based on the original prototype by parz1val + * + * License: LGPL, don't be a dick please + */ #ifndef webAPI_H #define webAPI_H #include "json.hpp" + +#ifdef WIN32 +#include +#else + +#include #include -namespace webAPI -{ - using namespace std; - - string simplePost(string endpoint, string data, string slotName); - //std::string simpleGet(char* endpoint, char* data); - //nlohmann::json post(char* endpoint, char* data); - //nlohmann::json get(char* endpoint, char* data); - - nlohmann::json request(string endpoint, string data, int reqType); // 1 for post, 0 for get - size_t writeCallback(void *contents, size_t size, size_t nmemb, void *userp); -}; - +#endif + +#include "../libLeff/libLeff.h" + +namespace StellaBellum { + enum HTTP { + GET = 0, POST = 1 + }; + enum DTYPE { + JSON = 0, RAW = 1 + }; + + class webAPI { + 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 + bool + submit(const int &reqType = DTYPE::JSON, const int &getPost = HTTP::POST, const int &respType = DTYPE::JSON); + + // set the endpoint after object creation...or change the target if needed + bool setEndpoint(const std::string endpoint); + + // get raw response + std::string getRaw(); + + // set a standard request string + bool setData(std::string &data); // all or nothing + + // get a string from a given slot + std::string getString(const std::string &slot); + + // get a vector of strings from a given slot + std::unordered_map getStringMap(const std::string &slot); + + // set json key and value for request + template bool addJsonData(const std::string &key, const T &value) { + if (!key.empty() && + responseData.count(key) == 0) // only alow one of a given key for now, unless we support nesting later + { + requestData[key] = value; + return true; + } + + return false; + } + + // get json response slot + template T getNullableValue(const std::string &slot) { + if (!responseData.empty() && !slot.empty() && responseData.count(slot)) { + return responseData[slot].get(); + } + + return 0; + } + + 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 + bool fetch(const int &getPost = HTTP::POST, const int &mimeType = DTYPE::JSON); + + // 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; + }; +} #endif