WebRequest.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. //
  2. // Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
  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 deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // 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 FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. #include "../Precompiled.h"
  23. #include "../Core/Profiler.h"
  24. #include "../Container/HashMap.h"
  25. #include "../IO/BufferQueue.h"
  26. #include "../IO/Log.h"
  27. #include "../Web/Web.h"
  28. #include "../Web/WebEvents.h"
  29. #include "../Web/WebRequest.h"
  30. #ifdef EMSCRIPTEN
  31. #include "../DebugNew.h"
  32. // Add code to use an XMLHttpRequest or ActiveX XMLHttpRequest here.
  33. #else
  34. #include "../Web/WebInternalConfig.h"
  35. #include <asio.hpp>
  36. #include <functional>
  37. #include <curl/curl.h>
  38. #include "../DebugNew.h"
  39. namespace Atomic
  40. {
  41. struct WebRequestInternalState
  42. {
  43. /// The WebRequest external state
  44. WebRequest& es;
  45. /// The work queue.
  46. asio::io_service* service;
  47. /// URL.
  48. String url;
  49. /// Verb.
  50. String verb;
  51. /// Response headers.
  52. HashMap<StringHash, Pair<String, String>> responseHeaders;
  53. /// Upload stream.
  54. SharedPtr<BufferQueue> upload;
  55. /// Download stream.
  56. SharedPtr<BufferQueue> download;
  57. /// Request Headers.
  58. curl_slist* headers = NULL;
  59. /// Connection state.
  60. WebRequestState state;
  61. /// cURL multi handle.
  62. CURLM* curlm;
  63. /// cURL easy handle.
  64. CURL* curl;
  65. /// A flag to know if the request has contents (has data to upload).
  66. curl_off_t requestContentSize;
  67. /// A flag to know if the operation has been aborted.
  68. bool isAborted;
  69. /// A flag to know if the easy handle has been added to the Web class's multi handle.
  70. bool isAddedToMulti;
  71. /// Error string. Empty if no error.
  72. char error[CURL_ERROR_SIZE];
  73. /// String cache of response
  74. String response;
  75. WebRequestInternalState(WebRequest &es_) :
  76. es(es_)
  77. {
  78. }
  79. ~WebRequestInternalState()
  80. {
  81. }
  82. static int onProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
  83. {
  84. WebRequestInternalState *is_(reinterpret_cast<WebRequestInternalState*>(clientp));
  85. if (is_->isAborted)
  86. {
  87. // This should probably be CURL_XFERINFO_ABORT, but that doesn't
  88. // exist. It probably would be the same numeric value, if it did.
  89. // The docs say that it just has to be a nonzero to abort.
  90. return CURL_READFUNC_ABORT;
  91. }
  92. using namespace WebRequestProgress;
  93. VariantMap eventData;
  94. eventData[P_REQUEST] = &is_->es;
  95. eventData[P_DOWNLOADTOTAL] = (int) dltotal;
  96. eventData[P_DOWNLOADED] = (int) dlnow;
  97. eventData[P_UPLOADTOTAL] = (int) ultotal;
  98. eventData[P_UPLOADED] = (int) ulnow;
  99. is_->es.SendEvent(E_WEBREQUESTPROGRESS, eventData);
  100. return 0;
  101. }
  102. static size_t onHeader(char *ptr, size_t size, size_t nmemb, void *userdata)
  103. {
  104. WebRequestInternalState *is_(reinterpret_cast<WebRequestInternalState*>(userdata));
  105. if (is_->isAborted)
  106. {
  107. is_->state = HTTP_CLOSED;
  108. // This should probably be CURL_HEADERFUNC_ABORT, but that doesn't
  109. // exist. It probably would be the same numeric value, if it did.
  110. // The docs say that it just has to be a number of bytes that is
  111. // not "size * nmemb" to abort.
  112. return CURL_READFUNC_ABORT;
  113. }
  114. // Find the size in bytes.
  115. size_t real_size(size * nmemb);
  116. // Check for some known values.
  117. if (real_size == 2 && ptr[0] == '\r' && ptr[1] == '\n')
  118. {
  119. return real_size;
  120. }
  121. if (real_size > 5 && !strncmp(ptr, "HTTP/", 5))
  122. {
  123. return real_size;
  124. }
  125. // Get the header key and value, and add them to the map.
  126. unsigned int key_end = 0;
  127. unsigned int value_begin = 2;
  128. while (value_begin < real_size)
  129. {
  130. if (ptr[key_end] == ':' && ptr[key_end + 1] == ' ')
  131. {
  132. break;
  133. }
  134. ++key_end;
  135. ++value_begin;
  136. }
  137. if (value_begin == real_size)
  138. {
  139. String key(ptr, (unsigned int)real_size);
  140. is_->responseHeaders.InsertNew(key.ToUpper(), MakePair(key, String()));
  141. }
  142. else
  143. {
  144. String key(ptr, (unsigned int)key_end);
  145. is_->responseHeaders.InsertNew(key.ToUpper(), MakePair(key, String(ptr + value_begin, (unsigned int)real_size - value_begin - 2)));
  146. }
  147. return real_size;
  148. }
  149. static size_t onWrite(char *ptr, size_t size, size_t nmemb, void *userdata)
  150. {
  151. WebRequestInternalState *is_(reinterpret_cast<WebRequestInternalState*>(userdata));
  152. is_->state = HTTP_OPEN;
  153. if (is_->isAborted)
  154. {
  155. is_->state = HTTP_CLOSED;
  156. // This should probably be CURL_WRITEFUNC_ABORT, but that doesn't
  157. // exist. It probably would be the same numeric value, if it did.
  158. // The docs say that it just has to be a number of bytes that is
  159. // not "size * nmemb" to abort.
  160. return CURL_READFUNC_ABORT;
  161. }
  162. // Find the size in bytes.
  163. size_t real_size(size * nmemb);
  164. // Write the date into the download buffer queue.
  165. is_->download->Write(ptr, (unsigned int)real_size);
  166. using namespace WebRequestDownloadChunk;
  167. VariantMap eventData;
  168. eventData[P_REQUEST] = &is_->es;
  169. eventData[P_DOWNLOAD] = is_->download;
  170. eventData[P_CHUNKSIZE] = (unsigned) real_size;
  171. is_->es.SendEvent(E_WEBREQUESTDOWNLOADCHUNK, eventData);
  172. return real_size;
  173. }
  174. static size_t onRead(char *buffer, size_t size, size_t nitems, void *instream)
  175. {
  176. WebRequestInternalState *is_(reinterpret_cast<WebRequestInternalState*>(instream));
  177. is_->state = HTTP_OPEN;
  178. if (is_->isAborted)
  179. {
  180. is_->state = HTTP_CLOSED;
  181. return CURL_READFUNC_ABORT;
  182. }
  183. // Find the size in bytes.
  184. size_t real_size(size * nitems);
  185. // Read as much as we can from the upload buffer queue.
  186. size_t size_queued(is_->upload->GetSize());
  187. size_t size_left(real_size);
  188. if ((size_left > 0) && (size_queued > 0))
  189. {
  190. size_t read_size(std::min(size_queued, size_left));
  191. is_->upload->Read(buffer, (unsigned int)read_size);
  192. size_left -= read_size;
  193. }
  194. // If we still have bytes to fill, then emit a E_WEBREQUESTUPLOADCHUNK event.
  195. if (size_left > 0)
  196. {
  197. using namespace WebRequestUploadChunk;
  198. VariantMap eventData;
  199. eventData[P_REQUEST] = &is_->es;
  200. eventData[P_UPLOAD] = is_->upload;
  201. eventData[P_BYTESREMAINING] = (unsigned)size_left;
  202. is_->es.SendEvent(E_WEBREQUESTUPLOADCHUNK, eventData);
  203. }
  204. // Read as much as we can from the upload buffer queue (again).
  205. size_queued = is_->upload->GetSize();
  206. size_left = real_size;
  207. if ((size_left > 0) && (size_queued > 0))
  208. {
  209. size_t read_size(std::min(size_queued, size_left));
  210. is_->upload->Read(buffer, (unsigned int)read_size);
  211. size_left -= read_size;
  212. }
  213. // If we still have bytes to fill, then something went wrong, so we should abort.
  214. if (size_left > 0)
  215. {
  216. is_->isAborted = true;
  217. return CURL_READFUNC_ABORT;
  218. }
  219. return real_size;
  220. }
  221. void onEnd(int code)
  222. {
  223. using namespace WebRequestComplete;
  224. VariantMap eventData;
  225. eventData[P_REQUEST] = &es;
  226. if (code != CURLE_OK)
  227. {
  228. state = HTTP_ERROR;
  229. eventData[P_ERROR] = String(error, (unsigned int)strnlen(error, sizeof(error)));
  230. }
  231. else
  232. {
  233. state = HTTP_CLOSED;
  234. eventData[P_DOWNLOAD] = download;
  235. eventData[P_UPLOAD] = upload;
  236. }
  237. es.SendEvent(E_WEBREQUESTCOMPLETE, eventData);
  238. }
  239. };
  240. WebRequest::WebRequest(Context* context, const String& verb, const String& url, double requestContentSize) :
  241. Object(context),
  242. is_(new WebRequestInternalState(*this))
  243. {
  244. is_->url = url.Trimmed();
  245. is_->verb = verb;
  246. is_->upload = new BufferQueue(context);
  247. is_->download = new BufferQueue(context);
  248. is_->state = HTTP_INITIALIZING;
  249. is_->curlm = NULL;
  250. is_->curl = NULL;
  251. is_->requestContentSize = curl_off_t(std::floor(requestContentSize));
  252. is_->isAborted = false;
  253. is_->isAddedToMulti = false;
  254. Web* web = GetSubsystem<Web>();
  255. web->setup(*this);
  256. }
  257. WebRequest::~WebRequest()
  258. {
  259. curl_slist_free_all(is_->headers);
  260. if (is_->curlm == NULL)
  261. {
  262. return;
  263. }
  264. curl_easy_cleanup(is_->curl);
  265. delete is_;
  266. }
  267. void WebRequest::setup(asio::io_service *service, CURLM *curlm)
  268. {
  269. is_->service = service;
  270. is_->curlm = curlm;
  271. is_->curl = curl_easy_init();
  272. curl_easy_setopt(is_->curl, CURLOPT_ERRORBUFFER, is_->error);
  273. is_->error[0] = '\0';
  274. #if !(defined WIN32 || defined APPLE)
  275. // This line will eventually go away with a CA bundle in place, or other TLS options.
  276. curl_easy_setopt(is_->curl, CURLOPT_SSL_VERIFYPEER, 0L);
  277. #endif
  278. curl_easy_setopt(is_->curl, CURLOPT_URL, is_->url.CString());
  279. // All callbacks must look at is_->isAborted flag!
  280. curl_easy_setopt(is_->curl, CURLOPT_HEADERFUNCTION, &WebRequestInternalState::onHeader);
  281. curl_easy_setopt(is_->curl, CURLOPT_HEADERDATA, is_);
  282. curl_easy_setopt(is_->curl, CURLOPT_WRITEFUNCTION, &WebRequestInternalState::onWrite);
  283. curl_easy_setopt(is_->curl, CURLOPT_WRITEDATA, is_);
  284. curl_easy_setopt(is_->curl, CURLOPT_NOPROGRESS, 0L);
  285. curl_easy_setopt(is_->curl, CURLOPT_XFERINFOFUNCTION, &WebRequestInternalState::onProgress);
  286. curl_easy_setopt(is_->curl, CURLOPT_XFERINFODATA, is_);
  287. curl_easy_setopt(is_->curl, CURLOPT_CUSTOMREQUEST, is_->verb.CString());
  288. curl_easy_setopt(is_->curl, CURLOPT_PRIVATE, this);
  289. curl_easy_setopt(is_->curl, CURLOPT_READFUNCTION, &WebRequestInternalState::onRead);
  290. curl_easy_setopt(is_->curl, CURLOPT_READDATA, is_);
  291. if (is_->requestContentSize)
  292. {
  293. curl_easy_setopt(is_->curl, CURLOPT_UPLOAD, 1L);
  294. curl_easy_setopt(is_->curl, CURLOPT_INFILESIZE_LARGE, is_->requestContentSize);
  295. }
  296. }
  297. void WebRequest::internalNotify(WebRequest *wr, int code)
  298. {
  299. wr->is_->onEnd(code);
  300. if (wr->is_->isAddedToMulti)
  301. {
  302. curl_multi_remove_handle(wr->is_->curlm, wr->is_->curl);
  303. wr->is_->isAddedToMulti = false;
  304. // release the reference held from the Send method
  305. wr->ReleaseRef();
  306. }
  307. }
  308. void WebRequest::Abort()
  309. {
  310. is_->isAborted = true;
  311. }
  312. const String& WebRequest::GetURL() const
  313. {
  314. return is_->url;
  315. }
  316. String WebRequest::GetError() const
  317. {
  318. return String(is_->error);
  319. }
  320. WebRequestState WebRequest::GetState() const
  321. {
  322. return is_->state;
  323. }
  324. String WebRequest::GetVerb() const
  325. {
  326. return is_->verb;
  327. }
  328. bool WebRequest::HasDownloadChunkEvent()
  329. {
  330. return true; // cURL based implementations always support the "download_chunk" event.
  331. }
  332. void WebRequest::SetRequestHeader(const String& key, const String& value)
  333. {
  334. // Trim and only add non-empty header strings.
  335. String header;
  336. header += key.Trimmed();
  337. header += ": ";
  338. header += value;
  339. if (header.Length())
  340. {
  341. is_->headers = curl_slist_append(is_->headers, header.CString());
  342. }
  343. }
  344. void WebRequest::Send()
  345. {
  346. if (!is_->isAddedToMulti && !is_->isAborted)
  347. {
  348. // Add a reference to ourselves during the Send, this is released
  349. // in notifyInternal, if we're leaking WebRequests check that
  350. // this is being called
  351. AddRef();
  352. curl_easy_setopt(is_->curl, CURLOPT_HTTPHEADER, is_->headers);
  353. if (CURLM_OK != curl_multi_add_handle(is_->curlm, is_->curl))
  354. {
  355. ATOMIC_LOGERROR("WebRequest::Send() - ERROR SENDING REQUEST!");
  356. }
  357. is_->isAddedToMulti = true;
  358. }
  359. }
  360. StringVector WebRequest::GetResponseHeaderKeys()
  361. {
  362. StringVector keys;
  363. for (auto it(is_->responseHeaders.Begin()),
  364. itEnd(is_->responseHeaders.End()); it != itEnd; ++it)
  365. {
  366. keys.Push(it->second_.first_);
  367. }
  368. return keys;
  369. }
  370. String WebRequest::GetResponseHeader(const String& header)
  371. {
  372. auto it(is_->responseHeaders.Find(header.ToUpper()));
  373. if (it == is_->responseHeaders.End())
  374. {
  375. return "";
  376. }
  377. return it->second_.second_;
  378. }
  379. String WebRequest::GetAllResponseHeaders()
  380. {
  381. String allHeaders;
  382. for (auto it(is_->responseHeaders.Begin()),
  383. itEnd(is_->responseHeaders.End()); it != itEnd; ++it)
  384. {
  385. allHeaders += it->second_.first_;
  386. allHeaders += ": ";
  387. allHeaders += it->second_.second_;
  388. allHeaders += "\r\n";
  389. }
  390. return allHeaders;
  391. }
  392. void WebRequest::SetPostData(const String& postData)
  393. {
  394. // use the copy post fields option so we don't need to hold onto the string buffer
  395. curl_easy_setopt(is_->curl, CURLOPT_COPYPOSTFIELDS, postData.CString());
  396. }
  397. const String& WebRequest::GetResponse()
  398. {
  399. // use cached response if we have one
  400. if (is_->response.Length())
  401. return is_->response;
  402. unsigned size = is_->download->GetSize();
  403. if (!size)
  404. return String::EMPTY;
  405. SharedArrayPtr<unsigned char> response(new unsigned char[size + 1]);
  406. // ensure 0 terminated string
  407. response[size] = 0;
  408. is_->download->Read( (void *) &response[0], size);
  409. is_->response = (const char*) &response[0];
  410. return is_->response;
  411. }
  412. }
  413. #endif