make the webAPI more generic so that we can use it in a more standardized fashion

This commit is contained in:
DarthArgus
2016-08-20 11:19:23 +00:00
parent e0d9f58ba0
commit 5f0e741a72
5 changed files with 302 additions and 132 deletions
@@ -123,9 +123,10 @@
#include <stdio.h>
// 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<int>("totalPlayerCount", m_totalPlayerCount);
api.addJsonData<int>("totalGameServers", (m_gameServers.size() - 1));
api.addJsonData<int>("totalPlanetServers", m_planetServers.size());
api.addJsonData<bool>("isPublic", getIsClusterPublic());
api.addJsonData<bool>("isLocked", getIsClusterLocked());
api.addJsonData<bool>("isSecret", getIsClusterSecret());
api.addJsonData<bool>("preloadFinished", getClusterStartupTime());
api.addJsonData<bool>("databasebacklogged", isDatabaseBacklogged());
api.addJsonData<int>("totalTutorialSceneCount", getClusterStartupTime());
api.addJsonData<int>("totalFalconSceneCount", isDatabaseBacklogged());
#ifdef _DEBUG
if (api.submit()) {
std::string status = api.getRespValue<std::string>("status");
if (status.empty() || status != "success")
{
std::string message = api.getRespValue<std::string>("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
}
//-----------------------------------------------------------------------
@@ -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);
@@ -23,9 +23,12 @@
#include "sharedNetworkMessages/GenericValueTypeMessage.h"
#include "sharedNetworkMessages/LoginEnumCluster.h"
#include "webAPI.h"
#include <algorithm>
#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<std::string>("user_name", id);
api.addJsonData<std::string>("user_password", key);
api.addJsonData<std::string>("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<std::string>("status");
uname = api.getRespValue<std::string>("username");
if (!status.empty() && status == "success" && !uname.empty())
{
authOK = true;
}
else
{
std::string msg = api.getRespValue<std::string>("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);
}
}
+138 -83
View File
@@ -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<std::string>() == statusVal && response.count(slotName))
{
return {true, response[slotName].get<std::string>()};
}
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<std::string>()};
}
}
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;
}
+89 -26
View File
@@ -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 <curl/curl.h>
#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<typename T> bool addJsonData(std::string key, T value)
{
if (!key.empty())
{
requestData[key] = value;
return true;
}
return false;
}
// get json response slot
template<typename T> T getRespValue(std::string slot)
{
if (!responseData.is_null() && !slot.empty() && responseData.count(slot))
{
return responseData[slot].get<T>();
}
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