1/*
2 * Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License").
5 * You may not use this file except in compliance with the License.
6 * A copy of the License is located at
7 *
8 * http://aws.amazon.com/apache2.0
9 *
10 * or in the "license" file accompanying this file. This file is distributed
11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12 * express or implied. See the License for the specific language governing
13 * permissions and limitations under the License.
14 */
15
16#include <aws/core/http/curl/CurlHttpClient.h>
17#include <aws/core/http/HttpRequest.h>
18#include <aws/core/http/standard/StandardHttpResponse.h>
19#include <aws/core/utils/StringUtils.h>
20#include <aws/core/utils/logging/LogMacros.h>
21#include <aws/core/utils/ratelimiter/RateLimiterInterface.h>
22#include <aws/core/utils/DateTime.h>
23#include <aws/core/monitoring/HttpClientMetrics.h>
24#include <cassert>
25#include <algorithm>
26
27
28using namespace Aws::Client;
29using namespace Aws::Http;
30using namespace Aws::Http::Standard;
31using namespace Aws::Utils;
32using namespace Aws::Utils::Logging;
33using namespace Aws::Monitoring;
34
35#ifdef AWS_CUSTOM_MEMORY_MANAGEMENT
36
37static const char* MemTag = "libcurl";
38static size_t offset = sizeof(size_t);
39
40static void* malloc_callback(size_t size)
41{
42 char* newMem = reinterpret_cast<char*>(Aws::Malloc(MemTag, size + offset));
43 std::size_t* pointerToSize = reinterpret_cast<std::size_t*>(newMem);
44 *pointerToSize = size;
45 return reinterpret_cast<void*>(newMem + offset);
46}
47
48static void free_callback(void* ptr)
49{
50 if(ptr)
51 {
52 char* shiftedMemory = reinterpret_cast<char*>(ptr);
53 Aws::Free(shiftedMemory - offset);
54 }
55}
56
57static void* realloc_callback(void* ptr, size_t size)
58{
59 if(!ptr)
60 {
61 return malloc_callback(size);
62 }
63
64
65 if(!size && ptr)
66 {
67 free_callback(ptr);
68 return nullptr;
69 }
70
71 char* originalLenCharPtr = reinterpret_cast<char*>(ptr) - offset;
72 size_t originalLen = *reinterpret_cast<size_t*>(originalLenCharPtr);
73
74 char* rawMemory = reinterpret_cast<char*>(Aws::Malloc(MemTag, size + offset));
75 if(rawMemory)
76 {
77 std::size_t* pointerToSize = reinterpret_cast<std::size_t*>(rawMemory);
78 *pointerToSize = size;
79
80 size_t copyLength = (std::min)(originalLen, size);
81#ifdef _MSC_VER
82 memcpy_s(rawMemory + offset, size, ptr, copyLength);
83#else
84 memcpy(rawMemory + offset, ptr, copyLength);
85#endif
86 free_callback(ptr);
87 return reinterpret_cast<void*>(rawMemory + offset);
88 }
89 else
90 {
91 return ptr;
92 }
93
94}
95
96static void* calloc_callback(size_t nmemb, size_t size)
97{
98 size_t dataSize = nmemb * size;
99 char* newMem = reinterpret_cast<char*>(Aws::Malloc(MemTag, dataSize + offset));
100 std::size_t* pointerToSize = reinterpret_cast<std::size_t*>(newMem);
101 *pointerToSize = dataSize;
102#ifdef _MSC_VER
103 memset_s(newMem + offset, dataSize, 0, dataSize);
104#else
105 memset(newMem + offset, 0, dataSize);
106#endif
107
108 return reinterpret_cast<void*>(newMem + offset);
109}
110
111static char* strdup_callback(const char* str)
112{
113 size_t len = strlen(str) + 1;
114 size_t newLen = len + offset;
115 char* newMem = reinterpret_cast<char*>(Aws::Malloc(MemTag, newLen));
116
117 if(newMem)
118 {
119 std::size_t* pointerToSize = reinterpret_cast<std::size_t*>(newMem);
120 *pointerToSize = len;
121#ifdef _MSC_VER
122 memcpy_s(newMem + offset, len, str, len);
123#else
124 memcpy(newMem + offset, str, len);
125#endif
126 return newMem + offset;
127 }
128 return nullptr;
129}
130
131#endif
132
133struct CurlWriteCallbackContext
134{
135 CurlWriteCallbackContext(const CurlHttpClient* client,
136 HttpRequest* request,
137 HttpResponse* response,
138 Aws::Utils::RateLimits::RateLimiterInterface* rateLimiter) :
139 m_client(client),
140 m_request(request),
141 m_response(response),
142 m_rateLimiter(rateLimiter),
143 m_numBytesResponseReceived(0)
144 {}
145
146 const CurlHttpClient* m_client;
147 HttpRequest* m_request;
148 HttpResponse* m_response;
149 Aws::Utils::RateLimits::RateLimiterInterface* m_rateLimiter;
150 int64_t m_numBytesResponseReceived;
151};
152
153struct CurlReadCallbackContext
154{
155 CurlReadCallbackContext(const CurlHttpClient* client, HttpRequest* request, Aws::Utils::RateLimits::RateLimiterInterface* limiter) :
156 m_client(client),
157 m_rateLimiter(limiter),
158 m_request(request)
159 {}
160
161 const CurlHttpClient* m_client;
162 Aws::Utils::RateLimits::RateLimiterInterface* m_rateLimiter;
163 HttpRequest* m_request;
164};
165
166static const char* CURL_HTTP_CLIENT_TAG = "CurlHttpClient";
167
168static size_t WriteData(char* ptr, size_t size, size_t nmemb, void* userdata)
169{
170 if (ptr)
171 {
172 CurlWriteCallbackContext* context = reinterpret_cast<CurlWriteCallbackContext*>(userdata);
173
174 const CurlHttpClient* client = context->m_client;
175 if(!client->ContinueRequest(*context->m_request) || !client->IsRequestProcessingEnabled())
176 {
177 return 0;
178 }
179
180 HttpResponse* response = context->m_response;
181 size_t sizeToWrite = size * nmemb;
182 if (context->m_rateLimiter)
183 {
184 context->m_rateLimiter->ApplyAndPayForCost(static_cast<int64_t>(sizeToWrite));
185 }
186
187 response->GetResponseBody().write(ptr, static_cast<std::streamsize>(sizeToWrite));
188 auto& receivedHandler = context->m_request->GetDataReceivedEventHandler();
189 if (receivedHandler)
190 {
191 receivedHandler(context->m_request, context->m_response, static_cast<long long>(sizeToWrite));
192 }
193
194 AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, sizeToWrite << " bytes written to response.");
195 context->m_numBytesResponseReceived += sizeToWrite;
196 return sizeToWrite;
197 }
198 return 0;
199}
200
201static size_t WriteHeader(char* ptr, size_t size, size_t nmemb, void* userdata)
202{
203 if (ptr)
204 {
205 AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, ptr);
206 HttpResponse* response = (HttpResponse*) userdata;
207 Aws::String headerLine(ptr);
208 Aws::Vector<Aws::String> keyValuePair = StringUtils::Split(headerLine, ':', 2);
209
210 if (keyValuePair.size() == 2)
211 {
212 response->AddHeader(StringUtils::Trim(keyValuePair[0].c_str()), StringUtils::Trim(keyValuePair[1].c_str()));
213 }
214
215 return size * nmemb;
216 }
217 return 0;
218}
219
220
221static size_t ReadBody(char* ptr, size_t size, size_t nmemb, void* userdata)
222{
223 CurlReadCallbackContext* context = reinterpret_cast<CurlReadCallbackContext*>(userdata);
224 if(context == nullptr)
225 {
226 return 0;
227 }
228
229 const CurlHttpClient* client = context->m_client;
230 if(!client->ContinueRequest(*context->m_request) || !client->IsRequestProcessingEnabled())
231 {
232 return CURL_READFUNC_ABORT;
233 }
234
235 HttpRequest* request = context->m_request;
236 const std::shared_ptr<Aws::IOStream>& ioStream = request->GetContentBody();
237
238 const size_t amountToRead = size * nmemb;
239 if (ioStream != nullptr && amountToRead > 0)
240 {
241 ioStream->read(ptr, amountToRead);
242 size_t amountRead = static_cast<size_t>(ioStream->gcount());
243 auto& sentHandler = request->GetDataSentEventHandler();
244 if (sentHandler)
245 {
246 sentHandler(request, static_cast<long long>(amountRead));
247 }
248
249 if (context->m_rateLimiter)
250 {
251 context->m_rateLimiter->ApplyAndPayForCost(static_cast<int64_t>(amountRead));
252 }
253
254 return amountRead;
255 }
256
257 return 0;
258}
259
260static size_t SeekBody(void* userdata, curl_off_t offset, int origin)
261{
262 CurlReadCallbackContext* context = reinterpret_cast<CurlReadCallbackContext*>(userdata);
263 if(context == nullptr)
264 {
265 return CURL_SEEKFUNC_FAIL;
266 }
267
268 const CurlHttpClient* client = context->m_client;
269 if(!client->ContinueRequest(*context->m_request) || !client->IsRequestProcessingEnabled())
270 {
271 return CURL_SEEKFUNC_FAIL;
272 }
273
274 HttpRequest* request = context->m_request;
275 const std::shared_ptr<Aws::IOStream>& ioStream = request->GetContentBody();
276
277 std::ios_base::seekdir dir;
278 switch(origin)
279 {
280 case SEEK_SET:
281 dir = std::ios_base::beg;
282 break;
283 case SEEK_CUR:
284 dir = std::ios_base::cur;
285 break;
286 case SEEK_END:
287 dir = std::ios_base::end;
288 break;
289 default:
290 return CURL_SEEKFUNC_FAIL;
291 }
292
293 ioStream->clear();
294 ioStream->seekg(offset, dir);
295 if (ioStream->fail()) {
296 return CURL_SEEKFUNC_CANTSEEK;
297 }
298
299 return CURL_SEEKFUNC_OK;
300}
301
302void SetOptCodeForHttpMethod(CURL* requestHandle, const HttpRequest& request)
303{
304 switch (request.GetMethod())
305 {
306 case HttpMethod::HTTP_GET:
307 curl_easy_setopt(requestHandle, CURLOPT_HTTPGET, 1L);
308 break;
309 case HttpMethod::HTTP_POST:
310 if (request.HasHeader(Aws::Http::CONTENT_LENGTH_HEADER) && request.GetHeaderValue(Aws::Http::CONTENT_LENGTH_HEADER) == "0")
311 {
312 curl_easy_setopt(requestHandle, CURLOPT_CUSTOMREQUEST, "POST");
313 }
314 else
315 {
316 curl_easy_setopt(requestHandle, CURLOPT_POST, 1L);
317 }
318 break;
319 case HttpMethod::HTTP_PUT:
320 if ((!request.HasHeader(Aws::Http::CONTENT_LENGTH_HEADER) || request.GetHeaderValue(Aws::Http::CONTENT_LENGTH_HEADER) == "0") &&
321 !request.HasHeader(Aws::Http::TRANSFER_ENCODING_HEADER))
322 {
323 curl_easy_setopt(requestHandle, CURLOPT_CUSTOMREQUEST, "PUT");
324 }
325 else
326 {
327 curl_easy_setopt(requestHandle, CURLOPT_PUT, 1L);
328 }
329 break;
330 case HttpMethod::HTTP_HEAD:
331 curl_easy_setopt(requestHandle, CURLOPT_HTTPGET, 1L);
332 curl_easy_setopt(requestHandle, CURLOPT_NOBODY, 1L);
333 break;
334 case HttpMethod::HTTP_PATCH:
335 if ((!request.HasHeader(Aws::Http::CONTENT_LENGTH_HEADER)|| request.GetHeaderValue(Aws::Http::CONTENT_LENGTH_HEADER) == "0") &&
336 !request.HasHeader(Aws::Http::TRANSFER_ENCODING_HEADER))
337 {
338 curl_easy_setopt(requestHandle, CURLOPT_CUSTOMREQUEST, "PATCH");
339 }
340 else
341 {
342 curl_easy_setopt(requestHandle, CURLOPT_POST, 1L);
343 curl_easy_setopt(requestHandle, CURLOPT_CUSTOMREQUEST, "PATCH");
344 }
345
346 break;
347 case HttpMethod::HTTP_DELETE:
348 curl_easy_setopt(requestHandle, CURLOPT_CUSTOMREQUEST, "DELETE");
349 break;
350 default:
351 assert(0);
352 curl_easy_setopt(requestHandle, CURLOPT_CUSTOMREQUEST, "GET");
353 break;
354 }
355}
356
357
358std::atomic<bool> CurlHttpClient::isInit(false);
359
360void CurlHttpClient::InitGlobalState()
361{
362 if (!isInit)
363 {
364 auto curlVersionData = curl_version_info(CURLVERSION_NOW);
365 AWS_LOGSTREAM_INFO(CURL_HTTP_CLIENT_TAG, "Initializing Curl library with version: " << curlVersionData->version
366 << ", ssl version: " << curlVersionData->ssl_version);
367 isInit = true;
368#ifdef AWS_CUSTOM_MEMORY_MANAGEMENT
369 curl_global_init_mem(CURL_GLOBAL_ALL, &malloc_callback, &free_callback, &realloc_callback, &strdup_callback, &calloc_callback);
370#else
371 curl_global_init(CURL_GLOBAL_ALL);
372#endif
373 }
374}
375
376
377void CurlHttpClient::CleanupGlobalState()
378{
379 curl_global_cleanup();
380}
381
382Aws::String CurlInfoTypeToString(curl_infotype type)
383{
384 switch(type)
385 {
386 case CURLINFO_TEXT:
387 return "Text";
388
389 case CURLINFO_HEADER_IN:
390 return "HeaderIn";
391
392 case CURLINFO_HEADER_OUT:
393 return "HeaderOut";
394
395 case CURLINFO_DATA_IN:
396 return "DataIn";
397
398 case CURLINFO_DATA_OUT:
399 return "DataOut";
400
401 case CURLINFO_SSL_DATA_IN:
402 return "SSLDataIn";
403
404 case CURLINFO_SSL_DATA_OUT:
405 return "SSLDataOut";
406
407 default:
408 return "Unknown";
409 }
410}
411
412int CurlDebugCallback(CURL *handle, curl_infotype type, char *data, size_t size, void *userptr)
413{
414 AWS_UNREFERENCED_PARAM(handle);
415 AWS_UNREFERENCED_PARAM(userptr);
416
417 if(type == CURLINFO_SSL_DATA_IN || type == CURLINFO_SSL_DATA_OUT)
418 {
419 AWS_LOGSTREAM_DEBUG("CURL", "(" << CurlInfoTypeToString(type) << ") " << size << "bytes");
420 }
421 else
422 {
423 Aws::String debugString(data, size);
424 AWS_LOGSTREAM_DEBUG("CURL", "(" << CurlInfoTypeToString(type) << ") " << debugString);
425 }
426
427 return 0;
428}
429
430
431CurlHttpClient::CurlHttpClient(const ClientConfiguration& clientConfig) :
432 Base(),
433 m_curlHandleContainer(clientConfig.maxConnections, clientConfig.httpRequestTimeoutMs, clientConfig.connectTimeoutMs, clientConfig.enableTcpKeepAlive,
434 clientConfig.tcpKeepAliveIntervalMs, clientConfig.requestTimeoutMs, clientConfig.lowSpeedLimit),
435 m_isUsingProxy(!clientConfig.proxyHost.empty()), m_proxyUserName(clientConfig.proxyUserName),
436 m_proxyPassword(clientConfig.proxyPassword), m_proxyScheme(SchemeMapper::ToString(clientConfig.proxyScheme)), m_proxyHost(clientConfig.proxyHost),
437 m_proxySSLCertPath(clientConfig.proxySSLCertPath), m_proxySSLCertType(clientConfig.proxySSLCertType),
438 m_proxySSLKeyPath(clientConfig.proxySSLKeyPath), m_proxySSLKeyType(clientConfig.proxySSLKeyType),
439 m_proxyKeyPasswd(clientConfig.proxySSLKeyPassword),
440 m_proxyPort(clientConfig.proxyPort), m_verifySSL(clientConfig.verifySSL), m_caPath(clientConfig.caPath),
441 m_caFile(clientConfig.caFile),
442 m_disableExpectHeader(clientConfig.disableExpectHeader),
443 m_allowRedirects(clientConfig.followRedirects)
444{
445}
446
447
448void CurlHttpClient::MakeRequestInternal(HttpRequest& request,
449 std::shared_ptr<StandardHttpResponse>& response,
450 Aws::Utils::RateLimits::RateLimiterInterface* readLimiter,
451 Aws::Utils::RateLimits::RateLimiterInterface* writeLimiter) const
452{
453 URI uri = request.GetUri();
454 Aws::String url = uri.GetURIString();
455
456 AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, "Making request to " << url);
457 struct curl_slist* headers = NULL;
458
459 if (writeLimiter != nullptr)
460 {
461 writeLimiter->ApplyAndPayForCost(request.GetSize());
462 }
463
464 Aws::StringStream headerStream;
465 HeaderValueCollection requestHeaders = request.GetHeaders();
466
467 AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, "Including headers:");
468 for (auto& requestHeader : requestHeaders)
469 {
470 headerStream.str("");
471 headerStream << requestHeader.first << ": " << requestHeader.second;
472 Aws::String headerString = headerStream.str();
473 AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, headerString);
474 headers = curl_slist_append(headers, headerString.c_str());
475 }
476
477 if (!request.HasHeader(Http::TRANSFER_ENCODING_HEADER))
478 {
479 headers = curl_slist_append(headers, "transfer-encoding:");
480 }
481
482 if (!request.HasHeader(Http::CONTENT_LENGTH_HEADER))
483 {
484 headers = curl_slist_append(headers, "content-length:");
485 }
486
487 if (!request.HasHeader(Http::CONTENT_TYPE_HEADER))
488 {
489 headers = curl_slist_append(headers, "content-type:");
490 }
491
492 // Discard Expect header so as to avoid using multiple payloads to send a http request (header + body)
493 if (m_disableExpectHeader)
494 {
495 headers = curl_slist_append(headers, "Expect:");
496 }
497
498 CURL* connectionHandle = m_curlHandleContainer.AcquireCurlHandle();
499
500 if (connectionHandle)
501 {
502 AWS_LOGSTREAM_DEBUG(CURL_HTTP_CLIENT_TAG, "Obtained connection handle " << connectionHandle);
503
504 if (headers)
505 {
506 curl_easy_setopt(connectionHandle, CURLOPT_HTTPHEADER, headers);
507 }
508
509 CurlWriteCallbackContext writeContext(this, &request, response.get(), readLimiter);
510 CurlReadCallbackContext readContext(this, &request, writeLimiter);
511
512 SetOptCodeForHttpMethod(connectionHandle, request);
513
514 curl_easy_setopt(connectionHandle, CURLOPT_URL, url.c_str());
515 curl_easy_setopt(connectionHandle, CURLOPT_WRITEFUNCTION, WriteData);
516 curl_easy_setopt(connectionHandle, CURLOPT_WRITEDATA, &writeContext);
517 curl_easy_setopt(connectionHandle, CURLOPT_HEADERFUNCTION, WriteHeader);
518 curl_easy_setopt(connectionHandle, CURLOPT_HEADERDATA, response.get());
519
520 //we only want to override the default path if someone has explicitly told us to.
521 if(!m_caPath.empty())
522 {
523 curl_easy_setopt(connectionHandle, CURLOPT_CAPATH, m_caPath.c_str());
524 }
525 if(!m_caFile.empty())
526 {
527 curl_easy_setopt(connectionHandle, CURLOPT_CAINFO, m_caFile.c_str());
528 }
529
530 // only set by android test builds because the emulator is missing a cert needed for aws services
531#ifdef TEST_CERT_PATH
532 curl_easy_setopt(connectionHandle, CURLOPT_CAPATH, TEST_CERT_PATH);
533#endif // TEST_CERT_PATH
534
535 if (m_verifySSL)
536 {
537 curl_easy_setopt(connectionHandle, CURLOPT_SSL_VERIFYPEER, 1L);
538 curl_easy_setopt(connectionHandle, CURLOPT_SSL_VERIFYHOST, 2L);
539
540#if LIBCURL_VERSION_MAJOR >= 7
541#if LIBCURL_VERSION_MINOR >= 34
542 curl_easy_setopt(connectionHandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
543#endif //LIBCURL_VERSION_MINOR
544#endif //LIBCURL_VERSION_MAJOR
545 }
546 else
547 {
548 curl_easy_setopt(connectionHandle, CURLOPT_SSL_VERIFYPEER, 0L);
549 curl_easy_setopt(connectionHandle, CURLOPT_SSL_VERIFYHOST, 0L);
550 }
551
552 if (m_allowRedirects)
553 {
554 curl_easy_setopt(connectionHandle, CURLOPT_FOLLOWLOCATION, 1L);
555 }
556 else
557 {
558 curl_easy_setopt(connectionHandle, CURLOPT_FOLLOWLOCATION, 0L);
559 }
560
561#ifdef ENABLE_CURL_LOGGING
562 curl_easy_setopt(connectionHandle, CURLOPT_VERBOSE, 1);
563 curl_easy_setopt(connectionHandle, CURLOPT_DEBUGFUNCTION, CurlDebugCallback);
564#endif
565 if (m_isUsingProxy)
566 {
567 Aws::StringStream ss;
568 ss << m_proxyScheme << "://" << m_proxyHost;
569 curl_easy_setopt(connectionHandle, CURLOPT_PROXY, ss.str().c_str());
570 curl_easy_setopt(connectionHandle, CURLOPT_PROXYPORT, (long) m_proxyPort);
571 if (!m_proxyUserName.empty() || !m_proxyPassword.empty())
572 {
573 curl_easy_setopt(connectionHandle, CURLOPT_PROXYUSERNAME, m_proxyUserName.c_str());
574 curl_easy_setopt(connectionHandle, CURLOPT_PROXYPASSWORD, m_proxyPassword.c_str());
575 }
576#ifdef CURL_HAS_TLS_PROXY
577 if (!m_proxySSLCertPath.empty())
578 {
579 curl_easy_setopt(connectionHandle, CURLOPT_PROXY_SSLCERT, m_proxySSLCertPath.c_str());
580 if (!m_proxySSLCertType.empty())
581 {
582 curl_easy_setopt(connectionHandle, CURLOPT_PROXY_SSLCERTTYPE, m_proxySSLCertType.c_str());
583 }
584 }
585 if (!m_proxySSLKeyPath.empty())
586 {
587 curl_easy_setopt(connectionHandle, CURLOPT_PROXY_SSLKEY, m_proxySSLKeyPath.c_str());
588 if (!m_proxySSLKeyType.empty())
589 {
590 curl_easy_setopt(connectionHandle, CURLOPT_PROXY_SSLKEYTYPE, m_proxySSLKeyType.c_str());
591 }
592 if (!m_proxyKeyPasswd.empty())
593 {
594 curl_easy_setopt(connectionHandle, CURLOPT_PROXY_KEYPASSWD, m_proxyKeyPasswd.c_str());
595 }
596 }
597#endif //CURL_HAS_TLS_PROXY
598 }
599 else
600 {
601 curl_easy_setopt(connectionHandle, CURLOPT_PROXY, "");
602 }
603
604 if (request.GetContentBody())
605 {
606 curl_easy_setopt(connectionHandle, CURLOPT_READFUNCTION, ReadBody);
607 curl_easy_setopt(connectionHandle, CURLOPT_READDATA, &readContext);
608 curl_easy_setopt(connectionHandle, CURLOPT_SEEKFUNCTION, SeekBody);
609 curl_easy_setopt(connectionHandle, CURLOPT_SEEKDATA, &readContext);
610 }
611 Aws::Utils::DateTime startTransmissionTime = Aws::Utils::DateTime::Now();
612 CURLcode curlResponseCode = curl_easy_perform(connectionHandle);
613 bool shouldContinueRequest = ContinueRequest(request);
614 if (curlResponseCode != CURLE_OK && shouldContinueRequest)
615 {
616 response = nullptr;
617 AWS_LOGSTREAM_ERROR(CURL_HTTP_CLIENT_TAG, "Curl returned error code " << curlResponseCode
618 << " - " << curl_easy_strerror(curlResponseCode));
619 }
620 else if(!shouldContinueRequest)
621 {
622 response->SetResponseCode(HttpResponseCode::REQUEST_NOT_MADE);
623 }
624 else
625 {
626 long responseCode;
627 curl_easy_getinfo(connectionHandle, CURLINFO_RESPONSE_CODE, &responseCode);
628 response->SetResponseCode(static_cast<HttpResponseCode>(responseCode));
629 AWS_LOGSTREAM_DEBUG(CURL_HTTP_CLIENT_TAG, "Returned http response code " << responseCode);
630
631 char* contentType = nullptr;
632 curl_easy_getinfo(connectionHandle, CURLINFO_CONTENT_TYPE, &contentType);
633 if (contentType)
634 {
635 response->SetContentType(contentType);
636 AWS_LOGSTREAM_DEBUG(CURL_HTTP_CLIENT_TAG, "Returned content type " << contentType);
637 }
638
639 if (request.GetMethod() != HttpMethod::HTTP_HEAD &&
640 writeContext.m_client->IsRequestProcessingEnabled() &&
641 response->HasHeader(Aws::Http::CONTENT_LENGTH_HEADER))
642 {
643 const Aws::String& contentLength = response->GetHeader(Aws::Http::CONTENT_LENGTH_HEADER);
644 int64_t numBytesResponseReceived = writeContext.m_numBytesResponseReceived;
645 AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, "Response content-length header: " << contentLength);
646 AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, "Response body length: " << numBytesResponseReceived);
647 if (StringUtils::ConvertToInt64(contentLength.c_str()) != numBytesResponseReceived)
648 {
649 response = nullptr;
650 AWS_LOGSTREAM_ERROR(CURL_HTTP_CLIENT_TAG, "Response body length doesn't match the content-length header.");
651 }
652 }
653
654 AWS_LOGSTREAM_DEBUG(CURL_HTTP_CLIENT_TAG, "Releasing curl handle " << connectionHandle);
655 }
656
657 double timep;
658 CURLcode ret = curl_easy_getinfo(connectionHandle, CURLINFO_NAMELOOKUP_TIME, &timep); // DNS Resolve Latency, seconds.
659 if (ret == CURLE_OK)
660 {
661 request.AddRequestMetric(GetHttpClientMetricNameByType(HttpClientMetricsType::DnsLatency), static_cast<int64_t>(timep * 1000));// to milliseconds
662 }
663
664 ret = curl_easy_getinfo(connectionHandle, CURLINFO_STARTTRANSFER_TIME, &timep); // Connect Latency
665 if (ret == CURLE_OK)
666 {
667 request.AddRequestMetric(GetHttpClientMetricNameByType(HttpClientMetricsType::ConnectLatency), static_cast<int64_t>(timep * 1000));
668 }
669
670 ret = curl_easy_getinfo(connectionHandle, CURLINFO_APPCONNECT_TIME, &timep); // Ssl Latency
671 if (ret == CURLE_OK)
672 {
673 request.AddRequestMetric(GetHttpClientMetricNameByType(HttpClientMetricsType::SslLatency), static_cast<int64_t>(timep * 1000));
674 }
675
676 const char* ip = nullptr;
677 auto curlGetInfoResult = curl_easy_getinfo(connectionHandle, CURLINFO_PRIMARY_IP, &ip); // Get the IP address of the remote endpoint
678 if (curlGetInfoResult == CURLE_OK && ip)
679 {
680 request.SetResolvedRemoteHost(ip);
681 }
682 if (curlResponseCode != CURLE_OK)
683 {
684 m_curlHandleContainer.DestroyCurlHandle(connectionHandle);
685 }
686 else
687 {
688 m_curlHandleContainer.ReleaseCurlHandle(connectionHandle);
689 }
690 //go ahead and flush the response body stream
691 if(response)
692 {
693 response->GetResponseBody().flush();
694 }
695 request.AddRequestMetric(GetHttpClientMetricNameByType(HttpClientMetricsType::RequestLatency), (DateTime::Now() - startTransmissionTime).count());
696 }
697
698 if (headers)
699 {
700 curl_slist_free_all(headers);
701 }
702}
703
704std::shared_ptr<HttpResponse> CurlHttpClient::MakeRequest(HttpRequest& request,
705 Aws::Utils::RateLimits::RateLimiterInterface* readLimiter,
706 Aws::Utils::RateLimits::RateLimiterInterface* writeLimiter) const
707{
708 auto response = Aws::MakeShared<StandardHttpResponse>(CURL_HTTP_CLIENT_TAG, request);
709 MakeRequestInternal(request, response, readLimiter, writeLimiter);
710 return response;
711}
712
713std::shared_ptr<HttpResponse> CurlHttpClient::MakeRequest(const std::shared_ptr<HttpRequest>& request, Aws::Utils::RateLimits::RateLimiterInterface* readLimiter,
714 Aws::Utils::RateLimits::RateLimiterInterface* writeLimiter) const
715{
716 auto response = Aws::MakeShared<StandardHttpResponse>(CURL_HTTP_CLIENT_TAG, request);
717 MakeRequestInternal(*request, response, readLimiter, writeLimiter);
718 return response;
719}
720
721