HttpConnection.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. /**
  2. * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
  3. * SPDX-License-Identifier: Apache-2.0.
  4. */
  5. #include <aws/crt/Api.h>
  6. #include <aws/crt/http/HttpConnection.h>
  7. #include <aws/crt/http/HttpProxyStrategy.h>
  8. #include <aws/crt/http/HttpRequestResponse.h>
  9. #include <aws/crt/io/Bootstrap.h>
  10. namespace Aws
  11. {
  12. namespace Crt
  13. {
  14. namespace Http
  15. {
  16. /* This exists to handle aws_http_connection's shutdown callback, which might fire after
  17. * HttpClientConnection has been destroyed. */
  18. struct ConnectionCallbackData
  19. {
  20. explicit ConnectionCallbackData(Allocator *allocator) : allocator(allocator) {}
  21. std::weak_ptr<HttpClientConnection> connection;
  22. Allocator *allocator;
  23. OnConnectionSetup onConnectionSetup;
  24. OnConnectionShutdown onConnectionShutdown;
  25. };
  26. class UnmanagedConnection final : public HttpClientConnection
  27. {
  28. public:
  29. UnmanagedConnection(aws_http_connection *connection, Aws::Crt::Allocator *allocator)
  30. : HttpClientConnection(connection, allocator)
  31. {
  32. }
  33. ~UnmanagedConnection() override
  34. {
  35. if (m_connection)
  36. {
  37. aws_http_connection_release(m_connection);
  38. m_connection = nullptr;
  39. }
  40. }
  41. };
  42. void HttpClientConnection::s_onClientConnectionSetup(
  43. struct aws_http_connection *connection,
  44. int errorCode,
  45. void *user_data) noexcept
  46. {
  47. /**
  48. * Allocate an HttpClientConnection and seat it to `ConnectionCallbackData`'s shared_ptr.
  49. */
  50. auto *callbackData = static_cast<ConnectionCallbackData *>(user_data);
  51. if (!errorCode)
  52. {
  53. auto connectionObj = std::allocate_shared<UnmanagedConnection>(
  54. Aws::Crt::StlAllocator<UnmanagedConnection>(), connection, callbackData->allocator);
  55. if (connectionObj)
  56. {
  57. callbackData->connection = connectionObj;
  58. callbackData->onConnectionSetup(std::move(connectionObj), errorCode);
  59. return;
  60. }
  61. aws_http_connection_release(connection);
  62. errorCode = aws_last_error();
  63. }
  64. callbackData->onConnectionSetup(nullptr, errorCode);
  65. Delete(callbackData, callbackData->allocator);
  66. }
  67. void HttpClientConnection::s_onClientConnectionShutdown(
  68. struct aws_http_connection *connection,
  69. int errorCode,
  70. void *user_data) noexcept
  71. {
  72. (void)connection;
  73. auto *callbackData = static_cast<ConnectionCallbackData *>(user_data);
  74. /* Don't invoke callback if the connection object has expired. */
  75. if (auto connectionPtr = callbackData->connection.lock())
  76. {
  77. callbackData->onConnectionShutdown(*connectionPtr, errorCode);
  78. }
  79. Delete(callbackData, callbackData->allocator);
  80. }
  81. bool HttpClientConnection::CreateConnection(
  82. const HttpClientConnectionOptions &connectionOptions,
  83. Allocator *allocator) noexcept
  84. {
  85. AWS_FATAL_ASSERT(connectionOptions.OnConnectionSetupCallback);
  86. AWS_FATAL_ASSERT(connectionOptions.OnConnectionShutdownCallback);
  87. if (connectionOptions.TlsOptions && !(*connectionOptions.TlsOptions))
  88. {
  89. AWS_LOGF_ERROR(
  90. AWS_LS_HTTP_GENERAL,
  91. "Cannot create HttpClientConnection: connectionOptions contains invalid TlsOptions.");
  92. aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
  93. return false;
  94. }
  95. if (connectionOptions.ProxyOptions)
  96. {
  97. const auto &proxyOpts = connectionOptions.ProxyOptions.value();
  98. if (proxyOpts.TlsOptions && !(*proxyOpts.TlsOptions))
  99. {
  100. AWS_LOGF_ERROR(
  101. AWS_LS_HTTP_GENERAL,
  102. "Cannot create HttpClientConnection: connectionOptions has ProxyOptions that contain "
  103. "invalid TlsOptions.");
  104. aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
  105. return false;
  106. }
  107. }
  108. auto *callbackData = New<ConnectionCallbackData>(allocator, allocator);
  109. if (!callbackData)
  110. {
  111. return false;
  112. }
  113. callbackData->onConnectionShutdown = connectionOptions.OnConnectionShutdownCallback;
  114. callbackData->onConnectionSetup = connectionOptions.OnConnectionSetupCallback;
  115. aws_http_client_connection_options options;
  116. AWS_ZERO_STRUCT(options);
  117. options.self_size = sizeof(aws_http_client_connection_options);
  118. if (options.bootstrap != nullptr)
  119. {
  120. options.bootstrap = connectionOptions.Bootstrap->GetUnderlyingHandle();
  121. }
  122. else
  123. {
  124. options.bootstrap = ApiHandle::GetOrCreateStaticDefaultClientBootstrap()->GetUnderlyingHandle();
  125. }
  126. if (connectionOptions.TlsOptions)
  127. {
  128. /* This is verified earlier in this function. */
  129. AWS_FATAL_ASSERT(*connectionOptions.TlsOptions);
  130. options.tls_options =
  131. const_cast<aws_tls_connection_options *>(connectionOptions.TlsOptions->GetUnderlyingHandle());
  132. }
  133. options.allocator = allocator;
  134. options.user_data = callbackData;
  135. options.host_name = aws_byte_cursor_from_c_str(connectionOptions.HostName.c_str());
  136. options.port = connectionOptions.Port;
  137. options.initial_window_size = connectionOptions.InitialWindowSize;
  138. options.socket_options = &connectionOptions.SocketOptions.GetImpl();
  139. options.on_setup = HttpClientConnection::s_onClientConnectionSetup;
  140. options.on_shutdown = HttpClientConnection::s_onClientConnectionShutdown;
  141. options.manual_window_management = connectionOptions.ManualWindowManagement;
  142. aws_http_proxy_options proxyOptions;
  143. AWS_ZERO_STRUCT(proxyOptions);
  144. if (connectionOptions.ProxyOptions)
  145. {
  146. const auto &proxyOpts = connectionOptions.ProxyOptions.value();
  147. /* This is verified earlier in this function. */
  148. AWS_FATAL_ASSERT(!proxyOpts.TlsOptions || *proxyOpts.TlsOptions);
  149. proxyOpts.InitializeRawProxyOptions(proxyOptions);
  150. options.proxy_options = &proxyOptions;
  151. }
  152. if (aws_http_client_connect(&options))
  153. {
  154. Delete(callbackData, allocator);
  155. return false;
  156. }
  157. return true;
  158. }
  159. HttpClientConnection::HttpClientConnection(aws_http_connection *connection, Allocator *allocator) noexcept
  160. : m_connection(connection), m_allocator(allocator), m_lastError(AWS_ERROR_SUCCESS)
  161. {
  162. }
  163. std::shared_ptr<HttpClientStream> HttpClientConnection::NewClientStream(
  164. const HttpRequestOptions &requestOptions) noexcept
  165. {
  166. AWS_ASSERT(requestOptions.onIncomingHeaders);
  167. AWS_ASSERT(requestOptions.onStreamComplete);
  168. aws_http_make_request_options options;
  169. AWS_ZERO_STRUCT(options);
  170. options.self_size = sizeof(aws_http_make_request_options);
  171. options.request = requestOptions.request->GetUnderlyingMessage();
  172. options.on_response_body = HttpStream::s_onIncomingBody;
  173. options.on_response_headers = HttpStream::s_onIncomingHeaders;
  174. options.on_response_header_block_done = HttpStream::s_onIncomingHeaderBlockDone;
  175. options.on_complete = HttpStream::s_onStreamComplete;
  176. /* Do the same ref counting trick we did with HttpClientConnection. We need to maintain a reference
  177. * internally (regardless of what the user does), until the Stream shuts down. */
  178. auto *toSeat = static_cast<HttpClientStream *>(aws_mem_acquire(m_allocator, sizeof(HttpClientStream)));
  179. if (toSeat)
  180. {
  181. toSeat = new (toSeat) HttpClientStream(this->shared_from_this());
  182. Allocator *captureAllocator = m_allocator;
  183. std::shared_ptr<HttpClientStream> stream(
  184. toSeat,
  185. [captureAllocator](HttpStream *stream) { Delete(stream, captureAllocator); },
  186. StlAllocator<HttpClientStream>(captureAllocator));
  187. stream->m_onIncomingBody = requestOptions.onIncomingBody;
  188. stream->m_onIncomingHeaders = requestOptions.onIncomingHeaders;
  189. stream->m_onIncomingHeadersBlockDone = requestOptions.onIncomingHeadersBlockDone;
  190. stream->m_onStreamComplete = requestOptions.onStreamComplete;
  191. stream->m_callbackData.allocator = m_allocator;
  192. // we purposefully do not set m_callbackData::stream because we don't want the reference count
  193. // incremented until the request is kicked off via HttpClientStream::Activate(). Activate()
  194. // increments the ref count.
  195. options.user_data = &stream->m_callbackData;
  196. stream->m_stream = aws_http_connection_make_request(m_connection, &options);
  197. if (!stream->m_stream)
  198. {
  199. stream = nullptr;
  200. m_lastError = aws_last_error();
  201. return nullptr;
  202. }
  203. return stream;
  204. }
  205. m_lastError = aws_last_error();
  206. return nullptr;
  207. }
  208. bool HttpClientConnection::IsOpen() const noexcept { return aws_http_connection_is_open(m_connection); }
  209. void HttpClientConnection::Close() noexcept { aws_http_connection_close(m_connection); }
  210. HttpVersion HttpClientConnection::GetVersion() noexcept
  211. {
  212. return (HttpVersion)aws_http_connection_get_version(m_connection);
  213. }
  214. int HttpStream::s_onIncomingHeaders(
  215. struct aws_http_stream *,
  216. enum aws_http_header_block headerBlock,
  217. const struct aws_http_header *headerArray,
  218. size_t numHeaders,
  219. void *userData) noexcept
  220. {
  221. auto callbackData = static_cast<ClientStreamCallbackData *>(userData);
  222. callbackData->stream->m_onIncomingHeaders(*callbackData->stream, headerBlock, headerArray, numHeaders);
  223. return AWS_OP_SUCCESS;
  224. }
  225. int HttpStream::s_onIncomingHeaderBlockDone(
  226. struct aws_http_stream *,
  227. enum aws_http_header_block headerBlock,
  228. void *userData) noexcept
  229. {
  230. auto callbackData = static_cast<ClientStreamCallbackData *>(userData);
  231. if (callbackData->stream->m_onIncomingHeadersBlockDone)
  232. {
  233. callbackData->stream->m_onIncomingHeadersBlockDone(*callbackData->stream, headerBlock);
  234. }
  235. return AWS_OP_SUCCESS;
  236. }
  237. int HttpStream::s_onIncomingBody(
  238. struct aws_http_stream *,
  239. const struct aws_byte_cursor *data,
  240. void *userData) noexcept
  241. {
  242. auto callbackData = static_cast<ClientStreamCallbackData *>(userData);
  243. if (callbackData->stream->m_onIncomingBody)
  244. {
  245. callbackData->stream->m_onIncomingBody(*callbackData->stream, *data);
  246. }
  247. return AWS_OP_SUCCESS;
  248. }
  249. void HttpStream::s_onStreamComplete(struct aws_http_stream *, int errorCode, void *userData) noexcept
  250. {
  251. auto callbackData = static_cast<ClientStreamCallbackData *>(userData);
  252. callbackData->stream->m_onStreamComplete(*callbackData->stream, errorCode);
  253. callbackData->stream = nullptr;
  254. }
  255. HttpStream::HttpStream(const std::shared_ptr<HttpClientConnection> &connection) noexcept
  256. : m_stream(nullptr), m_connection(connection)
  257. {
  258. }
  259. HttpStream::~HttpStream()
  260. {
  261. if (m_stream)
  262. {
  263. aws_http_stream_release(m_stream);
  264. }
  265. if (m_connection)
  266. {
  267. m_connection = nullptr;
  268. }
  269. }
  270. HttpClientConnection &HttpStream::GetConnection() const noexcept { return *m_connection; }
  271. HttpClientStream::HttpClientStream(const std::shared_ptr<HttpClientConnection> &connection) noexcept
  272. : HttpStream(connection)
  273. {
  274. }
  275. HttpClientStream::~HttpClientStream() {}
  276. int HttpClientStream::GetResponseStatusCode() const noexcept
  277. {
  278. int status = 0;
  279. if (!aws_http_stream_get_incoming_response_status(m_stream, &status))
  280. {
  281. return status;
  282. }
  283. return -1;
  284. }
  285. bool HttpClientStream::Activate() noexcept
  286. {
  287. m_callbackData.stream = shared_from_this();
  288. if (aws_http_stream_activate(m_stream))
  289. {
  290. m_callbackData.stream = nullptr;
  291. return false;
  292. }
  293. return true;
  294. }
  295. void HttpStream::UpdateWindow(std::size_t incrementSize) noexcept
  296. {
  297. aws_http_stream_update_window(m_stream, incrementSize);
  298. }
  299. HttpClientConnectionProxyOptions::HttpClientConnectionProxyOptions()
  300. : HostName(), Port(0), TlsOptions(), ProxyConnectionType(AwsHttpProxyConnectionType::Legacy),
  301. ProxyStrategy(), AuthType(AwsHttpProxyAuthenticationType::None)
  302. {
  303. }
  304. void HttpClientConnectionProxyOptions::InitializeRawProxyOptions(
  305. struct aws_http_proxy_options &rawOptions) const
  306. {
  307. AWS_ZERO_STRUCT(rawOptions);
  308. rawOptions.connection_type = (enum aws_http_proxy_connection_type)ProxyConnectionType;
  309. rawOptions.host = aws_byte_cursor_from_c_str(HostName.c_str());
  310. rawOptions.port = Port;
  311. if (TlsOptions.has_value())
  312. {
  313. rawOptions.tls_options = TlsOptions->GetUnderlyingHandle();
  314. }
  315. if (ProxyStrategy)
  316. {
  317. rawOptions.proxy_strategy = ProxyStrategy->GetUnderlyingHandle();
  318. }
  319. if (AuthType == AwsHttpProxyAuthenticationType::Basic)
  320. {
  321. rawOptions.auth_type = AWS_HPAT_BASIC;
  322. rawOptions.auth_username = ByteCursorFromCString(BasicAuthUsername.c_str());
  323. rawOptions.auth_password = ByteCursorFromCString(BasicAuthPassword.c_str());
  324. }
  325. }
  326. HttpClientConnectionOptions::HttpClientConnectionOptions()
  327. : Bootstrap(nullptr), InitialWindowSize(SIZE_MAX), OnConnectionSetupCallback(),
  328. OnConnectionShutdownCallback(), HostName(), Port(0), SocketOptions(), TlsOptions(), ProxyOptions(),
  329. ManualWindowManagement(false)
  330. {
  331. }
  332. } // namespace Http
  333. } // namespace Crt
  334. } // namespace Aws