httpObject.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "app/net/httpObject.h"
  23. #include "platform/platform.h"
  24. #include "core/stream/fileStream.h"
  25. #include "console/simBase.h"
  26. #include "console/consoleInternal.h"
  27. #include "console/engineAPI.h"
  28. IMPLEMENT_CONOBJECT(HTTPObject);
  29. ConsoleDocClass( HTTPObject,
  30. "@brief Allows communications between the game and a server using HTTP.\n\n"
  31. "HTTPObject is derrived from TCPObject and makes use of the same callbacks for dealing with "
  32. "connections and received data. However, the way in which you use HTTPObject to connect "
  33. "with a server is different than TCPObject. Rather than opening a connection, sending data, "
  34. "waiting to receive data, and then closing the connection, you issue a get() or post() and "
  35. "handle the response. The connection is automatically created and destroyed for you.\n\n"
  36. "@tsexample\n"
  37. "// In this example we'll retrieve the weather in Las Vegas using\n"
  38. "// Google's API. The response is in XML which could be processed\n"
  39. "// and used by the game using SimXMLDocument, but we'll just output\n"
  40. "// the results to the console in this example.\n\n"
  41. "// Define callbacks for our specific HTTPObject using our instance's\n"
  42. "// name (WeatherFeed) as the namespace.\n\n"
  43. "// Handle an issue with resolving the server's name\n"
  44. "function WeatherFeed::onDNSFailed(%this)\n"
  45. "{\n"
  46. " // Store this state\n"
  47. " %this.lastState = \"DNSFailed\";\n\n"
  48. " // Handle DNS failure\n"
  49. "}\n\n"
  50. "function WeatherFeed::onConnectFailed(%this)\n"
  51. "{\n"
  52. " // Store this state\n"
  53. " %this.lastState = \"ConnectFailed\";\n\n"
  54. " // Handle connection failure\n"
  55. "}\n\n"
  56. "function WeatherFeed::onDNSResolved(%this)\n"
  57. "{\n"
  58. " // Store this state\n"
  59. " %this.lastState = \"DNSResolved\";\n\n"
  60. "}\n\n"
  61. "function WeatherFeed::onConnected(%this)\n"
  62. "{\n"
  63. " // Store this state\n"
  64. " %this.lastState = \"Connected\";\n\n"
  65. " // Clear our buffer\n"
  66. " %this.buffer = \"\";\n"
  67. "}\n\n"
  68. "function WeatherFeed::onDisconnect(%this)\n"
  69. "{\n"
  70. " // Store this state\n"
  71. " %this.lastState = \"Disconnected\";\n\n"
  72. " // Output the buffer to the console\n"
  73. " echo(\"Google Weather Results:\");\n"
  74. " echo(%this.buffer);\n"
  75. "}\n\n"
  76. "// Handle a line from the server\n"
  77. "function WeatherFeed::onLine(%this, %line)\n"
  78. "{\n"
  79. " // Store this line in out buffer\n"
  80. " %this.buffer = %this.buffer @ %line;\n"
  81. "}\n\n"
  82. "// Create the HTTPObject\n"
  83. "%feed = new HTTPObject(WeatherFeed);\n\n"
  84. "// Define a dynamic field to store the last connection state\n"
  85. "%feed.lastState = \"None\";\n\n"
  86. "// Send the GET command\n"
  87. "%feed.get(\"www.google.com:80\", \"/ig/api\", \"weather=Las-Vegas,US\");\n"
  88. "@endtsexample\n\n"
  89. "@see TCPObject\n"
  90. "@ingroup Networking\n"
  91. );
  92. //--------------------------------------
  93. HTTPObject::HTTPObject()
  94. : mParseState(ParsingStatusLine),
  95. mTotalBytes(0),
  96. mBytesRemaining(0),
  97. mStatus(0),
  98. mVersion(0.0f),
  99. mContentLength(0),
  100. mChunkedEncoding(false),
  101. mChunkSize(0),
  102. mContentType(""),
  103. mHostName(NULL),
  104. mPath(NULL),
  105. mQuery(NULL),
  106. mPost(NULL),
  107. mBufferSave(NULL),
  108. mBufferSaveSize(0)
  109. {
  110. }
  111. HTTPObject::~HTTPObject()
  112. {
  113. dFree(mHostName);
  114. dFree(mPath);
  115. dFree(mQuery);
  116. dFree(mPost);
  117. mHostName = 0;
  118. mPath = 0;
  119. mQuery = 0;
  120. mPost = 0;
  121. dFree(mBufferSave);
  122. }
  123. //--------------------------------------
  124. //--------------------------------------
  125. void HTTPObject::get(const char *host, const char *path, const char *query)
  126. {
  127. if(mHostName)
  128. dFree(mHostName);
  129. if(mPath)
  130. dFree(mPath);
  131. if(mQuery)
  132. dFree(mQuery);
  133. if(mPost)
  134. dFree(mPost);
  135. if(mBufferSave)
  136. dFree(mBufferSave);
  137. mBufferSave = 0;
  138. mHostName = dStrdup(host);
  139. mPath = dStrdup(path);
  140. if(query)
  141. mQuery = dStrdup(query);
  142. else
  143. mQuery = NULL;
  144. mPost = NULL;
  145. connect(host);
  146. }
  147. void HTTPObject::post(const char *host, const char *path, const char *query, const char *post)
  148. {
  149. if(mHostName)
  150. dFree(mHostName);
  151. if(mPath)
  152. dFree(mPath);
  153. if(mQuery)
  154. dFree(mQuery);
  155. if(mPost)
  156. dFree(mPost);
  157. if(mBufferSave)
  158. dFree(mBufferSave);
  159. mBufferSave = 0;
  160. mHostName = dStrdup(host);
  161. mPath = dStrdup(path);
  162. if(query && query[0])
  163. mQuery = dStrdup(query);
  164. else
  165. mQuery = NULL;
  166. mPost = dStrdup(post);
  167. connect(host);
  168. }
  169. static char getHex(char c)
  170. {
  171. if(c <= 9)
  172. return c + '0';
  173. return c - 10 + 'A';
  174. }
  175. static S32 getHexVal(char c)
  176. {
  177. if(c >= '0' && c <= '9')
  178. return c - '0';
  179. else if(c >= 'A' && c <= 'Z')
  180. return c - 'A' + 10;
  181. else if(c >= 'a' && c <= 'z')
  182. return c - 'a' + 10;
  183. return -1;
  184. }
  185. void HTTPObject::expandPath(char *dest, const char *path, U32 destSize)
  186. {
  187. static bool asciiEscapeTableBuilt = false;
  188. static bool asciiEscapeTable[256];
  189. if(!asciiEscapeTableBuilt)
  190. {
  191. asciiEscapeTableBuilt = true;
  192. U32 i;
  193. for(i = 0; i <= ' '; i++)
  194. asciiEscapeTable[i] = true;
  195. for(;i <= 0x7F; i++)
  196. asciiEscapeTable[i] = false;
  197. for(;i <= 0xFF; i++)
  198. asciiEscapeTable[i] = true;
  199. asciiEscapeTable[static_cast<U32>('\"')] = true;
  200. asciiEscapeTable[static_cast<U32>('_')] = true;
  201. asciiEscapeTable[static_cast<U32>('\'')] = true;
  202. asciiEscapeTable[static_cast<U32>('#')] = true;
  203. asciiEscapeTable[static_cast<U32>('$')] = true;
  204. asciiEscapeTable[static_cast<U32>('%')] = true;
  205. asciiEscapeTable[static_cast<U32>('&')] = false;
  206. asciiEscapeTable[static_cast<U32>('+')] = true;
  207. asciiEscapeTable[static_cast<U32>('-')] = true;
  208. asciiEscapeTable[static_cast<U32>('~')] = true;
  209. }
  210. U32 destIndex = 0;
  211. U32 srcIndex = 0;
  212. while(path[srcIndex] && destIndex < destSize - 3)
  213. {
  214. char c = path[srcIndex++];
  215. if(asciiEscapeTable[static_cast<U32>(c)])
  216. {
  217. dest[destIndex++] = '%';
  218. dest[destIndex++] = getHex((c >> 4) & 0xF);
  219. dest[destIndex++] = getHex(c & 0xF);
  220. }
  221. else
  222. dest[destIndex++] = c;
  223. }
  224. dest[destIndex] = 0;
  225. }
  226. //--------------------------------------
  227. void HTTPObject::onConnected()
  228. {
  229. Parent::onConnected();
  230. char expPath[8192];
  231. char buffer[8192];
  232. if(mQuery)
  233. {
  234. dSprintf(buffer, sizeof(buffer), "%s?%s", mPath, mQuery);
  235. expandPath(expPath, buffer, sizeof(expPath));
  236. }
  237. else
  238. expandPath(expPath, mPath, sizeof(expPath));
  239. char *pt = dStrchr(mHostName, ':');
  240. if(pt)
  241. *pt = 0;
  242. //If we want to do a get request
  243. if(mPost == NULL)
  244. {
  245. dSprintf(buffer, sizeof(buffer), "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", expPath, mHostName);
  246. }
  247. //Else we want to do a post request
  248. else
  249. {
  250. dSprintf(buffer, sizeof(buffer), "POST %s HTTP/1.1\r\nHost: %s\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: %i\r\n\r\n%s\r\n\r\n",
  251. expPath, mHostName, dStrlen(mPost), mPost);
  252. }
  253. if(pt)
  254. *pt = ':';
  255. send((U8*)buffer, dStrlen(buffer));
  256. mParseState = ParsingStatusLine;
  257. mChunkedEncoding = false;
  258. }
  259. void HTTPObject::onConnectFailed()
  260. {
  261. dFree(mHostName);
  262. dFree(mPath);
  263. dFree(mQuery);
  264. mHostName = 0;
  265. mPath = 0;
  266. mQuery = 0;
  267. Parent::onConnectFailed();
  268. }
  269. void HTTPObject::onDisconnect()
  270. {
  271. dFree(mHostName);
  272. dFree(mPath);
  273. dFree(mQuery);
  274. mHostName = 0;
  275. mPath = 0;
  276. mQuery = 0;
  277. Parent::onDisconnect();
  278. }
  279. bool HTTPObject::processLine(UTF8 *line)
  280. {
  281. if(mParseState == ParsingStatusLine)
  282. {
  283. mParseState = ParsingHeader;
  284. }
  285. else if(mParseState == ParsingHeader)
  286. {
  287. if(!dStricmp((char *) line, "transfer-encoding: chunked"))
  288. mChunkedEncoding = true;
  289. if(line[0] == 0)
  290. {
  291. if(mChunkedEncoding)
  292. mParseState = ParsingChunkHeader;
  293. else
  294. mParseState = ProcessingBody;
  295. return true;
  296. }
  297. }
  298. else if(mParseState == ParsingChunkHeader)
  299. {
  300. if(line[0]) // strip off the crlf if necessary
  301. {
  302. mChunkSize = 0;
  303. S32 hexVal;
  304. while((hexVal = getHexVal(*line++)) != -1)
  305. {
  306. mChunkSize *= 16;
  307. mChunkSize += hexVal;
  308. }
  309. if(mBufferSave)
  310. {
  311. mBuffer = mBufferSave;
  312. mBufferSize = mBufferSaveSize;
  313. mBufferSave = 0;
  314. }
  315. if(mChunkSize)
  316. mParseState = ProcessingBody;
  317. else
  318. {
  319. mParseState = ProcessingDone;
  320. finishLastLine();
  321. }
  322. }
  323. }
  324. else
  325. {
  326. return Parent::processLine((UTF8*)line);
  327. }
  328. return true;
  329. }
  330. U32 HTTPObject::onDataReceive(U8 *buffer, U32 bufferLen)
  331. {
  332. U32 start = 0;
  333. parseLine(buffer, &start, bufferLen);
  334. return start;
  335. }
  336. //--------------------------------------
  337. U32 HTTPObject::onReceive(U8 *buffer, U32 bufferLen)
  338. {
  339. if(mParseState == ProcessingBody)
  340. {
  341. if(mChunkedEncoding && bufferLen >= mChunkSize)
  342. {
  343. U32 ret = onDataReceive(buffer, mChunkSize);
  344. mChunkSize -= ret;
  345. if(mChunkSize == 0)
  346. {
  347. if(mBuffer)
  348. {
  349. mBufferSaveSize = mBufferSize;
  350. mBufferSave = mBuffer;
  351. mBuffer = 0;
  352. mBufferSize = 0;
  353. }
  354. mParseState = ParsingChunkHeader;
  355. }
  356. return ret;
  357. }
  358. else
  359. {
  360. U32 ret = onDataReceive(buffer, bufferLen);
  361. mChunkSize -= ret;
  362. return ret;
  363. }
  364. }
  365. else if(mParseState != ProcessingDone)
  366. {
  367. U32 start = 0;
  368. parseLine(buffer, &start, bufferLen);
  369. return start;
  370. }
  371. return bufferLen;
  372. }
  373. //--------------------------------------
  374. DefineEngineMethod( HTTPObject, get, void, ( const char* Address, const char* requirstURI, const char* query ), ( "" ),
  375. "@brief Send a GET command to a server to send or retrieve data.\n\n"
  376. "@param Address HTTP web address to send this get call to. Be sure to include the port at the end (IE: \"www.garagegames.com:80\").\n"
  377. "@param requirstURI Specific location on the server to access (IE: \"index.php\".)\n"
  378. "@param query Optional. Actual data to transmit to the server. Can be anything required providing it sticks with limitations of the HTTP protocol. "
  379. "If you were building the URL manually, this is the text that follows the question mark. For example: http://www.google.com/ig/api?<b>weather=Las-Vegas,US</b>\n"
  380. "@tsexample\n"
  381. "// Create an HTTP object for communications\n"
  382. "%httpObj = new HTTPObject();\n\n"
  383. "// Specify a URL to transmit to\n"
  384. "%url = \"www.garagegames.com:80\";\n\n"
  385. "// Specify a URI to communicate with\n"
  386. "%URI = \"/index.php\";\n\n"
  387. "// Specify a query to send.\n"
  388. "%query = \"\";\n\n"
  389. "// Send the GET command to the server\n"
  390. "%httpObj.get(%url,%URI,%query);\n"
  391. "@endtsexample\n\n"
  392. )
  393. {
  394. if( !query || !query[ 0 ] )
  395. object->get(Address, requirstURI, NULL);
  396. else
  397. object->get(Address, requirstURI, query);
  398. }
  399. DefineEngineMethod( HTTPObject, post, void, ( const char* Address, const char* requirstURI, const char* query, const char* post ),,
  400. "@brief Send POST command to a server to send or retrieve data.\n\n"
  401. "@param Address HTTP web address to send this get call to. Be sure to include the port at the end (IE: \"www.garagegames.com:80\").\n"
  402. "@param requirstURI Specific location on the server to access (IE: \"index.php\".)\n"
  403. "@param query Actual data to transmit to the server. Can be anything required providing it sticks with limitations of the HTTP protocol. \n"
  404. "@param post Submission data to be processed.\n"
  405. "@tsexample\n"
  406. "// Create an HTTP object for communications\n"
  407. "%httpObj = new HTTPObject();\n\n"
  408. "// Specify a URL to transmit to\n"
  409. "%url = \"www.garagegames.com:80\";\n\n"
  410. "// Specify a URI to communicate with\n"
  411. "%URI = \"/index.php\";\n\n"
  412. "// Specify a query to send.\n"
  413. "%query = \"\";\n\n"
  414. "// Specify the submission data.\n"
  415. "%post = \"\";\n\n"
  416. "// Send the POST command to the server\n"
  417. "%httpObj.POST(%url,%URI,%query,%post);\n"
  418. "@endtsexample\n\n"
  419. )
  420. {
  421. object->post(Address, requirstURI, query, post);
  422. }