initial webserver implementation

git-svn-id: https://svn.code.sf.net/p/speed-dreams/code/trunk@6260 30fe4595-0a0c-4342-8851-515496e4dcbd

Former-commit-id: d37f36df1e9977949815b066e227d8ec52fd06c2
Former-commit-id: fca3ca6fc182472d6c7070bd7e8e30f7ea517835
This commit is contained in:
madbad 2015-11-23 19:43:24 +00:00
parent cb13a64c13
commit 8223cded18
4 changed files with 570 additions and 0 deletions

View file

@ -55,6 +55,397 @@
#include "raceinit.h"
//webserver requirements
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//#include <curl/curl.h>
#include <curl/multi.h>
#include <playerpref.h>
//webserver utility
//replace
void replaceAll(std::string& str, const std::string& from, const std::string& to) {
if(from.empty())
return;
size_t start_pos = 0;
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
str.replace(start_pos, from.length(), to);
start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
}
}
//to string (from c++11)
template <typename T>
std::string to_string(T value)
{
std::ostringstream os ;
os << value ;
return os.str() ;
}
CURLM* multi_handle;
/* START webserver*/
WebServer::WebServer(){
void *configHandle;
char configFileUrl[256];
//initialize some curl var
multi_handle = curl_multi_init();
this->handle_count = 0;
//get the preferencies file location
sprintf(configFileUrl, "%s%s", GfLocalDir(), "config/webserver.xml");
//read the preferencies file
configHandle = GfParmReadFile(configFileUrl, GFPARM_RMODE_REREAD);
//get webServer url from the config
this->url = GfParmGetStr(configHandle, "WebServer Settings", "url","val");
GfLogInfo("WebServer - webserver url is: %s\n", this->url);
}
WebServer::~WebServer(){
//cleanup curl
curl_multi_cleanup(multi_handle);
}
int WebServer::updateAsyncStatus(){
//perform the pending requests
curl_multi_perform(multi_handle, &this->handle_count);
if( this->handle_count>0){
GfLogInfo("############################# ASYNC WAITING UPDATES: %i\n", this->handle_count);
}
CURLMsg *msg;
CURL *eh=NULL;
CURLcode return_code;
int http_status_code;
const char *szUrl;
while ((msg = curl_multi_info_read(multi_handle, &this->handle_count))) {
if (msg->msg == CURLMSG_DONE) {
eh = msg->easy_handle;
return_code = msg->data.result;
if(return_code!=CURLE_OK) {
fprintf(stderr, "CURL error code: %d\n", msg->data.result);
continue;
}
// Get HTTP status code
http_status_code=0;
szUrl = NULL;
curl_easy_getinfo(eh, CURLINFO_RESPONSE_CODE, &http_status_code);
//curl_easy_getinfo(eh, CURLINFO_PRIVATE, &szUrl);
curl_easy_getinfo(eh, CURLINFO_EFFECTIVE_URL, &szUrl);
//the server replyed
if(http_status_code==200) {
printf("200 OK for %s\n", szUrl);
GfLogInfo("############################# ASYNC SERVER REPLY:\n %s\n", this->curlServerReply.c_str());
//manage server replyes
//read the xml reply of the server
void *xmlReply;
xmlReply = GfParmReadBuf(const_cast<char*>(this->curlServerReply.c_str()));
//login reply
//store the webServer session and id assigned
if(GfParmGetNum(xmlReply, "content/reply/login", "id", "null",0) != 0){
this->sessionId = GfParmGetStr(xmlReply, "content/reply/login", "sessionid", "null");
this->userId = GfParmGetNum(xmlReply, "content/reply/login", "id", "null",0);
}
//race reply
//store the webServer assigned race id
if(GfParmGetNum(xmlReply, "content/reply/races", "id", "null", 0) != 0){
this->raceId = (int)GfParmGetNum(xmlReply, "content/reply/races", "id", "null", 0);
GfLogInfo("WebServer - assigned race id is: %i\n", this->raceId);
}
//empty the string
this->curlServerReply.clear();
} else {
fprintf(stderr, "GET of %s returned http status code %d\n", szUrl, http_status_code);
}
curl_multi_remove_handle(multi_handle, eh);
curl_easy_cleanup(eh);
/* then cleanup the formpost chain */
//curl_formfree(formpost);
}
else {
fprintf(stderr, "error: after curl_multi_info_read(), CURLMsg=%d\n", msg->msg);
}
}
return 0;
}
int WebServer::addAsyncRequest(std::string const data){
GfLogInfo("############################# ADD ASYNC REQUEST:\n %s \n", data.c_str());
CURL* curl = NULL;
struct curl_httppost *formpost=NULL;
struct curl_httppost *lastptr=NULL;
//struct MemoryStruct chunk;
//
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, this->url);
//curl_easy_setopt(curl, CURLOPT_URL, testurl);
// send all data to this function
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteStringCallback);
//pass our std::string to the WriteStringCallback functions so it can write into it
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &this->curlServerReply);
// some servers don't like requests that are made without a user-agent
// field, so we provide one
curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
//prepare the form-post to be sent to the server
curl_formadd(&formpost,
&lastptr,
CURLFORM_COPYNAME, "data", //the field name where the data will be stored
CURLFORM_COPYCONTENTS, data.c_str(), //the actual data
CURLFORM_END);
//inform curl to send the form-post
curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
}
//add the request to the queque
curl_multi_add_handle(multi_handle, curl);
return 0;
}
int WebServer::sendGenericRequest (std::string data, std::string& serverReply){
CURL *curl;
CURLcode res;
GfLogInfo("WebServer - SENDING data to server:\n %s \n", data.c_str());
//insert "data=" before the actual data
data.insert(0,"data=");
const char *postthis=data.c_str();
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, this->url);
// send all data to this function
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteStringCallback);
//pass our std::string to the WriteStringCallback functions so it can write into it
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &this->curlServerReply);
// some servers don't like requests that are made without a user-agent
// field, so we provide one
curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postthis);
// if we don't provide POSTFIELDSIZE, libcurl will strlen() by
// itself
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)strlen(postthis));
// Perform the request, res will get the return code
res = curl_easy_perform(curl);
// Check for errors
if(res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
}
else {
//
// Now, our this->curlServerReply
// contains the remote file (aka serverReply).
//
// Do something nice with it!
//
GfLogInfo("WebServer - RECEIVING data from server:\n %s\n", this->curlServerReply.c_str());
//empty the string
this->curlServerReply.clear();
}
// always cleanup
curl_easy_cleanup(curl);
// we're done with libcurl, so clean it up
curl_global_cleanup();
}
return 0;
}
int WebServer::readUserConfig (int userId){
void *prHandle;
char prefFileUrl[256];
char xmlPath[256];
// find the xmlPath to our specific user in the preferencies xml file
sprintf(xmlPath, "%s%i", "Preferences/Drivers/", userId);
//get the preferencies file location
sprintf(prefFileUrl, "%s%s", GfLocalDir(), HM_PREF_FILE);
//read the preferencies file
prHandle = GfParmReadFile(prefFileUrl, GFPARM_RMODE_REREAD);
//get webServer user id for current user
this->username = GfParmGetStr(prHandle, xmlPath, "WebServerUsername","val");
//get webServer user password for current user
this->password = GfParmGetStr(prHandle, xmlPath, "WebServerPassword","val");
return 0;
}
int WebServer::sendLogin (int userId){
std::string serverReply;
//read username and password and save it in as webserver properties
this->readUserConfig(userId);
//prepare the string to send
std::string dataToSend ("");
dataToSend.append( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
"<content>"
"<request>"
"<login>"
"<username>{{username}}</username>"
"<password>{{password}}</password>"
"</login>"
"</request>"
"</content>");
//replace the {{tags}} with the respecting values
replaceAll(dataToSend, "{{username}}", this->username);
replaceAll(dataToSend, "{{password}}", this->password);
GfLogInfo("WebServer - sending LOGIN info\n");
this->sendGenericRequest(dataToSend, serverReply);
//read the xml reply of the server
void *xmlReply;
xmlReply = GfParmReadBuf(const_cast<char*>(serverReply.c_str()));
//store the webServer session and id assigned
this->sessionId = GfParmGetStr(xmlReply, "content/reply/login", "sessionid", "null");
this->userId = GfParmGetNum(xmlReply, "content/reply/login", "id", "null",0);
GfLogInfo("WebServer - assigned session id is: %s\n", this->sessionId);
return 0;
}
int WebServer::sendLap (int race_id, double laptime, double fuel, int position, int wettness){
//prepare the string to send
std::string dataToSend ("");
dataToSend.append( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
"<content>"
"<request>"
"<laps>"
"<race_id>{{race_id}}</race_id>"
"<laptime>{{laptime}}</laptime>"
"<fuel>{{fuel}}</fuel>"
"<position>{{position}}</position>"
"<wettness>{{wettness}}</wettness>"
"</laps>"
"</request>"
"</content>");
//replace the {{tags}} with the respecting values
replaceAll(dataToSend, "{{race_id}}", to_string(race_id));
replaceAll(dataToSend, "{{laptime}}", to_string(laptime));
replaceAll(dataToSend, "{{fuel}}", to_string(fuel));
replaceAll(dataToSend, "{{position}}", to_string(position));
replaceAll(dataToSend, "{{wettness}}", to_string(wettness));
GfLogInfo("WebServer - sending LAP info\n");
this->addAsyncRequest(dataToSend);
return 0;
}
int WebServer::sendRaceStart (int user_skill, const char *track_id, char *car_id, int type, void *setup, int startposition, const char *sdversion){
std::string serverReply;
std::string mysetup;
std::string dataToSend;
//read the setup
GfParmWriteString(setup, mysetup);
//prepare the string to send
dataToSend.append( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
"<content>"
"<request>"
"<races>"
"<user_id>{{user_id}}</user_id>"
"<user_skill>{{user_skill}}</user_skill>"
"<track_id>{{track_id}}</track_id>"
"<car_id>{{car_id}}</car_id>"
"<type>{{type}}</type>"
"<setup><![CDATA[{{setup}}]]></setup>"
"<startposition>{{startposition}}</startposition>"
"<sdversion>{{sdversion}}</sdversion>"
"</races>"
"</request>"
"</content>");
//replace the {{tags}} with the respecting values
replaceAll(dataToSend, "{{user_id}}", to_string(this->userId));
replaceAll(dataToSend, "{{user_skill}}", to_string(user_skill));
replaceAll(dataToSend, "{{track_id}}", to_string(track_id));
replaceAll(dataToSend, "{{car_id}}", to_string(car_id));
replaceAll(dataToSend, "{{type}}", to_string(type));
replaceAll(dataToSend, "{{setup}}", mysetup);
replaceAll(dataToSend, "{{startposition}}", to_string(startposition));
replaceAll(dataToSend, "{{sdversion}}", to_string(sdversion));
GfLogInfo("WebServer - sending RACE START info\n");
this->addAsyncRequest(dataToSend);
return 0;
}
int WebServer::sendRaceEnd (int race_id, int endposition){
std::string serverReply;
//prepare the string to send
std::string dataToSend ("");
dataToSend.append( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
"<content>"
"<request>"
"<races>"
"<id>{{race_id}}</id>"
"<endposition>{{endposition}}</endposition>"
"</races>"
"</request>"
"</content>");
//replace the {{tags}} with the respecting values
replaceAll(dataToSend, "{{race_id}}", to_string(race_id));
replaceAll(dataToSend, "{{endposition}}", to_string(endposition));
GfLogInfo("WebServer - sending RACE END information \n");
this->sendGenericRequest(dataToSend, serverReply);
return 0;
}
//initialize the web server
WebServer webServer;
/* END webserver*/
static const char *aPszSkillLevelNames[] =
{ ROB_VAL_ARCADE, ROB_VAL_SEMI_ROOKIE, ROB_VAL_ROOKIE, ROB_VAL_AMATEUR, ROB_VAL_SEMI_PRO, ROB_VAL_PRO };
@ -955,6 +1346,29 @@ ReInitCars(void)
#endif
}
// webServer lap logger.
//Find human cars
for (int i = 0; i < ReInfo->s->_ncars; i++) {
if(ReInfo->s->cars[i]->_driverType == RM_DRV_HUMAN){
//login
webServer.sendLogin(ReInfo->s->cars[i]->_driverIndex);
//send race data
webServer.sendRaceStart (
ReInfo->s->cars[i]->_skillLevel, //user_skill,
ReInfo->track->internalname, //track_id,
ReInfo->s->cars[i]->_carName, //car_id
ReInfo->s->_raceType, //type of race: 0 practice/ 1 qualify/ 2 race
ReInfo->s->cars[i]->_carHandle, //car setup file,
ReInfo->s->cars[i]->_pos, //car starting position,
VERSION_LONG //speed dreams version
);
}
}
ReInfo->_rePitRequester = 0;
// TODO: reconsider splitting the call into one for cars, track and maybe other objects.

View file

@ -58,6 +58,85 @@ extern tModList *ReRacingRobotsModList;
struct RmInfo;
extern struct RmInfo *ReInfo;
// WEBSERVER class and utilus
struct MemoryStruct {
char *memory;
size_t size;
};
/*
static size_t
WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)userp;
mem->memory =(char*)realloc(mem->memory, mem->size + realsize + 1);
if(mem->memory == NULL) {
// out of memory!
printf("not enough memory (realloc returned NULL)\n");
return 0;
}
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
* */
static size_t WriteStringCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
((std::string*)userp)->append((char*)contents, size * nmemb);
return size * nmemb;
}
class WebServer {
public:
int readUserConfig(int userId);
int sendGenericRequest (std::string data, std::string& serverReply);
int sendLogin (int userId);
int sendLap (int race_id, double laptime, double fuel, int position, int wettness);
int sendRaceStart (int user_skill, const char *track_id, char *car_id, int type, void *setup, int startposition, const char *sdversion);
int sendRaceEnd (int race_id, int endposition);
int raceId;
//user info
int userId;
int previousLaps;
const char* username;
const char* password;
const char* sessionId;
//config
const char* url;
//async requests
int updateAsyncStatus();
int addAsyncRequest(std::string const data);
//curl
//CURLM multi_handle;
int handle_count;
std::string curlServerReply;
//constructor
WebServer();
//destructor
~WebServer();
};
/*
* from raciini.cpp
* ReInfo->carList[carindex]
* (elt is a car from ReInfo carlist)
* elt->_paramsHandle => car setup params?
* elt->_carHandle
* line 671
* */
#endif /* _RACEINIT_H_ */

View file

@ -37,6 +37,12 @@
#include "racestate.h"
#include "raceresults.h"
//webserver requirements
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#include "raceinit.h"
//TODO: is it still necessary?
//static const char *aSessionTypeNames[3] = {"Practice", "Qualifications", "Race"};
@ -449,6 +455,26 @@ ReStoreRaceResults(const char *race)
break;
}
}
// webServer lap logger.
extern WebServer webServer;
// webServer lap logger.
//Find human cars
for (int i = 0; i < ReInfo->s->_ncars; i++) {
if(ReInfo->s->cars[i]->_driverType == RM_DRV_HUMAN){
//read webServer config from user preferencies file
//webServer.readUserConfig(ReInfo->s->cars[i]->_driverIndex);
//login
webServer.sendLogin(ReInfo->s->cars[i]->_driverIndex);
//send race data
webServer.sendRaceEnd (
webServer.raceId,
ReInfo->s->cars[i]->_pos //car end position,
);
}
}
}
void

View file

@ -48,6 +48,21 @@
#include "racemessage.h"
#include "racenetwork.h"
//webserver requirements
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#include "raceinit.h"
//==/CURL
//==ALTRO
//double bestNetworkLapTime = 180;
//int previousLaps[100]; /*harcoded max number of cars on track*/
//==/ALTRO
//madbad-end
// The singleton.
ReSituation* ReSituation::_pSelf = 0;
@ -369,6 +384,42 @@ void ReSituationUpdater::runOneStep(double deltaTimeIncrement)
if (replayRecord && pCurrReInfo->s->currentTime >= replayTimestamp) {
replaySituation(pCurrReInfo);
}
// webServer lap logger.
extern WebServer webServer;
//Find human cars
for (int i = 0; i < pCurrReInfo->s->_ncars; i++) {
if(pCurrReInfo->s->cars[i]->_driverType == RM_DRV_HUMAN){
//if at least a lap has been done and a lap is passed then log it to the webServer
if(pCurrReInfo->s->cars[i]->_laps > 1 && pCurrReInfo->s->cars[i]->_laps > webServer.previousLaps){
//remember the current number of laps for next cicle
webServer.previousLaps = pCurrReInfo->s->cars[i]->_laps;
//GfLogInfo("############rain: %i, ",trackLocal->rain); //0=no 1=little 2=medium 3=heawy
//GfLogInfo("############WATER: %i, ",trackLocal->water); //0=no 1=little 2=medium 3=heawy
//send the lap info to the server
webServer.sendLap(
webServer.raceId, //race_id
pCurrReInfo->s->cars[i]->_lastLapTime, //laptime
pCurrReInfo->s->cars[i]->_fuel, //car remaining fuel at the end of the lap
pCurrReInfo->s->cars[i]->_pos, //car position
trackLocal->water //level of water on track
//VERSION_LONG, //speed dreams version
//pCurrReInfo->s->cars[i]->_name, //player name
//pCurrReInfo->s->cars[i]->_skillLevel, //player skill level: 0 rokie/ 1 amateour/ 2 semi-pro/ 3 pro
//pCurrReInfo->track->name, //track name
//strName, //os info
//pCurrReInfo->s->_raceType, //type of race: 0 practice/ 1 qualify/ 2 race
//pCurrReInfo->s->cars[i]->_carName, //car name
//pCurrReInfo->s->cars[i]->_category, //car category
);
}
}
}
webServer.updateAsyncStatus();
}
int ReSituationUpdater::threadLoop(void* pUpdater)