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 | |
28 | using namespace Aws::Client; |
29 | using namespace Aws::Http; |
30 | using namespace Aws::Http::Standard; |
31 | using namespace Aws::Utils; |
32 | using namespace Aws::Utils::Logging; |
33 | using namespace Aws::Monitoring; |
34 | |
35 | #ifdef AWS_CUSTOM_MEMORY_MANAGEMENT |
36 | |
37 | static const char* MemTag = "libcurl" ; |
38 | static size_t offset = sizeof(size_t); |
39 | |
40 | static 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 | |
48 | static 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 | |
57 | static 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 | |
96 | static 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 | |
111 | static 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 | |
133 | struct 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 | |
153 | struct 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 | |
166 | static const char* CURL_HTTP_CLIENT_TAG = "CurlHttpClient" ; |
167 | |
168 | static 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 | |
201 | static size_t (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 (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 | |
221 | static 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 | |
260 | static 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 | |
302 | void 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 | |
358 | std::atomic<bool> CurlHttpClient::isInit(false); |
359 | |
360 | void 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 | |
377 | void CurlHttpClient::CleanupGlobalState() |
378 | { |
379 | curl_global_cleanup(); |
380 | } |
381 | |
382 | Aws::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 | |
412 | int 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 | |
431 | CurlHttpClient::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 | |
448 | void 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* = NULL; |
458 | |
459 | if (writeLimiter != nullptr) |
460 | { |
461 | writeLimiter->ApplyAndPayForCost(request.GetSize()); |
462 | } |
463 | |
464 | Aws::StringStream ; |
465 | HeaderValueCollection = request.GetHeaders(); |
466 | |
467 | AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, "Including headers:" ); |
468 | for (auto& : requestHeaders) |
469 | { |
470 | headerStream.str("" ); |
471 | headerStream << requestHeader.first << ": " << requestHeader.second; |
472 | Aws::String = 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 | |
704 | std::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 | |
713 | std::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 | |