3
0

HttpRequestManager.cpp 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  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 <AzFramework/AzFramework_Traits_Platform.h>
  9. #include <AzCore/PlatformDef.h>
  10. // The AWS Native SDK AWSAllocator triggers a warning due to accessing members of std::allocator directly.
  11. // AWSAllocator.h(70): warning C4996: 'std::allocator<T>::pointer': warning STL4010: Various members of std::allocator are deprecated in C++17.
  12. // Use std::allocator_traits instead of accessing these members directly.
  13. // You can define _SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.
  14. AZ_PUSH_DISABLE_WARNING(4251 4996, "-Wunknown-warning-option")
  15. #include <aws/core/http/HttpClient.h>
  16. #include <aws/core/http/HttpClientFactory.h>
  17. #include <aws/core/http/HttpRequest.h>
  18. #include <aws/core/http/HttpResponse.h>
  19. #include <aws/core/client/ClientConfiguration.h>
  20. AZ_POP_DISABLE_WARNING
  21. #include <AWSNativeSDKInit/AWSNativeSDKInit.h>
  22. #include <AzCore/std/string/conversions.h>
  23. #include "HttpRequestManager.h"
  24. namespace HttpRequestor
  25. {
  26. const char* Manager::s_loggingName = "GemHttpRequestManager";
  27. Manager::Manager()
  28. {
  29. AZStd::thread_desc desc;
  30. desc.m_name = s_loggingName;
  31. desc.m_cpuId = AFFINITY_MASK_USERTHREADS;
  32. m_runThread = true;
  33. // Multiple different Gems might try to use the AWSNativeSDK, so make sure it only gets initialized / shutdown once
  34. // by the first Gem to try using it.
  35. m_ownsAwsNativeInitialization = !AWSNativeSDKInit::InitializationManager::IsInitialized();
  36. if (m_ownsAwsNativeInitialization)
  37. {
  38. AWSNativeSDKInit::InitializationManager::InitAwsApi();
  39. }
  40. auto function = [this]
  41. {
  42. ThreadFunction();
  43. };
  44. m_thread = AZStd::thread(desc, function);
  45. }
  46. Manager::~Manager()
  47. {
  48. m_runThread = false;
  49. m_requestConditionVar.notify_all();
  50. if (m_thread.joinable())
  51. {
  52. m_thread.join();
  53. }
  54. // Shutdown after background thread has closed.
  55. if (m_ownsAwsNativeInitialization)
  56. {
  57. AWSNativeSDKInit::InitializationManager::Shutdown();
  58. }
  59. }
  60. void Manager::AddRequest(Parameters&& httpRequestParameters)
  61. {
  62. {
  63. AZStd::lock_guard<AZStd::mutex> lock(m_requestMutex);
  64. m_requestsToHandle.push(AZStd::move(httpRequestParameters));
  65. }
  66. m_requestConditionVar.notify_all();
  67. }
  68. void Manager::AddTextRequest(TextParameters&& httpTextRequestParameters)
  69. {
  70. {
  71. AZStd::lock_guard<AZStd::mutex> lock(m_requestMutex);
  72. m_textRequestsToHandle.push(AZStd::move(httpTextRequestParameters));
  73. }
  74. m_requestConditionVar.notify_all();
  75. }
  76. AZStd::chrono::milliseconds Manager::GetLastRoundTripTime() const
  77. {
  78. return m_lastRoundTripTime.load(AZStd::memory_order_relaxed);
  79. }
  80. void Manager::ThreadFunction()
  81. {
  82. // Run the thread as long as directed
  83. while (m_runThread)
  84. {
  85. HandleRequestBatch();
  86. }
  87. }
  88. void Manager::HandleRequestBatch()
  89. {
  90. // Lock mutex and wait for work to be signaled via the condition variable
  91. AZStd::unique_lock<AZStd::mutex> lock(m_requestMutex);
  92. m_requestConditionVar.wait(
  93. lock,
  94. [&]
  95. {
  96. return !m_runThread || !m_requestsToHandle.empty() || !m_textRequestsToHandle.empty();
  97. });
  98. // Swap queues
  99. AZStd::queue<Parameters> requestsToHandle;
  100. requestsToHandle.swap(m_requestsToHandle);
  101. AZStd::queue<TextParameters> textRequestsToHandle;
  102. textRequestsToHandle.swap(m_textRequestsToHandle);
  103. // Release lock
  104. lock.unlock();
  105. // Handle requests
  106. while (!requestsToHandle.empty())
  107. {
  108. HandleRequest(requestsToHandle.front());
  109. requestsToHandle.pop();
  110. }
  111. while (!textRequestsToHandle.empty())
  112. {
  113. HandleTextRequest(textRequestsToHandle.front());
  114. textRequestsToHandle.pop();
  115. }
  116. }
  117. void Manager::HandleRequest(const Parameters& httpRequestParameters)
  118. {
  119. Aws::Client::ClientConfiguration config = httpRequestParameters.GetClientConfiguration();
  120. config.enableTcpKeepAlive = AZ_TRAIT_AZFRAMEWORK_AWS_ENABLE_TCP_KEEP_ALIVE_SUPPORTED;
  121. std::shared_ptr<Aws::Http::HttpClient> httpClient = Aws::Http::CreateHttpClient(config);
  122. auto httpRequest = Aws::Http::CreateHttpRequest(
  123. httpRequestParameters.GetURI(), httpRequestParameters.GetMethod(), Aws::Utils::Stream::DefaultResponseStreamFactoryMethod);
  124. AZ_Assert(httpRequest, "HttpRequest not created!");
  125. for (const auto& it : httpRequestParameters.GetHeaders())
  126. {
  127. httpRequest->SetHeaderValue(it.first.c_str(), it.second.c_str());
  128. }
  129. if (httpRequestParameters.GetBodyStream() != nullptr)
  130. {
  131. httpRequest->AddContentBody(httpRequestParameters.GetBodyStream());
  132. httpRequest->SetContentLength(AZStd::to_string(httpRequestParameters.GetBodyStream()->str().length()).c_str());
  133. }
  134. AZStd::chrono::steady_clock::time_point start = AZStd::chrono::steady_clock::now();
  135. const auto httpResponse = httpClient->MakeRequest(httpRequest);
  136. m_lastRoundTripTime.store(
  137. AZStd::chrono::duration_cast<AZStd::chrono::milliseconds>(AZStd::chrono::steady_clock::now() - start),
  138. AZStd::memory_order_relaxed);
  139. if (!httpResponse)
  140. {
  141. httpRequestParameters.GetCallback()(Aws::Utils::Json::JsonValue(), Aws::Http::HttpResponseCode::INTERNAL_SERVER_ERROR);
  142. return;
  143. }
  144. if (httpResponse->GetResponseCode() != Aws::Http::HttpResponseCode::OK)
  145. {
  146. httpRequestParameters.GetCallback()(Aws::Utils::Json::JsonValue(), httpResponse->GetResponseCode());
  147. return;
  148. }
  149. Aws::Utils::Json::JsonValue json(httpResponse->GetResponseBody());
  150. if (json.WasParseSuccessful())
  151. {
  152. httpRequestParameters.GetCallback()(AZStd::move(json), httpResponse->GetResponseCode());
  153. }
  154. else
  155. {
  156. httpRequestParameters.GetCallback()(Aws::Utils::Json::JsonValue(), Aws::Http::HttpResponseCode::INTERNAL_SERVER_ERROR);
  157. }
  158. }
  159. void Manager::HandleTextRequest(const TextParameters& httpRequestParameters)
  160. {
  161. Aws::Client::ClientConfiguration config = httpRequestParameters.GetClientConfiguration();
  162. config.enableTcpKeepAlive = AZ_TRAIT_AZFRAMEWORK_AWS_ENABLE_TCP_KEEP_ALIVE_SUPPORTED;
  163. std::shared_ptr<Aws::Http::HttpClient> httpClient = Aws::Http::CreateHttpClient(config);
  164. auto httpRequest = Aws::Http::CreateHttpRequest(
  165. httpRequestParameters.GetURI(), httpRequestParameters.GetMethod(), Aws::Utils::Stream::DefaultResponseStreamFactoryMethod);
  166. for (const auto& it : httpRequestParameters.GetHeaders())
  167. {
  168. httpRequest->SetHeaderValue(it.first.c_str(), it.second.c_str());
  169. }
  170. if (httpRequestParameters.GetBodyStream() != nullptr)
  171. {
  172. httpRequest->AddContentBody(httpRequestParameters.GetBodyStream());
  173. }
  174. AZStd::chrono::steady_clock::time_point start = AZStd::chrono::steady_clock::now();
  175. const auto httpResponse = httpClient->MakeRequest(httpRequest);
  176. m_lastRoundTripTime.store(
  177. AZStd::chrono::duration_cast<AZStd::chrono::milliseconds>(AZStd::chrono::steady_clock::now() - start),
  178. AZStd::memory_order_relaxed);
  179. if (!httpResponse)
  180. {
  181. httpRequestParameters.GetCallback()(AZStd::string(), Aws::Http::HttpResponseCode::INTERNAL_SERVER_ERROR);
  182. return;
  183. }
  184. if (httpResponse->GetResponseCode() != Aws::Http::HttpResponseCode::OK)
  185. {
  186. httpRequestParameters.GetCallback()(AZStd::string(), httpResponse->GetResponseCode());
  187. return;
  188. }
  189. // load up the raw output into a string
  190. // TODO(aaj): it feels like there should be some limit maybe 1 MB?
  191. std::istreambuf_iterator<char> eos;
  192. AZStd::string data(std::istreambuf_iterator<char>(httpResponse->GetResponseBody()), eos);
  193. httpRequestParameters.GetCallback()(AZStd::move(data), httpResponse->GetResponseCode());
  194. }
  195. } // namespace HttpRequestor