in src/aws-cpp-sdk-core/source/http/curl/CurlHttpClient.cpp [649:1010]
std::shared_ptr<HttpResponse> CurlHttpClient::MakeRequest(const std::shared_ptr<HttpRequest>& request,
Aws::Utils::RateLimits::RateLimiterInterface* readLimiter,
Aws::Utils::RateLimits::RateLimiterInterface* writeLimiter) const
{
URI uri = request->GetUri();
Aws::String url = uri.GetURIString();
std::shared_ptr<HttpResponse> response = Aws::MakeShared<StandardHttpResponse>(CURL_HTTP_CLIENT_TAG, request);
AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, "Making request to " << url);
struct curl_slist* headers = NULL;
if (writeLimiter != nullptr)
{
writeLimiter->ApplyAndPayForCost(request->GetSize());
}
Aws::StringStream headerStream;
HeaderValueCollection requestHeaders = request->GetHeaders();
AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, "Including headers:");
for (auto& requestHeader : requestHeaders)
{
headerStream.str("");
if (requestHeader.second.empty()) {
headerStream << requestHeader.first << ";";
} else {
headerStream << requestHeader.first << ": " << requestHeader.second;
}
Aws::String headerString = headerStream.str();
AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, headerString);
headers = curl_slist_append(headers, headerString.c_str());
}
if (!request->HasHeader(Http::TRANSFER_ENCODING_HEADER))
{
headers = curl_slist_append(headers, "transfer-encoding:");
}
if (!request->HasHeader(Http::CONTENT_LENGTH_HEADER))
{
headers = curl_slist_append(headers, "content-length:");
}
if (!request->HasHeader(Http::CONTENT_TYPE_HEADER))
{
headers = curl_slist_append(headers, "content-type:");
}
// Discard Expect header so as to avoid using multiple payloads to send a http request (header + body)
if (m_disableExpectHeader)
{
headers = curl_slist_append(headers, "Expect:");
}
CURL* connectionHandle = m_curlHandleContainer.AcquireCurlHandle();
if (connectionHandle)
{
AWS_LOGSTREAM_DEBUG(CURL_HTTP_CLIENT_TAG, "Obtained connection handle " << connectionHandle);
if (headers)
{
curl_easy_setopt(connectionHandle, CURLOPT_HTTPHEADER, headers);
}
CurlWriteCallbackContext writeContext(this, connectionHandle ,request.get(), response.get(), readLimiter);
const auto readContext = [this, &connectionHandle, &request, &writeLimiter]() -> CurlReadCallbackContext {
if (request->GetContentBody() != nullptr) {
auto chunkedBodyPtr = Aws::MakeShared<AwsChunkedStream<>>(CURL_HTTP_CLIENT_TAG, request.get(), request->GetContentBody());
return {this, connectionHandle, request.get(), writeLimiter, std::move(chunkedBodyPtr)};
}
return {this, connectionHandle, request.get(), writeLimiter};
}();
SetOptCodeForHttpMethod(connectionHandle, request);
curl_easy_setopt(connectionHandle, CURLOPT_URL, url.c_str());
curl_easy_setopt(connectionHandle, CURLOPT_WRITEFUNCTION, WriteData);
curl_easy_setopt(connectionHandle, CURLOPT_WRITEDATA, &writeContext);
curl_easy_setopt(connectionHandle, CURLOPT_HEADERFUNCTION, WriteHeader);
curl_easy_setopt(connectionHandle, CURLOPT_HEADERDATA, &writeContext);
//we only want to override the default path if someone has explicitly told us to.
if(!m_caPath.empty())
{
curl_easy_setopt(connectionHandle, CURLOPT_CAPATH, m_caPath.c_str());
}
if(!m_caFile.empty())
{
curl_easy_setopt(connectionHandle, CURLOPT_CAINFO, m_caFile.c_str());
}
// enable the cookie engine without reading any initial cookies.
curl_easy_setopt(connectionHandle, CURLOPT_COOKIEFILE, "");
// only set by android test builds because the emulator is missing a cert needed for aws services
#ifdef TEST_CERT_PATH
curl_easy_setopt(connectionHandle, CURLOPT_CAPATH, TEST_CERT_PATH);
#endif // TEST_CERT_PATH
if (m_verifySSL)
{
curl_easy_setopt(connectionHandle, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(connectionHandle, CURLOPT_SSL_VERIFYHOST, 2L);
#if defined(ENFORCE_TLS_V1_3) && LIBCURL_VERSION_NUM >= 0x073400 // 7.52.0
curl_easy_setopt(connectionHandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_3);
#elif defined(ENFORCE_TLS_V1_2) && LIBCURL_VERSION_NUM >= 0x072200 // 7.34.0
curl_easy_setopt(connectionHandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
#else
curl_easy_setopt(connectionHandle, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
#endif
}
else
{
curl_easy_setopt(connectionHandle, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(connectionHandle, CURLOPT_SSL_VERIFYHOST, 0L);
}
if (m_allowRedirects)
{
curl_easy_setopt(connectionHandle, CURLOPT_FOLLOWLOCATION, 1L);
}
else
{
curl_easy_setopt(connectionHandle, CURLOPT_FOLLOWLOCATION, 0L);
}
if (m_enableHttpClientTrace)
{
AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, "Activating CURL traces");
curl_easy_setopt(connectionHandle, CURLOPT_VERBOSE, 1);
curl_easy_setopt(connectionHandle, CURLOPT_DEBUGFUNCTION, CurlDebugCallback);
}
if (m_isUsingProxy)
{
Aws::StringStream ss;
ss << m_proxyScheme << "://" << m_proxyHost;
curl_easy_setopt(connectionHandle, CURLOPT_PROXY, ss.str().c_str());
curl_easy_setopt(connectionHandle, CURLOPT_PROXYPORT, (long) m_proxyPort);
#if LIBCURL_VERSION_NUM >= 0x073400 // 7.52.0
if(!m_proxyCaPath.empty())
{
curl_easy_setopt(connectionHandle, CURLOPT_PROXY_CAPATH, m_proxyCaPath.c_str());
}
if(!m_proxyCaFile.empty())
{
curl_easy_setopt(connectionHandle, CURLOPT_PROXY_CAINFO, m_proxyCaFile.c_str());
}
#endif
if (!m_proxyUserName.empty() || !m_proxyPassword.empty())
{
curl_easy_setopt(connectionHandle, CURLOPT_PROXYUSERNAME, m_proxyUserName.c_str());
curl_easy_setopt(connectionHandle, CURLOPT_PROXYPASSWORD, m_proxyPassword.c_str());
}
curl_easy_setopt(connectionHandle, CURLOPT_NOPROXY, m_nonProxyHosts.c_str());
#ifdef CURL_HAS_TLS_PROXY
if (!m_proxySSLCertPath.empty())
{
curl_easy_setopt(connectionHandle, CURLOPT_PROXY_SSLCERT, m_proxySSLCertPath.c_str());
if (!m_proxySSLCertType.empty())
{
curl_easy_setopt(connectionHandle, CURLOPT_PROXY_SSLCERTTYPE, m_proxySSLCertType.c_str());
}
}
if (!m_proxySSLKeyPath.empty())
{
curl_easy_setopt(connectionHandle, CURLOPT_PROXY_SSLKEY, m_proxySSLKeyPath.c_str());
if (!m_proxySSLKeyType.empty())
{
curl_easy_setopt(connectionHandle, CURLOPT_PROXY_SSLKEYTYPE, m_proxySSLKeyType.c_str());
}
if (!m_proxyKeyPasswd.empty())
{
curl_easy_setopt(connectionHandle, CURLOPT_PROXY_KEYPASSWD, m_proxyKeyPasswd.c_str());
}
}
#endif //CURL_HAS_TLS_PROXY
}
else
{
if(!m_isAllowSystemProxy)
{
curl_easy_setopt(connectionHandle, CURLOPT_PROXY, "");
}
}
if (request->GetContentBody())
{
curl_easy_setopt(connectionHandle, CURLOPT_READFUNCTION, ReadBodyFunc);
curl_easy_setopt(connectionHandle, CURLOPT_READDATA, &readContext);
curl_easy_setopt(connectionHandle, CURLOPT_SEEKFUNCTION, SeekBody);
curl_easy_setopt(connectionHandle, CURLOPT_SEEKDATA, &readContext);
if (request->IsEventStreamRequest() && !response->HasHeader(Aws::Http::X_AMZN_ERROR_TYPE))
{
curl_easy_setopt(connectionHandle, CURLOPT_READFUNCTION, ReadBodyStreaming);
curl_easy_setopt(connectionHandle, CURLOPT_NOPROGRESS, 0L);
#if LIBCURL_VERSION_NUM >= 0x072000 // 7.32.0
curl_easy_setopt(connectionHandle, CURLOPT_XFERINFOFUNCTION, CurlHttpClient::CurlProgressCallback);
curl_easy_setopt(connectionHandle, CURLOPT_XFERINFODATA, &readContext);
#else
curl_easy_setopt(connectionHandle, CURLOPT_PROGRESSFUNCTION, CurlHttpClient::CurlProgressCallback);
curl_easy_setopt(connectionHandle, CURLOPT_PROGRESSDATA, &readContext);
#endif
}
}
ByteBuffer errorBuffer(CURL_ERROR_SIZE);
if (errorBuffer.GetUnderlyingData() && errorBuffer.GetSize() >= CURL_ERROR_SIZE) {
errorBuffer[0] = '\0';
curl_easy_setopt(connectionHandle, CURLOPT_ERRORBUFFER, errorBuffer.GetUnderlyingData());
} else {
AWS_LOGSTREAM_ERROR(CURL_HTTP_CLIENT_TAG, "Failed to allocate CURLOPT_ERRORBUFFER");
}
OverrideOptionsOnConnectionHandle(connectionHandle);
Aws::Utils::DateTime startTransmissionTime = Aws::Utils::DateTime::Now();
CURLcode curlResponseCode = curl_easy_perform(connectionHandle);
curl_easy_setopt(connectionHandle, CURLOPT_ERRORBUFFER, nullptr);
bool shouldContinueRequest = ContinueRequest(*request);
if (curlResponseCode != CURLE_OK && shouldContinueRequest)
{
response->SetClientErrorType(CoreErrors::NETWORK_CONNECTION);
Aws::StringStream ss;
ss << "curlCode: " << curlResponseCode << ", " << curl_easy_strerror(curlResponseCode);
if (errorBuffer.GetUnderlyingData() && errorBuffer.GetSize() >= CURL_ERROR_SIZE) {
errorBuffer[CURL_ERROR_SIZE-1] = '\0';
ss << "; Details: " << errorBuffer.GetUnderlyingData();
}
response->SetClientErrorMessage(ss.str());
AWS_LOGSTREAM_ERROR(CURL_HTTP_CLIENT_TAG, "Curl returned error: " << response->GetClientErrorMessage());
}
else if(!shouldContinueRequest)
{
response->SetClientErrorType(CoreErrors::USER_CANCELLED);
response->SetClientErrorMessage("Request cancelled by user's continuation handler");
}
else
{
long responseCode;
curl_easy_getinfo(connectionHandle, CURLINFO_RESPONSE_CODE, &responseCode);
response->SetResponseCode(static_cast<HttpResponseCode>(responseCode));
AWS_LOGSTREAM_DEBUG(CURL_HTTP_CLIENT_TAG, "Returned http response code " << responseCode);
char* contentType = nullptr;
curl_easy_getinfo(connectionHandle, CURLINFO_CONTENT_TYPE, &contentType);
if (contentType)
{
response->SetContentType(contentType);
AWS_LOGSTREAM_DEBUG(CURL_HTTP_CLIENT_TAG, "Returned content type " << contentType);
}
bool hasContentLength = false;
int64_t contentLength =
GetContentLengthFromHeader(connectionHandle, hasContentLength);
if (request->GetMethod() != HttpMethod::HTTP_HEAD &&
writeContext.m_client->IsRequestProcessingEnabled() &&
hasContentLength)
{
int64_t numBytesResponseReceived = writeContext.m_numBytesResponseReceived;
AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, "Response content-length header: " << contentLength);
AWS_LOGSTREAM_TRACE(CURL_HTTP_CLIENT_TAG, "Response body length: " << numBytesResponseReceived);
if (contentLength != numBytesResponseReceived)
{
response->SetClientErrorType(CoreErrors::NETWORK_CONNECTION);
response->SetClientErrorMessage("Response body length doesn't match the content-length header.");
AWS_LOGSTREAM_ERROR(CURL_HTTP_CLIENT_TAG, "Response body length doesn't match the content-length header.");
}
}
AWS_LOGSTREAM_DEBUG(CURL_HTTP_CLIENT_TAG, "Releasing curl handle " << connectionHandle);
}
double timep;
CURLcode ret = curl_easy_getinfo(connectionHandle, CURLINFO_NAMELOOKUP_TIME, &timep); // DNS Resolve Latency, seconds.
if (ret == CURLE_OK)
{
request->AddRequestMetric(GetHttpClientMetricNameByType(HttpClientMetricsType::DnsLatency), static_cast<int64_t>(timep * 1000));// to milliseconds
}
ret = curl_easy_getinfo(connectionHandle, CURLINFO_STARTTRANSFER_TIME, &timep); // Connect Latency
if (ret == CURLE_OK)
{
request->AddRequestMetric(GetHttpClientMetricNameByType(HttpClientMetricsType::ConnectLatency), static_cast<int64_t>(timep * 1000));
request->AddRequestMetric(GetHttpClientMetricNameByType(HttpClientMetricsType::TimeToFirstByte), static_cast<int64_t>(timep * 1000));
}
ret = curl_easy_getinfo(connectionHandle, CURLINFO_PRETRANSFER_TIME, &timep);
if (ret == CURLE_OK)
{
request->AddRequestMetric(GetHttpClientMetricNameByType(HttpClientMetricsType::TimeToConnect), static_cast<int64_t>(timep * 1000));
}
#if LIBCURL_VERSION_NUM >= 0x073D00 // 7.61.0
curl_off_t metric;
ret = curl_easy_getinfo(connectionHandle, CURLINFO_APPCONNECT_TIME_T, &metric); // Ssl Latency
#else
double metric;
ret = curl_easy_getinfo(connectionHandle, CURLINFO_APPCONNECT_TIME, &metric); // Ssl Latency
#endif
if (ret == CURLE_OK)
{
request->AddRequestMetric(GetHttpClientMetricNameByType(HttpClientMetricsType::SslLatency), static_cast<int64_t>(metric * 1000));
}
#if LIBCURL_VERSION_NUM >= 0x073700 // 7.55.0
ret = curl_easy_getinfo(connectionHandle, CURLINFO_SPEED_DOWNLOAD_T, &metric); // throughput
#else
ret = curl_easy_getinfo(connectionHandle, CURLINFO_SPEED_DOWNLOAD, &metric); // throughput
#endif
if (ret == CURLE_OK)
{
//Record two metric names to preserve backwards compat
request->AddRequestMetric(GetHttpClientMetricNameByType(HttpClientMetricsType::Throughput), static_cast<int64_t>(metric));
request->AddRequestMetric(GetHttpClientMetricNameByType(HttpClientMetricsType::DownloadSpeed), static_cast<int64_t>(metric));
}
#if LIBCURL_VERSION_NUM >= 0x073700 // 7.55.0
ret = curl_easy_getinfo(connectionHandle, CURLINFO_SPEED_UPLOAD_T, &metric); // Upload Speed
#else
ret = curl_easy_getinfo(connectionHandle, CURLINFO_SPEED_UPLOAD, &metric); // Upload Speed
#endif
if (ret == CURLE_OK)
{
request->AddRequestMetric(GetHttpClientMetricNameByType(HttpClientMetricsType::UploadSpeed), static_cast<int64_t>(metric));
}
const char* ip = nullptr;
auto curlGetInfoResult = curl_easy_getinfo(connectionHandle, CURLINFO_PRIMARY_IP, &ip); // Get the IP address of the remote endpoint
if (curlGetInfoResult == CURLE_OK && ip)
{
request->SetResolvedRemoteHost(ip);
}
if (curlResponseCode != CURLE_OK) {
m_curlHandleContainer.DestroyCurlHandle(connectionHandle);
} else {
m_curlHandleContainer.ReleaseCurlHandle(connectionHandle);
}
connectionHandle = nullptr;
//go ahead and flush the response body stream
response->GetResponseBody().flush();
if (response->GetResponseBody().fail()) {
const auto& ref = response->GetResponseBody();
Aws::StringStream ss;
ss << "Failed to flush response stream (eof: " << ref.eof() << ", bad: " << ref.bad() << ")";
response->SetClientErrorType(CoreErrors::INTERNAL_FAILURE);
response->SetClientErrorMessage(ss.str());
AWS_LOGSTREAM_ERROR(CURL_HTTP_CLIENT_TAG, ss.str());
}
request->AddRequestMetric(GetHttpClientMetricNameByType(HttpClientMetricsType::RequestLatency), (DateTime::Now() - startTransmissionTime).count());
}
if (headers)
{
curl_slist_free_all(headers);
}
return response;
}