POST requests for APIRequest, allow saving user details from within the game, Asynchronous HTTP POST

This commit is contained in:
Simon Robertshaw 2013-03-21 21:49:06 +00:00
parent b4564f212a
commit 81a34222d1
9 changed files with 425 additions and 11 deletions

View File

@ -3,6 +3,7 @@
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <iomanip>
#include <time.h>
#include <stdio.h>
@ -1181,6 +1182,40 @@ std::vector<unsigned char> Client::GetSaveData(int saveID, int saveDate)
return saveData;
}
RequestBroker::Request * Client::SaveUserInfoAsync(UserInfo info)
{
class StatusParser: public APIResultParser
{
virtual void * ProcessResponse(unsigned char * data, int dataLength)
{
try
{
std::istringstream dataStream((char*)data);
json::Object objDocument;
json::Reader::Read(objDocument, dataStream);
json::Number tempStatus = objDocument["Status"];
bool returnValue = tempStatus.Value() == 1;
return (void*)(returnValue ? 1 : 0);
}
catch (json::Exception &e)
{
return 0;
}
}
virtual void Cleanup(void * objectPtr)
{
//delete (UserInfo*)objectPtr;
}
virtual ~StatusParser() { }
};
std::map<std::string, std::string> postData;
postData.insert(std::pair<std::string, std::string>("Location", info.Location));
postData.insert(std::pair<std::string, std::string>("Biography", info.Biography));
return new APIRequest("http://" SERVER "/Profile.json", postData, new StatusParser());
}
RequestBroker::Request * Client::GetUserInfoAsync(std::string username)
{
class UserInfoParser: public APIResultParser
@ -1197,13 +1232,15 @@ RequestBroker::Request * Client::GetUserInfoAsync(std::string username)
json::Number userIDTemp = tempUser["ID"];
json::String usernameTemp = tempUser["Username"];
json::String bioTemp = tempUser["Biography"];
//json::Number ageTemp = tempUser["Age"];
json::String locationTemp = tempUser["Location"];
json::Number ageTemp = tempUser["Age"];
return new UserInfo(
userIDTemp.Value(),
0,//ageTemp.Value(),
ageTemp.Value(),
usernameTemp.Value(),
bioTemp.Value());
bioTemp.Value(),
locationTemp.Value());
}
catch (json::Exception &e)
{

View File

@ -9,6 +9,7 @@
#include "Singleton.h"
#include "User.h"
#include "UserInfo.h"
#include "cajun/elements.h"
@ -93,6 +94,9 @@ public:
std::vector<std::string> DirectorySearch(std::string directory, std::string search, std::vector<std::string> extensions);
std::vector<std::string> DirectorySearch(std::string directory, std::string search, std::string extension);
std::string FileOpenDialogue();
//std::string FileSaveDialogue();
bool DoInstallation();
std::vector<unsigned char> ReadFile(std::string filename);
@ -130,6 +134,7 @@ public:
//Retrieves a "UserInfo" object
RequestBroker::Request * GetUserInfoAsync(std::string username);
RequestBroker::Request * SaveUserInfoAsync(UserInfo info);
unsigned char * GetSaveData(int saveID, int saveDate, int & dataLength);
std::vector<unsigned char> GetSaveData(int saveID, int saveDate);

View File

@ -1102,3 +1102,194 @@ fail:
*len = 0;
return NULL;
}
void *http_multipart_post_async(char *uri, char **names, char **parts, int *plens, char *user, char *pass, char *session_id)
{
void *ctx;
char *data = NULL, *tmp, *p;
int dlen = 0, i, j;
unsigned char hash[16];
unsigned char boundary[32], ch;
int blen = 0;
unsigned int map[62], m;
struct md5_context md5;
//struct md5_context md52;
int own_plen = 0;
if (names)
{
if (!plens)
{
own_plen = 1;
for (i=0; names[i]; i++) ;
plens = (int *)calloc(i, sizeof(int));
for (i=0; names[i]; i++)
plens[i] = strlen(parts[i]);
}
retry:
if (blen >= 31)
goto fail;
memset(map, 0, 62*sizeof(int));
for (i=0; names[i]; i++)
{
for (j=0; j<plens[i]-blen; j++)
if (!blen || !memcmp(parts[i]+j, boundary, blen))
{
ch = parts[i][j+blen];
if (ch>='0' && ch<='9')
map[ch-'0']++;
else if (ch>='A' && ch<='Z')
map[ch-'A'+10]++;
else if (ch>='a' && ch<='z')
map[ch-'a'+36]++;
}
}
m = ~0;
j = 61;
for (i=0; i<62; i++)
if (map[i]<m)
{
m = map[i];
j = i;
}
if (j<10)
boundary[blen] = '0'+j;
else if (j<36)
boundary[blen] = 'A'+(j-10);
else
boundary[blen] = 'a'+(j-36);
blen++;
if (map[j])
goto retry;
boundary[blen] = 0;
for (i=0; names[i]; i++)
dlen += blen+strlen(names[i])+plens[i]+128;
dlen += blen+8;
data = (char *)malloc(dlen);
dlen = 0;
for (i=0; names[i]; i++)
{
dlen += sprintf(data+dlen, "--%s\r\n", boundary);
dlen += sprintf(data+dlen, "Content-transfer-encoding: binary\r\n");
if (strchr(names[i], ':'))
{
tmp = mystrdup(names[i]);
p = strchr(tmp, ':');
*p = 0;
dlen += sprintf(data+dlen, "content-disposition: form-data; name=\"%s\"; ", tmp);
free(tmp);
p = strchr(names[i], ':');
dlen += sprintf(data+dlen, "filename=\"%s\"\r\n\r\n", p+1);
}
else
dlen += sprintf(data+dlen, "content-disposition: form-data; name=\"%s\"\r\n\r\n", names[i]);
memcpy(data+dlen, parts[i], plens[i]);
dlen += plens[i];
dlen += sprintf(data+dlen, "\r\n");
}
dlen += sprintf(data+dlen, "--%s--\r\n", boundary);
}
ctx = http_async_req_start(NULL, uri, data, dlen, 0);
if (!ctx)
goto fail;
if (user)
{
//http_async_add_header(ctx, "X-Auth-User", user);
if (pass)
{
md5_init(&md5);
md5_update(&md5, (unsigned char *)user, strlen(user));
md5_update(&md5, (unsigned char *)"-", 1);
m = 0;
if (names)
{
for (i=0; names[i]; i++)
{
//md5_update(&md5, (unsigned char *)parts[i], plens[i]); //WHY?
//md5_update(&md5, (unsigned char *)"-", 1);
p = strchr(names[i], ':');
if (p)
m += (p - names[i]) + 1;
else
m += strlen(names[i])+1;
}
tmp = (char *)malloc(m);
m = 0;
for (i=0; names[i]; i++)
{
p = strchr(names[i], ':');
if (m)
{
tmp[m] = ' ';
m ++;
}
if (p)
{
memcpy(tmp+m, names[i], p-names[i]);
m += p - names[i];
}
else
{
strcpy(tmp+m, names[i]);
m += strlen(names[i]);
}
}
tmp[m] = 0;
http_async_add_header(ctx, "X-Auth-Objects", tmp);
free(tmp);
}
md5_update(&md5, (unsigned char *)pass, strlen(pass));
md5_final(hash, &md5);
tmp = (char *)malloc(33);
for (i=0; i<16; i++)
{
tmp[i*2] = hexChars[hash[i]>>4];
tmp[i*2+1] = hexChars[hash[i]&15];
}
tmp[32] = 0;
http_async_add_header(ctx, "X-Auth-Hash", tmp);
free(tmp);
}
if (session_id)
{
http_async_add_header(ctx, "X-Auth-User-Id", user);
http_async_add_header(ctx, "X-Auth-Session-Key", session_id);
}
else
{
http_async_add_header(ctx, "X-Auth-User", user);
}
}
if (data)
{
tmp = (char *)malloc(32+strlen((char *)boundary));
sprintf(tmp, "multipart/form-data, boundary=%s", boundary);
http_async_add_header(ctx, "Content-type", tmp);
free(tmp);
free(data);
}
if (own_plen)
free(plens);
return ctx;
fail:
if (data)
free(data);
if (own_plen)
free(plens);
//if (ret)
// *ret = 600;
//if (len)
// *len = 0;
return NULL;
}

View File

@ -39,6 +39,7 @@ char *http_async_req_stop(void *ctx, int *ret, int *len);
void http_async_req_close(void *ctx);
char *http_multipart_post(char *uri, char **names, char **parts, int *plens, char *user, char *pass, char * session_id, int *ret, int *len);
void *http_multipart_post_async(char *uri, char **names, char **parts, int *plens, char *user, char *pass, char * session_id);
char *http_ret_text(int ret);

View File

@ -10,12 +10,15 @@ public:
int Age;
std::string Username;
std::string Biography;
UserInfo(int id, int age, std::string username, std::string biography):
std::string Location;
UserInfo(int id, int age, std::string username, std::string biography, std::string location):
ID(id),
Age(age),
Username(username),
Biography(biography)
Biography(biography),
Location(location)
{ }
UserInfo() {}
};

View File

@ -1,6 +1,10 @@
#include <iostream>
#include <typeinfo>
#include <cstdlib>
#include <cstring>
#include "Config.h"
#include "Format.h"
#include "client/Client.h"
#include "APIRequest.h"
#include "client/HTTP.h"
#include "APIResultParser.h"
@ -8,6 +12,17 @@
APIRequest::APIRequest(std::string url, APIResultParser * parser, ListenerHandle listener):
RequestBroker::Request(API, listener)
{
Post = false;
HTTPContext = NULL;
Parser = parser;
URL = url;
}
APIRequest::APIRequest(std::string url, std::map<std::string, std::string> postData, APIResultParser * parser, ListenerHandle listener):
RequestBroker::Request(API, listener)
{
Post = true;
PostData = postData;
HTTPContext = NULL;
Parser = parser;
URL = url;
@ -26,17 +41,18 @@ RequestBroker::ProcessResponse APIRequest::Process(RequestBroker & rb)
if (status == 200 && data)
{
void * resultObject = Parser->ProcessResponse((unsigned char *)data, data_size);
free(data);
if(resultObject)
{
this->ResultObject = resultObject;
rb.requestComplete(this);
free(data);
return RequestBroker::Finished;
}
else
{
std::cout << typeid(*this).name() << " Request for " << URL << " could not be parsed" << status << std::endl;
std::cout << typeid(*this).name() << " Request for " << URL << " could not be parsed: " << data << std::endl;
free(data);
return RequestBroker::Failed;
}
}
@ -55,7 +71,50 @@ RequestBroker::ProcessResponse APIRequest::Process(RequestBroker & rb)
else
{
std::cout << typeid(*this).name() << " New Request for " << URL << std::endl;
HTTPContext = http_async_req_start(NULL, (char *)URL.c_str(), NULL, 0, 0);
if(Post)
{
char ** postNames = new char*[PostData.size() + 1];
char ** postData = new char*[PostData.size()];
int * postLength = new int[PostData.size()];
int i = 0;
std::map<std::string, std::string>::iterator iter = PostData.begin();
while(iter != PostData.end())
{
std::string name = iter->first;
std::string data = iter->second;
char * cName = new char[name.length() + 1];
char * cData = new char[data.length() + 1];
std::strcpy(cName, name.c_str());
std::strcpy(cData, data.c_str());
postNames[i] = cName;
postData[i] = cData;
postLength[i] = data.length();
i++;
iter++;
}
postNames[i] = NULL;
if(Client::Ref().GetAuthUser().ID)
{
std::cout << typeid(*this).name() << " Authenticated " << std::endl;
User user = Client::Ref().GetAuthUser();
char userName[12];
char userSession[user.SessionID.length() + 1];
std::strcpy(userName, format::NumberToString<int>(user.ID).c_str());
std::strcpy(userSession, user.SessionID.c_str());
HTTPContext = http_multipart_post_async((char*)URL.c_str(), postNames, postData, postLength, userName, NULL, userSession);
}
else
{
HTTPContext = http_multipart_post_async((char*)URL.c_str(), postNames, postData, postLength, NULL, NULL, NULL);
}
}
else
{
HTTPContext = http_async_req_start(NULL, (char *)URL.c_str(), NULL, 0, 0);
}
//RequestTime = time(NULL);
}
return RequestBroker::OK;

View File

@ -1,13 +1,17 @@
#include <map>
#include "RequestBroker.h"
class APIResultParser;
class APIRequest: public RequestBroker::Request
{
public:
bool Post;
APIResultParser * Parser;
std::string URL;
std::map<std::string, std::string> PostData;
void * HTTPContext;
APIRequest(std::string url, APIResultParser * parser, ListenerHandle listener = ListenerHandle(0, 0));
APIRequest(std::string url, std::map<std::string, std::string>, APIResultParser * parser, ListenerHandle listener = ListenerHandle(0, 0));
virtual RequestBroker::ProcessResponse Process(RequestBroker & rb);
virtual ~APIRequest();
virtual void Cleanup();

View File

@ -3,15 +3,20 @@
#include "interface/Button.h"
#include "interface/Textbox.h"
#include "interface/Label.h"
#include "interface/AvatarButton.h"
#include "interface/ScrollPanel.h"
#include "interface/Keys.h"
#include "Style.h"
#include "client/Client.h"
#include "client/UserInfo.h"
#include "client/requestbroker/RequestListener.h"
ProfileActivity::ProfileActivity(std::string username) :
WindowActivity(ui::Point(-1, -1), ui::Point(236, 302))
WindowActivity(ui::Point(-1, -1), ui::Point(236, 200)),
loading(false),
saving(false)
{
bool editable = Client::Ref().GetAuthUser().ID && Client::Ref().GetAuthUser().Username == username;
editable = Client::Ref().GetAuthUser().ID && Client::Ref().GetAuthUser().Username == username;
class CloseAction: public ui::ButtonAction
@ -32,6 +37,15 @@ ProfileActivity::ProfileActivity(std::string username) :
SaveAction(ProfileActivity * a) : a(a) { }
void ActionCallback(ui::Button * sender_)
{
if(!a->loading && !a->saving && a->editable)
{
sender_->Enabled = false;
sender_->SetText("Saving...");
a->saving = true;
a->info.Location = ((ui::Textbox*)a->location)->GetText();
a->info.Biography = ((ui::Textbox*)a->bio)->GetText();
RequestBroker::Ref().Start(Client::Ref().SaveUserInfoAsync(a->info), a);
}
}
};
@ -47,12 +61,104 @@ ProfileActivity::ProfileActivity(std::string username) :
AddComponent(closeButton);
loading = true;
RequestBroker::Ref().Start(Client::Ref().GetUserInfoAsync(username), this);
}
void ProfileActivity::setUserInfo(UserInfo newInfo)
{
info = newInfo;
if(!info.Biography.length() && !editable)
info.Biography = "\bg(no bio)";
if(!info.Location.length() && !editable)
info.Location = "\bg(no location)";
ui::AvatarButton * avatar = new ui::AvatarButton(ui::Point((Size.X-40)-8, 8), ui::Point(40, 40), info.Username);
AddComponent(avatar);
int currentY = 5;
ui::Label * title = new ui::Label(ui::Point(4, currentY), ui::Point(Size.X-8-(40+8), 15), info.Username);
title->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
AddComponent(title);
currentY += 20;
ui::Label * locationTitle = new ui::Label(ui::Point(4, currentY), ui::Point(Size.X-8-(40+8), 15), "Location");
locationTitle->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
AddComponent(locationTitle);
currentY += 17;
if(editable)
{
ui::Textbox * location = new ui::Textbox(ui::Point(8, currentY), ui::Point(Size.X-16-(40+8), 17), info.Location);
location->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
AddComponent(location);
this->location = location;
currentY += 10+location->Size.Y;
}
else
{
ui::Label * location = new ui::Label(ui::Point(4, currentY), ui::Point(Size.X-8-(40+8), 12), info.Location);
location->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
location->SetTextColour(ui::Colour(180, 180, 180));
AddComponent(location);
this->location = location;
currentY += 10+location->Size.Y;
}
ui::Label * bioTitle = new ui::Label(ui::Point(4, currentY), ui::Point(Size.X-8, 15), "Biography");
bioTitle->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
AddComponent(bioTitle);
currentY += 17;
if(editable)
{
ui::Textbox * bio = new ui::Textbox(ui::Point(8, currentY), ui::Point(Size.X-16, Size.Y-30-currentY), info.Biography);
bio->SetMultiline(true);
bio->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
bio->Appearance.VerticalAlign = ui::Appearance::AlignTop;
AddComponent(bio);
currentY += 10+bio->Size.Y;
this->bio = bio;
}
else
{
ui::Label * bio = new ui::Label(ui::Point(4, currentY), ui::Point(Size.X-8, -1), info.Biography);
bio->SetMultiline(true);
bio->SetTextColour(ui::Colour(180, 180, 180));
bio->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
currentY += 10+bio->Size.Y;
if(currentY > Size.Y - 20)
{
ui::ScrollPanel * scrollPanel = new ui::ScrollPanel(bio->Position, ui::Point(Size.X, Size.Y-30-bio->Position.Y));
AddComponent(scrollPanel);
bio->Position = ui::Point(4, 4);
scrollPanel->AddChild(bio);
scrollPanel->InnerSize = ui::Point(Size.X, bio->Size.Y+8);
}
else
{
AddComponent(bio);
}
this->bio = bio;
}
//exit(0);
}
void ProfileActivity::OnResponseReady(void * userDataPtr)
{
exit(0);
if(loading)
{
loading = false;
setUserInfo(*(UserInfo*)userDataPtr);
delete (UserInfo*)userDataPtr;
}
else if(saving)
{
Exit();
}
}
void ProfileActivity::OnDraw()

View File

@ -4,9 +4,17 @@
#include <string>
#include "Activity.h"
#include "client/requestbroker/RequestListener.h"
#include "client/UserInfo.h"
#include "interface/Window.h"
class ProfileActivity: public WindowActivity, public RequestListener {
ui::Component * location;
ui::Component * bio;
UserInfo info;
bool editable;
bool loading;
bool saving;
void setUserInfo(UserInfo newInfo);
public:
ProfileActivity(std::string username);
virtual ~ProfileActivity();