WebRequest.cpp 14 KB

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