ServiceRequestJob.h 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678
  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. #pragma once
  9. #include <AzCore/std/functional.h>
  10. #include <AzCore/std/string/regex.h>
  11. #include <AzCore/std/string/tokenize.h>
  12. #include <AzCore/JSON/document.h>
  13. #include <AzCore/JSON/prettywriter.h>
  14. #include <AzCore/Component/TickBus.h>
  15. #include <Framework/ServiceClientJob.h>
  16. #include <Framework/JsonObjectHandler.h>
  17. #include <Framework/JsonWriter.h>
  18. #include <Framework/Error.h>
  19. #include <Framework/ServiceRequestJobConfig.h>
  20. #include <Framework/RequestBuilder.h>
  21. #include <Framework/ServiceJobUtil.h>
  22. // The AWS Native SDK AWSAllocator triggers a warning due to accessing members of std::allocator directly.
  23. // AWSAllocator.h(70): warning C4996: 'std::allocator<T>::pointer': warning STL4010: Various members of std::allocator are deprecated in C++17.
  24. // Use std::allocator_traits instead of accessing these members directly.
  25. // You can define _SILENCE_CXX17_OLD_ALLOCATOR_MEMBERS_DEPRECATION_WARNING or _SILENCE_ALL_CXX17_DEPRECATION_WARNINGS to acknowledge that you have received this warning.
  26. AZ_PUSH_DISABLE_WARNING(4251 4996, "-Wunknown-warning-option")
  27. #include <aws/core/http/HttpResponse.h>
  28. #include <aws/core/auth/AWSAuthSigner.h>
  29. #include <aws/core/auth/AWSCredentialsProvider.h>
  30. AZ_POP_DISABLE_WARNING
  31. namespace AWSCore
  32. {
  33. const char logRequestsChannel[] = "ServiceRequest";
  34. /// Base class for service requests. To use, derive a class and
  35. /// then provide that class as the argument to the ServiceRequestJob
  36. /// template class.
  37. ///
  38. /// This class provide defaults, but many of these need to be
  39. /// overridden in the derived type for most requests, and ServiceTraits
  40. /// must be overridden for all requests. Use the SERVICE_REQUEST
  41. /// macro to implement the common overrides.
  42. class ServiceRequest
  43. {
  44. public:
  45. /// Macro used in derived classes to perform the common overrides.
  46. #define SERVICE_REQUEST(SERVICE_NAME, METHOD, PATH) \
  47. using ServiceTraits = SERVICE_NAME##ServiceTraits; \
  48. static const char* Path() { return PATH; } \
  49. static HttpMethod Method() { return METHOD; }
  50. /// ServiceTraits must be overridden by the derived type.
  51. using ServiceTraits = void;
  52. using HttpMethod = Aws::Http::HttpMethod;
  53. /// Must be overridden if the request method is not GET.
  54. static HttpMethod Method()
  55. {
  56. return HttpMethod::HTTP_GET;
  57. }
  58. /// Must be overridden if the request requires an URL path.
  59. /// By default the service url alone will be used.
  60. static const char* Path()
  61. {
  62. return ""; // no path
  63. }
  64. /// Type used for request parameters. If the request has
  65. /// parameters, define a parameters type and use it to
  66. /// override the parameters member.
  67. struct NoParameters
  68. {
  69. bool BuildRequest(AWSCore::RequestBuilder& request)
  70. {
  71. AZ_UNUSED(request);
  72. return true; // ok
  73. }
  74. };
  75. /// Stores parameter values. Must be overridden if the
  76. /// request has parameters.
  77. NoParameters parameters;
  78. /// Type used for result data. If the request has result data,
  79. /// define a result type and use it to override the result member.
  80. struct EmptyResult
  81. {
  82. bool OnJsonKey(const char* key, AWSCore::JsonReader& reader)
  83. {
  84. AZ_UNUSED(key);
  85. AZ_UNUSED(reader);
  86. return reader.Ignore();
  87. }
  88. };
  89. /// Stores result data. Must be overridden if the request has
  90. /// result data.
  91. EmptyResult result;
  92. /// Stores error information should the request fail. There is no
  93. /// need to override this member and doing so will waste a little
  94. /// memory.
  95. Error error;
  96. /// Determines if the AWS credentials, as supplied by the credentialsProvider from
  97. /// the ServiceRequestJobConfig object (which defaults to the user's credentials),
  98. /// are used to sign the request. The default is true. Override this and return false
  99. /// if calling a public API and want to avoid the overhead of signing requests.
  100. bool UseAWSCredentials() {
  101. return true;
  102. }
  103. };
  104. /// Base class for Cloud Gem service request jobs.
  105. template<class RequestType>
  106. class ServiceRequestJob
  107. : public ServiceClientJob<typename RequestType::ServiceTraits>
  108. , public RequestType
  109. {
  110. public:
  111. // To use a different allocator, extend this class and use this macro.
  112. AZ_CLASS_ALLOCATOR(ServiceRequestJob, AZ::SystemAllocator);
  113. /// Aliases for the configuration types used for this job class.
  114. using IConfig = IServiceRequestJobConfig;
  115. using Config = ServiceRequestJobConfig<RequestType>;
  116. /// An alias for this type, useful in derived classes.
  117. using ServiceRequestJobType = ServiceRequestJob<RequestType>;
  118. using ServiceClientJobType = ServiceClientJob<typename RequestType::ServiceTraits>;
  119. using OnSuccessFunction = AZStd::function<void(ServiceRequestJob* job)>;
  120. using OnFailureFunction = AZStd::function<void(ServiceRequestJob* job)>;
  121. class Function;
  122. template<class Allocator = AZ::SystemAllocator>
  123. static ServiceRequestJob* Create(OnSuccessFunction onSuccess, OnFailureFunction onFailure = OnFailureFunction{}, IConfig* config = GetDefaultConfig());
  124. static Config* GetDefaultConfig()
  125. {
  126. static AwsApiJobConfigHolder<Config> s_configHolder{};
  127. return s_configHolder.GetConfig(ServiceClientJobType::GetDefaultConfig());
  128. }
  129. ServiceRequestJob(bool isAutoDelete, IConfig* config = GetDefaultConfig())
  130. : ServiceClientJobType{ isAutoDelete, config }
  131. , m_requestUrl{ config->GetRequestUrl() }
  132. {
  133. if (RequestType::UseAWSCredentials())
  134. {
  135. if (!HasCredentials(config))
  136. {
  137. m_missingCredentials = true;
  138. return;
  139. }
  140. m_AWSAuthSigner.reset(
  141. new Aws::Client::AWSAuthV4Signer{
  142. config->GetCredentialsProvider(),
  143. "execute-api",
  144. DetermineRegionFromRequestUrl()
  145. }
  146. );
  147. }
  148. }
  149. bool HasCredentials(IConfig* config)
  150. {
  151. if (config == nullptr)
  152. {
  153. return false;
  154. }
  155. if (!config->GetCredentialsProvider())
  156. {
  157. return false;
  158. }
  159. auto awsCredentials = config->GetCredentialsProvider()->GetAWSCredentials();
  160. return !(awsCredentials.GetAWSAccessKeyId().empty() || awsCredentials.GetAWSSecretKey().empty());
  161. }
  162. /// Returns true if no error has occurred.
  163. bool WasSuccess()
  164. {
  165. return RequestType::error.type.empty();
  166. }
  167. /// Override AZ:Job defined method to reset request state when
  168. /// the job object is reused.
  169. void Reset(bool isClearDependent) override
  170. {
  171. RequestType::parameters = decltype(RequestType::parameters)();
  172. RequestType::result = decltype(RequestType::result)();
  173. RequestType::error = decltype(RequestType::error)();
  174. ServiceClientJobType::Reset(isClearDependent);
  175. }
  176. protected:
  177. /// Called to prepare the request. By default no changes
  178. /// are made to the parameters object. Override to defer the preparation
  179. /// of parameters until running on the job's worker thread,
  180. /// instead of setting parameters before calling Start.
  181. ///
  182. /// \return true if the request was prepared successfully.
  183. virtual bool PrepareRequest()
  184. {
  185. return IsValid();
  186. }
  187. virtual bool IsValid() const
  188. {
  189. return m_requestUrl.length() && !m_missingCredentials;
  190. }
  191. /// Called when a request completes without error.
  192. virtual void OnSuccess()
  193. {
  194. }
  195. /// Called when an error occurs.
  196. virtual void OnFailure()
  197. {
  198. }
  199. /// Provided so derived functions that do not auto delete can clean up
  200. virtual void DoCleanup()
  201. {
  202. }
  203. /// The URL created by appending the API path to the service URL.
  204. /// The path may contain {param} format parameters. The
  205. /// RequestType::parameters.BuildRequest method is responsible
  206. /// for replacing these parts of the url.
  207. const Aws::String& m_requestUrl;
  208. std::shared_ptr<Aws::Client::AWSAuthV4Signer> m_AWSAuthSigner{ nullptr };
  209. // Passed in configuration contains the AWS Credentials to use. If this request requires credentials
  210. // check in the constructor and set this bool to indicate if we're not valid before placing the credentials
  211. // in the m_AWSAuthSigner
  212. bool m_missingCredentials{ false };
  213. private:
  214. bool BuildRequest(RequestBuilder& request) override
  215. {
  216. bool ok = PrepareRequest();
  217. if (ok)
  218. {
  219. request.SetHttpMethod(RequestType::Method());
  220. request.SetRequestUrl(m_requestUrl);
  221. request.SetAWSAuthSigner(m_AWSAuthSigner);
  222. ok = RequestType::parameters.BuildRequest(request);
  223. if (!ok)
  224. {
  225. RequestType::error.type = Error::TYPE_CONTENT_ERROR;
  226. RequestType::error.message = request.GetErrorMessage();
  227. OnFailure();
  228. }
  229. }
  230. return ok;
  231. }
  232. AZStd::string EscapePercentCharsInString(const AZStd::string& str) const
  233. {
  234. return AZStd::regex_replace(str, AZStd::regex("%"), "%%");
  235. }
  236. void ProcessResponse(const std::shared_ptr<Aws::Http::HttpResponse>& response) override
  237. {
  238. if (ServiceClientJobType::IsCancelled())
  239. {
  240. RequestType::error.type = Error::TYPE_NETWORK_ERROR;
  241. RequestType::error.message = "Job canceled while waiting for a response.";
  242. }
  243. else if (!response)
  244. {
  245. RequestType::error.type = Error::TYPE_NETWORK_ERROR;
  246. RequestType::error.message = "An unknown error occurred while making the request.";
  247. }
  248. else
  249. {
  250. #ifdef _DEBUG
  251. // Code assumes application/json; charset=utf-8
  252. const Aws::String& contentType = response->GetContentType();
  253. AZ_Error(
  254. ServiceClientJobType::COMPONENT_DISPLAY_NAME,
  255. contentType.find("application/json") != AZStd::string::npos &&
  256. (contentType.find("charset") == AZStd::string::npos ||
  257. contentType.find("utf-8") != AZStd::string::npos),
  258. "Service response content type is not application/json; charset=utf-8: %s", contentType.c_str()
  259. );
  260. #endif
  261. int responseCode = static_cast<int>(response->GetResponseCode());
  262. Aws::IOStream& responseBody = response->GetResponseBody();
  263. #ifdef _DEBUG
  264. std::istreambuf_iterator<AZStd::string::value_type> eos;
  265. AZStd::string responseContent = AZStd::string{ std::istreambuf_iterator<AZStd::string::value_type>(responseBody),eos };
  266. AZ_Printf(ServiceClientJobType::COMPONENT_DISPLAY_NAME, "Processing %d response: %s.", responseCode, responseContent.c_str());
  267. responseBody.clear();
  268. responseBody.seekg(0);
  269. #endif
  270. static AZ::EnvironmentVariable<bool> envVar = AZ::Environment::FindVariable<bool>("AWSLogVerbosity");
  271. if (envVar && envVar.Get())
  272. {
  273. ShowRequestLog(response);
  274. }
  275. JsonInputStream stream{ responseBody };
  276. if (responseCode >= 200 && responseCode <= 299)
  277. {
  278. ReadResponseObject(stream);
  279. }
  280. else
  281. {
  282. ReadErrorObject(responseCode, stream);
  283. }
  284. }
  285. if (WasSuccess())
  286. {
  287. OnSuccess();
  288. }
  289. else
  290. {
  291. AZStd::string requestContent;
  292. AZStd::string responseContent;
  293. if (response)
  294. {
  295. // Get request and response content. Not attempting to do any charset
  296. // encoding/decoding, so the display may not be correct but it should
  297. // work fine for the usual ascii and utf8 content.
  298. std::istreambuf_iterator<AZStd::string::value_type> eos;
  299. std::shared_ptr<Aws::IOStream> requestStream = response->GetOriginatingRequest().GetContentBody();
  300. if (requestStream)
  301. {
  302. requestStream->clear();
  303. requestStream->seekg(0);
  304. requestContent = AZStd::string{ std::istreambuf_iterator<AZStd::string::value_type>(*requestStream.get()),eos };
  305. // Replace the character "%" with "%%" to prevent the error when printing the string that contains the percentage sign
  306. requestContent = EscapePercentCharsInString(requestContent);
  307. }
  308. Aws::IOStream& responseStream = response->GetResponseBody();
  309. responseStream.clear();
  310. responseStream.seekg(0);
  311. responseContent = AZStd::string{ std::istreambuf_iterator<AZStd::string::value_type>(responseStream),eos };
  312. }
  313. #if defined(AZ_ENABLE_TRACING)
  314. AZStd::string message = AZStd::string::format("An %s error occurred when performing %s %s on service %s using %s: %s\n\nRequest Content:\n%s\n\nResponse Content:\n%s\n\n",
  315. RequestType::error.type.c_str(),
  316. ServiceRequestJobType::HttpMethodToString(RequestType::Method()),
  317. RequestType::Path(),
  318. RequestType::ServiceTraits::ServiceName,
  319. response ? response->GetOriginatingRequest().GetURIString().c_str() : "NULL",
  320. RequestType::error.message.c_str(),
  321. requestContent.c_str(),
  322. responseContent.c_str()
  323. );
  324. // This is determined by AZ::g_maxMessageLength defined in in dev\Code\Framework\AzCore\AzCore\Debug\Trace.cpp.
  325. // It has the value 4096, but there is the timestamp, etc., to account for so we reduce it by a few characters.
  326. const int MAX_MESSAGE_LENGTH = 4096 - 128;
  327. // Replace the character "%" with "%%" to prevent the error when printing the string that contains the percentage sign
  328. message = EscapePercentCharsInString(message);
  329. if (message.size() > MAX_MESSAGE_LENGTH)
  330. {
  331. int offset = 0;
  332. while (offset < message.size())
  333. {
  334. int count = static_cast<int>((offset + MAX_MESSAGE_LENGTH < message.size()) ? MAX_MESSAGE_LENGTH : message.size() - offset);
  335. AZ_Warning(ServiceClientJobType::COMPONENT_DISPLAY_NAME, false, message.substr(offset, count).c_str());
  336. offset += MAX_MESSAGE_LENGTH;
  337. }
  338. }
  339. else
  340. {
  341. AZ_Warning(ServiceClientJobType::COMPONENT_DISPLAY_NAME, false, message.c_str());
  342. }
  343. #endif
  344. OnFailure();
  345. }
  346. }
  347. void ReadErrorObject(int responseCode, JsonInputStream& stream)
  348. {
  349. AZStd::string parseErrorMessage;
  350. bool ok = JsonReader::ReadObject(stream, RequestType::error, parseErrorMessage);
  351. ok = ok && !RequestType::error.message.empty();
  352. ok = ok && !RequestType::error.type.empty();
  353. if (!ok)
  354. {
  355. if (responseCode < 400)
  356. {
  357. // Not expected to make it here: 100 (info), 200 (success), or 300 (redirect).
  358. RequestType::error.type = Error::TYPE_CONTENT_ERROR;
  359. RequestType::error.message = AZStd::string::format("Unexpected response code %i received. %s", responseCode, stream.GetContent().c_str());
  360. }
  361. else if (responseCode < 500)
  362. {
  363. RequestType::error.type = Error::TYPE_CLIENT_ERROR;
  364. switch (responseCode)
  365. {
  366. case 401:
  367. case 403:
  368. RequestType::error.message = AZStd::string::format("Access denied (%i). %s", responseCode, stream.GetContent().c_str());
  369. break;
  370. case 404:
  371. RequestType::error.message = AZStd::string::format("Not found (%i). %s", responseCode, stream.GetContent().c_str());
  372. break;
  373. case 405:
  374. RequestType::error.message = AZStd::string::format("Method not allowed (%i). %s", responseCode, stream.GetContent().c_str());
  375. break;
  376. case 406:
  377. RequestType::error.message = AZStd::string::format("Content not acceptable (%i). %s", responseCode, stream.GetContent().c_str());
  378. break;
  379. default:
  380. RequestType::error.message = AZStd::string::format("Client error (%i). %s", responseCode, stream.GetContent().c_str());
  381. break;
  382. }
  383. }
  384. else if (responseCode < 600)
  385. {
  386. RequestType::error.type = Error::TYPE_SERVICE_ERROR;
  387. RequestType::error.message = AZStd::string::format("Service error (%i). %s", responseCode, stream.GetContent().c_str());
  388. }
  389. else
  390. {
  391. // Anything above 599 isn't valid HTTP.
  392. RequestType::error.type = Error::TYPE_CONTENT_ERROR;
  393. RequestType::error.message = AZStd::string::format("Unexpected response code %i received. %s", responseCode, stream.GetContent().c_str());
  394. }
  395. }
  396. }
  397. /// Parses a JSON object from a stream and writes the values found to a
  398. /// provided object. ResultObjectType should implement the following function:
  399. ///
  400. /// void OnJsonKey(const char* key, JsonObjectHandler& handler)
  401. ///
  402. /// This function will be called for each of the object's properties. It should
  403. /// call one of the Expect methods on the state object to identify the expected
  404. /// property type and provide a location where the property value can be stored.
  405. void ReadResponseObject(JsonInputStream& stream)
  406. {
  407. JsonKeyHandler objectKeyHandler = JsonReader::GetJsonKeyHandler(RequestType::result);
  408. JsonKeyHandler responseKeyHandler = GetResponseObjectKeyHandler(objectKeyHandler);
  409. bool ok = JsonReader::ReadObject(stream, responseKeyHandler, RequestType::error.message);
  410. if (!ok)
  411. {
  412. RequestType::error.type = Error::TYPE_CONTENT_ERROR;
  413. }
  414. }
  415. /// Creates the JsonKeyHandler function used by ReadResultObject to
  416. /// process the response body received from the service. The
  417. /// response content is determined by the response mappings
  418. /// used to configure API Gateway. The response is expected to be a
  419. /// JSON object with, at minimum, an "result" property.
  420. ///
  421. /// Can extend response properties in the swagger/OpenAPI spec and provide a handler for
  422. /// those properties by implementing GetResponseObjectKeyHandler. For
  423. /// example, it may be useful to return the API Gateway generated
  424. /// request id, which can help when trying to diagnose problems.
  425. JsonKeyHandler GetResponseObjectKeyHandler(JsonKeyHandler resultKeyHandler)
  426. {
  427. return [resultKeyHandler](const char* key, JsonReader& reader)
  428. {
  429. if (strcmp(key, "result") == 0) return reader.Accept(resultKeyHandler);
  430. return reader.Ignore();
  431. };
  432. }
  433. Aws::String DetermineRegionFromRequestUrl()
  434. {
  435. Aws::String region = DetermineRegionFromServiceUrl(m_requestUrl);
  436. if (region.empty())
  437. {
  438. AZ_Warning(
  439. ServiceClientJobType::COMPONENT_DISPLAY_NAME,
  440. false,
  441. "Service request url %s does not have the expected format. Cannot determine region from the url.",
  442. m_requestUrl.c_str()
  443. );
  444. region = "us-east-1";
  445. }
  446. return region;
  447. }
  448. static AZStd::string GetFormattedJSON(const AZStd::string& inputStr)
  449. {
  450. rapidjson::Document jsonRep;
  451. jsonRep.Parse(inputStr.c_str());
  452. if (jsonRep.HasParseError())
  453. {
  454. // If input couldn't be parsed, just return as is so it'll be printed
  455. return inputStr;
  456. }
  457. rapidjson::StringBuffer buffer;
  458. rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
  459. jsonRep.Accept(writer);
  460. return buffer.GetString();
  461. }
  462. // If request output is longer than allowed, let's just break it apart during printing
  463. void PrintRequestOutput(const AZStd::string& outputStr)
  464. {
  465. AZStd::size_t startPos = 0;
  466. while (startPos < outputStr.length())
  467. {
  468. AZStd::size_t endPos = outputStr.find_first_of('\n', startPos);
  469. if (endPos == AZStd::string::npos)
  470. {
  471. AZ_Printf(logRequestsChannel, outputStr.substr(startPos).c_str());
  472. break;
  473. }
  474. else
  475. {
  476. AZ_Printf(logRequestsChannel, outputStr.substr(startPos, endPos - startPos).c_str());
  477. startPos = endPos + 1;
  478. }
  479. }
  480. }
  481. void ShowRequestLog(const std::shared_ptr<Aws::Http::HttpResponse>& response)
  482. {
  483. if (!response)
  484. {
  485. return;
  486. }
  487. AZStd::string requestContent;
  488. std::shared_ptr<Aws::IOStream> requestStream = response->GetOriginatingRequest().GetContentBody();
  489. if (requestStream)
  490. {
  491. std::istreambuf_iterator<AZStd::string::value_type> eos;
  492. requestStream->clear();
  493. requestStream->seekg(0);
  494. requestContent = AZStd::string{ std::istreambuf_iterator<AZStd::string::value_type>(*requestStream.get()),eos };
  495. // Replace the character "%" with "%%" to prevent the error when printing the string that contains the percentage sign
  496. requestContent = EscapePercentCharsInString(requestContent);
  497. requestContent = GetFormattedJSON(requestContent);
  498. }
  499. std::istreambuf_iterator<AZStd::string::value_type> responseEos;
  500. Aws::IOStream& responseStream = response->GetResponseBody();
  501. responseStream.clear();
  502. responseStream.seekg(0);
  503. AZStd::string responseContent = AZStd::string{ std::istreambuf_iterator<AZStd::string::value_type>(responseStream), responseEos };
  504. responseContent = EscapePercentCharsInString(responseContent);
  505. responseStream.seekg(0);
  506. responseContent = GetFormattedJSON(responseContent);
  507. AZ_Printf(logRequestsChannel, "Service Request Complete");
  508. AZ_Printf(logRequestsChannel, "Service: %s URI : %s", RequestType::ServiceTraits::ServiceName, response ? response->GetOriginatingRequest().GetURIString().c_str() : "NULL");
  509. AZ_Printf(logRequestsChannel, "Request: %s %s", ServiceRequestJobType::HttpMethodToString(RequestType::Method()), RequestType::Path());
  510. PrintRequestOutput(requestContent);
  511. AZ_Printf(logRequestsChannel, "Got Response Code: %d", static_cast<int>(response->GetResponseCode()));
  512. AZ_Printf(logRequestsChannel, "Response Body:\n");
  513. PrintRequestOutput(responseContent);
  514. }
  515. };
  516. /// A derived class that calls lambda functions on job completion.
  517. template<class RequestTraits>
  518. class ServiceRequestJob<RequestTraits>::Function
  519. : public ServiceRequestJob
  520. {
  521. public:
  522. // To use a different allocator, extend this class and use this macro.
  523. AZ_CLASS_ALLOCATOR(Function, AZ::SystemAllocator);
  524. Function(OnSuccessFunction onSuccess, OnFailureFunction onFailure = OnFailureFunction{}, IConfig* config = GetDefaultConfig())
  525. : ServiceRequestJob(false, config) // No auto delete - The Function class will handle it with the DoCleanup() function
  526. , m_onSuccess{ onSuccess }
  527. , m_onFailure{ onFailure }
  528. {
  529. }
  530. private:
  531. void OnSuccess() override
  532. {
  533. AZStd::function<void()> callbackHandler = [this]()
  534. {
  535. if (m_onSuccess)
  536. {
  537. m_onSuccess(this);
  538. }
  539. delete this;
  540. };
  541. AZ::TickBus::QueueFunction(callbackHandler);
  542. }
  543. void OnFailure() override
  544. {
  545. AZStd::function<void()> callbackHandler = [this]()
  546. {
  547. if (m_onFailure)
  548. {
  549. m_onFailure(this);
  550. }
  551. delete this;
  552. };
  553. AZ::TickBus::QueueFunction(callbackHandler);
  554. }
  555. // Code doesn't use auto delete - this ensure things get cleaned up in cases when code can't call success or failure
  556. void DoCleanup() override
  557. {
  558. AZStd::function<void()> callbackHandler = [this]()
  559. {
  560. delete this;
  561. };
  562. AZ::TickBus::QueueFunction(callbackHandler);
  563. }
  564. OnSuccessFunction m_onSuccess;
  565. OnFailureFunction m_onFailure;
  566. };
  567. template<class RequestType>
  568. template<class Allocator>
  569. ServiceRequestJob<RequestType>* ServiceRequestJob<RequestType>::Create(
  570. OnSuccessFunction onSuccess, OnFailureFunction onFailure, IConfig* config)
  571. {
  572. return azcreate(Function, (onSuccess, onFailure, config), Allocator);
  573. }
  574. } // namespace AWSCore