HttpRequestJob.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <Framework/HttpRequestJob.h>
  9. // The AWS Native SDK AWSAllocator triggers a warning due to accessing members of std::allocator directly.
  10. // AWSAllocator.h(70): warning C4996: 'std::allocator<T>::pointer': warning STL4010: Various members of std::allocator are deprecated in C++17.
  11. // Use std::allocator_traits instead of accessing these members directly.
  12. // You can define _SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.
  13. #include <AzCore/PlatformDef.h>
  14. AZ_PUSH_DISABLE_WARNING(4251 4996, "-Wunknown-warning-option")
  15. #include <aws/core/http/HttpRequest.h>
  16. #include <aws/core/http/HttpResponse.h>
  17. #include <aws/core/http/HttpClient.h>
  18. #include <aws/core/http/HttpClientFactory.h>
  19. #include <aws/core/utils/stream/ResponseStream.h>
  20. #include <aws/core/utils/memory/stl/AWSStringStream.h>
  21. #include <aws/core/auth/AWSAuthSigner.h>
  22. AZ_POP_DISABLE_WARNING
  23. #include <AzCore/Component/TickBus.h>
  24. #include <AzCore/std/smart_ptr/make_shared.h>
  25. namespace AWSCore
  26. {
  27. namespace
  28. {
  29. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  30. // Mappings from HttpRequestJob nested types to Aws types
  31. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  32. struct HttpMethodInfo
  33. {
  34. Aws::Http::HttpMethod m_awsMethod;
  35. AZStd::string m_name;
  36. };
  37. // This will run the code fed to the macro, and then assign 0 to a static int (note the ,0 at the end)
  38. #define AWS_CORE_ONCE_PASTE(x) (x)
  39. #define AWS_CORE_ONCE(x) [[maybe_unused]] static int AZ_JOIN(init, __LINE__)((AWS_CORE_ONCE_PASTE(x), 0))
  40. #define AWS_CORE_HTTP_METHOD_ENTRY(x) { HttpRequestJob::HttpMethod::HTTP_##x, HttpMethodInfo{ Aws::Http::HttpMethod::HTTP_##x, #x } }
  41. using MethodLookup = AZStd::unordered_map<HttpRequestJob::HttpMethod, HttpMethodInfo>;
  42. AZ::EnvironmentVariable<MethodLookup> s_methodLookup = nullptr;
  43. const MethodLookup& GetMethodLookup()
  44. {
  45. AWS_CORE_ONCE(
  46. s_methodLookup = AZ::Environment::CreateVariable<MethodLookup>("methodlookup.httprequestjob.awscore", MethodLookup
  47. {
  48. AWS_CORE_HTTP_METHOD_ENTRY(GET),
  49. AWS_CORE_HTTP_METHOD_ENTRY(POST),
  50. AWS_CORE_HTTP_METHOD_ENTRY(DELETE),
  51. AWS_CORE_HTTP_METHOD_ENTRY(PUT),
  52. AWS_CORE_HTTP_METHOD_ENTRY(HEAD),
  53. AWS_CORE_HTTP_METHOD_ENTRY(PATCH),
  54. })
  55. );
  56. return *s_methodLookup;
  57. }
  58. #undef CLOUD_CANVAS_HTTP_METHOD_ENTRY
  59. #define AWS_CORE_HTTP_METHOD_AWS_ENTRY(x) { Aws::Http::HttpMethod::HTTP_##x, HttpRequestJob::HttpMethod::HTTP_##x }
  60. using MethodAwsReverseLookup = AZStd::unordered_map<Aws::Http::HttpMethod, HttpRequestJob::HttpMethod>;
  61. AZ::EnvironmentVariable<MethodAwsReverseLookup> s_methodAwsReverseLookup = nullptr;
  62. const MethodAwsReverseLookup& GetMethodAwsReverseLookup()
  63. {
  64. AWS_CORE_ONCE(s_methodAwsReverseLookup = AZ::Environment::CreateVariable<MethodAwsReverseLookup>("methodawsreverselookup.httprequestjob.awscore", MethodAwsReverseLookup
  65. {
  66. AWS_CORE_HTTP_METHOD_AWS_ENTRY(GET),
  67. AWS_CORE_HTTP_METHOD_AWS_ENTRY(POST),
  68. AWS_CORE_HTTP_METHOD_AWS_ENTRY(DELETE),
  69. AWS_CORE_HTTP_METHOD_AWS_ENTRY(PUT),
  70. AWS_CORE_HTTP_METHOD_AWS_ENTRY(HEAD),
  71. AWS_CORE_HTTP_METHOD_AWS_ENTRY(PATCH),
  72. })
  73. );
  74. return *s_methodAwsReverseLookup;
  75. }
  76. #undef AWS_CORE_HTTP_METHOD_AWS_ENTRY
  77. #define AWS_CORE_HTTP_METHOD_STRING_ENTRY(x) { #x, HttpRequestJob::HttpMethod::HTTP_##x }
  78. using MethodStringReverseLookup = AZStd::unordered_map<AZStd::string, HttpRequestJob::HttpMethod>;
  79. AZ::EnvironmentVariable<MethodStringReverseLookup> s_methodStringReverseLookup = nullptr;
  80. const MethodStringReverseLookup& GetMethodStringReverseLookup()
  81. {
  82. AWS_CORE_ONCE(s_methodStringReverseLookup = AZ::Environment::CreateVariable<MethodStringReverseLookup>("methodstringreverselookup.httprequestjob.awscore", MethodStringReverseLookup
  83. {
  84. AWS_CORE_HTTP_METHOD_STRING_ENTRY(GET),
  85. AWS_CORE_HTTP_METHOD_STRING_ENTRY(POST),
  86. AWS_CORE_HTTP_METHOD_STRING_ENTRY(DELETE),
  87. AWS_CORE_HTTP_METHOD_STRING_ENTRY(PUT),
  88. AWS_CORE_HTTP_METHOD_STRING_ENTRY(HEAD),
  89. AWS_CORE_HTTP_METHOD_STRING_ENTRY(PATCH),
  90. })
  91. );
  92. return *s_methodStringReverseLookup;
  93. }
  94. #undef AWS_CORE_HTTP_METHOD_STRING_ENTRY
  95. #define AWS_CORE_HEADER_FIELD_ENTRY(x) { HttpRequestJob::HeaderField::x, Aws::Http::x##_HEADER }
  96. using HeaderLookup = AZStd::unordered_map<HttpRequestJob::HeaderField, AZStd::string>;
  97. AZ::EnvironmentVariable<HeaderLookup> s_headerLookup = nullptr;
  98. const HeaderLookup& GetHeaderLookup()
  99. {
  100. AWS_CORE_ONCE(s_headerLookup = AZ::Environment::CreateVariable<HeaderLookup>("headerlookup.httprequestjob.awscore", HeaderLookup
  101. {
  102. AWS_CORE_HEADER_FIELD_ENTRY(DATE),
  103. AWS_CORE_HEADER_FIELD_ENTRY(AWS_DATE),
  104. { HttpRequestJob::HeaderField::AWS_SECURITY_TOKEN, Aws::Http::AWS_SECURITY_TOKEN },
  105. AWS_CORE_HEADER_FIELD_ENTRY(ACCEPT),
  106. AWS_CORE_HEADER_FIELD_ENTRY(ACCEPT_CHAR_SET),
  107. AWS_CORE_HEADER_FIELD_ENTRY(ACCEPT_ENCODING),
  108. AWS_CORE_HEADER_FIELD_ENTRY(AUTHORIZATION),
  109. AWS_CORE_HEADER_FIELD_ENTRY(AWS_AUTHORIZATION),
  110. AWS_CORE_HEADER_FIELD_ENTRY(COOKIE),
  111. AWS_CORE_HEADER_FIELD_ENTRY(CONTENT_LENGTH),
  112. AWS_CORE_HEADER_FIELD_ENTRY(CONTENT_TYPE),
  113. AWS_CORE_HEADER_FIELD_ENTRY(USER_AGENT),
  114. AWS_CORE_HEADER_FIELD_ENTRY(VIA),
  115. AWS_CORE_HEADER_FIELD_ENTRY(HOST),
  116. AWS_CORE_HEADER_FIELD_ENTRY(AMZ_TARGET),
  117. AWS_CORE_HEADER_FIELD_ENTRY(X_AMZ_EXPIRES),
  118. AWS_CORE_HEADER_FIELD_ENTRY(CONTENT_MD5),
  119. })
  120. );
  121. return *s_headerLookup;
  122. }
  123. #undef AWS_CORE_HEADER_FIELD_ENTRY
  124. #undef AWS_CORE_ONCE_PASTE
  125. #undef AWS_CORE_ONCE
  126. template<typename MapT>
  127. inline const typename MapT::mapped_type* FindInMap(const MapT& haystack, const typename MapT::key_type& needle)
  128. {
  129. const typename MapT::mapped_type* result = nullptr;
  130. auto itr = haystack.find(needle);
  131. if (itr != haystack.end())
  132. {
  133. result = &itr->second;
  134. }
  135. return result;
  136. }
  137. }
  138. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  139. // HttpRequestJob methods
  140. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  141. void HttpRequestJob::StaticInit()
  142. {
  143. GetMethodLookup();
  144. GetMethodAwsReverseLookup();
  145. GetMethodStringReverseLookup();
  146. GetHeaderLookup();
  147. }
  148. void HttpRequestJob::StaticShutdown()
  149. {
  150. s_methodLookup.Reset();
  151. s_methodAwsReverseLookup.Reset();
  152. s_methodStringReverseLookup.Reset();
  153. s_headerLookup.Reset();
  154. }
  155. void HttpRequestJob::SetUrl(AZStd::string url)
  156. {
  157. m_url = url;
  158. }
  159. const AZStd::string& HttpRequestJob::GetUrl() const
  160. {
  161. return m_url;
  162. }
  163. void HttpRequestJob::SetMethod(HttpMethod method)
  164. {
  165. m_method = method;
  166. }
  167. bool HttpRequestJob::SetMethod(const AZStd::string& method)
  168. {
  169. bool result = false;
  170. if (AZStd::optional<HttpMethod> value = StringToHttpMethod(method))
  171. {
  172. SetMethod(*value);
  173. result = true;
  174. }
  175. return result;
  176. }
  177. HttpRequestJob::HttpMethod HttpRequestJob::GetMethod() const
  178. {
  179. return m_method;
  180. }
  181. void HttpRequestJob::SetRequestHeader(AZStd::string key, AZStd::string value)
  182. {
  183. m_requestHeaders.emplace(AZStd::move(key), AZStd::move(value));
  184. }
  185. bool HttpRequestJob::GetRequestHeader(const AZStd::string& key, AZStd::string* result)
  186. {
  187. bool found = false;
  188. auto itr = m_requestHeaders.find(key);
  189. if (itr != m_requestHeaders.end())
  190. {
  191. found = true;
  192. if (result)
  193. {
  194. *result = itr->second;
  195. }
  196. }
  197. return found;
  198. }
  199. void HttpRequestJob::SetRequestHeader(HeaderField field, AZStd::string value)
  200. {
  201. if (auto headerString = FindInMap(GetHeaderLookup(), field))
  202. {
  203. SetRequestHeader(*headerString, value);
  204. }
  205. }
  206. bool HttpRequestJob::GetRequestHeader(HeaderField field, AZStd::string* result)
  207. {
  208. bool found = false;
  209. if (auto headerString = FindInMap(GetHeaderLookup(), field))
  210. {
  211. found = GetRequestHeader(*headerString, result);
  212. }
  213. return found;
  214. }
  215. HttpRequestJob::StringMap& HttpRequestJob::GetRequestHeaders()
  216. {
  217. return m_requestHeaders;
  218. }
  219. const HttpRequestJob::StringMap& HttpRequestJob::GetRequestHeaders() const
  220. {
  221. return m_requestHeaders;
  222. }
  223. void HttpRequestJob::SetAccept(AZStd::string accept)
  224. {
  225. SetRequestHeader(HeaderField::ACCEPT, accept);
  226. }
  227. void HttpRequestJob::SetAcceptCharSet(AZStd::string acceptCharSet)
  228. {
  229. SetRequestHeader(HeaderField::ACCEPT_CHAR_SET, acceptCharSet);
  230. }
  231. void HttpRequestJob::SetContentLength(AZStd::string contentLength)
  232. {
  233. SetRequestHeader(HeaderField::CONTENT_LENGTH, contentLength);
  234. }
  235. void HttpRequestJob::SetContentType(AZStd::string contentType)
  236. {
  237. SetRequestHeader(HeaderField::CONTENT_TYPE, contentType);
  238. }
  239. void HttpRequestJob::SetAWSAuthSigner(const std::shared_ptr<Aws::Client::AWSAuthSigner>& authSigner)
  240. {
  241. m_awsAuthSigner = authSigner;
  242. }
  243. const std::shared_ptr<Aws::Client::AWSAuthSigner>& HttpRequestJob::GetAWSAuthSigner() const
  244. {
  245. return m_awsAuthSigner;
  246. }
  247. void HttpRequestJob::SetBody(AZStd::string body)
  248. {
  249. m_requestBody = std::move(body);
  250. }
  251. const AZStd::string& HttpRequestJob::GetBody() const
  252. {
  253. return m_requestBody;
  254. }
  255. AZStd::string& HttpRequestJob::GetBody()
  256. {
  257. return m_requestBody;
  258. }
  259. const char* HttpRequestJob::HttpMethodToString(HttpMethod method)
  260. {
  261. const char* result = nullptr;
  262. if (auto methodInfo = FindInMap(GetMethodLookup(), method))
  263. {
  264. result = methodInfo->m_name.c_str();
  265. }
  266. return result;
  267. }
  268. const char* HttpRequestJob::HttpMethodToString(Aws::Http::HttpMethod method)
  269. {
  270. const char* result = nullptr;
  271. if (auto convertedMethod = FindInMap(GetMethodAwsReverseLookup(), method))
  272. {
  273. result = HttpMethodToString(*convertedMethod);
  274. }
  275. return result;
  276. }
  277. AZStd::optional<HttpRequestJob::HttpMethod> HttpRequestJob::StringToHttpMethod(const AZStd::string& method)
  278. {
  279. AZStd::optional<HttpRequestJob::HttpMethod> result;
  280. const auto& haystack = GetMethodStringReverseLookup();
  281. auto itr = haystack.find(method);
  282. if (itr != haystack.end())
  283. {
  284. result = itr->second;
  285. }
  286. return result;
  287. }
  288. void HttpRequestJob::Process()
  289. {
  290. // Someday the AWS Http client may support real async I/O. The
  291. // GetRequest and OnResponse methods are designed with that in
  292. ///mind. When that feature is available, we can use the AZ::Job
  293. // defined IncrementDependentCount method, start the async i/o,
  294. // and call WaitForChildren. When the i/o completes, we would call
  295. // DecrementDependentCount, which would cause WaitForChildren to
  296. // return. We would then call OnResponse.
  297. // Create the request
  298. std::shared_ptr<Aws::Http::HttpRequest> request = InitializeRequest();
  299. std::shared_ptr<Aws::Http::HttpResponse> httpResponse;
  300. if (request)
  301. {
  302. // Populate headers
  303. for (const auto& header : m_requestHeaders)
  304. {
  305. request->SetHeaderValue(Util::ToAwsString(header.first), Util::ToAwsString(header.second));
  306. }
  307. // Populate the body
  308. if (!m_requestBody.empty())
  309. {
  310. auto body = std::make_shared<Aws::StringStream>();
  311. body->write(m_requestBody.c_str(), m_requestBody.length());
  312. request->AddContentBody(body);
  313. }
  314. // Allow descendant classes to modify the request if desired
  315. this->CustomizeRequest(request);
  316. // Sign the request
  317. if (m_awsAuthSigner)
  318. {
  319. m_awsAuthSigner->SignRequest(*request);
  320. }
  321. httpResponse = m_httpClient->MakeRequest(request, m_readRateLimiter.get(), m_writeRateLimiter.get());
  322. }
  323. // Allow descendant classes to process the response
  324. this->ProcessResponse(httpResponse);
  325. // Configure and deliver our response
  326. auto callbackResponse = AZStd::make_shared<Response>();
  327. callbackResponse->m_response = httpResponse;
  328. bool failure = true;
  329. if (httpResponse)
  330. {
  331. Aws::IOStream& responseBody = httpResponse->GetResponseBody();
  332. std::istreambuf_iterator<AZStd::string::value_type> eos;
  333. callbackResponse->m_responseBody = AZStd::string{ std::istreambuf_iterator<AZStd::string::value_type>(responseBody), eos };
  334. callbackResponse->m_responseCode = static_cast<int>(httpResponse->GetResponseCode());
  335. if (callbackResponse->m_responseCode >= 200 && callbackResponse->m_responseCode <= 299)
  336. {
  337. failure = false;
  338. if (m_successCallback)
  339. {
  340. auto callback = AZStd::make_shared<SuccessFn>(AZStd::move(m_successCallback));
  341. auto fn = AZStd::function<void()>([callbackResponse, callback]() { (*callback)(callbackResponse); });
  342. AZ::TickBus::QueueFunction(fn);
  343. }
  344. }
  345. }
  346. if (failure && m_failureCallback)
  347. {
  348. auto callback = AZStd::make_shared<SuccessFn>(AZStd::move(m_failureCallback));
  349. auto fn = AZStd::function<void()>([callbackResponse, callback]() { (*callback)(callbackResponse); });
  350. AZ::TickBus::QueueFunction(fn);
  351. }
  352. }
  353. std::shared_ptr<Aws::Http::HttpRequest> HttpRequestJob::InitializeRequest()
  354. {
  355. std::shared_ptr<Aws::Http::HttpRequest> result;
  356. auto methodInfo = FindInMap(GetMethodLookup(), m_method);
  357. if (!m_url.empty() && methodInfo)
  358. {
  359. result = Aws::Http::CreateHttpRequest(
  360. Util::ToAwsString(m_url),
  361. methodInfo->m_awsMethod,
  362. &Aws::Utils::Stream::DefaultResponseStreamFactoryMethod
  363. );
  364. }
  365. return result;
  366. }
  367. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  368. // HttpRequestJob::Response methods
  369. ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  370. const AZStd::string& HttpRequestJob::Response::GetResponseBody() const
  371. {
  372. return m_responseBody;
  373. }
  374. int HttpRequestJob::Response::GetResponseCode() const
  375. {
  376. return m_responseCode;
  377. }
  378. const std::shared_ptr<Aws::Http::HttpResponse>& HttpRequestJob::Response::GetUnderlyingResponse() const
  379. {
  380. return m_response;
  381. }
  382. } // namespace AWSCore