| 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 | |