diff --git a/src/aws-cpp-sdk-core/source/http/curl/CurlHttpClient.cpp b/src/aws-cpp-sdk-core/source/http/curl/CurlHttpClient.cpp index 58fc56875de7..5964872258c3 100644 --- a/src/aws-cpp-sdk-core/source/http/curl/CurlHttpClient.cpp +++ b/src/aws-cpp-sdk-core/source/http/curl/CurlHttpClient.cpp @@ -138,7 +138,8 @@ struct CurlWriteCallbackContext m_request(request), m_response(response), m_rateLimiter(rateLimiter), - m_numBytesResponseReceived(0) + m_numBytesResponseReceived(0), + m_headersReceivedEventFired(false) {} const CurlHttpClient* m_client; @@ -147,6 +148,7 @@ struct CurlWriteCallbackContext HttpResponse* m_response; Aws::Utils::RateLimits::RateLimiterInterface* m_rateLimiter; int64_t m_numBytesResponseReceived; + bool m_headersReceivedEventFired; }; static const char* CURL_HTTP_CLIENT_TAG = "CurlHttpClient"; @@ -194,11 +196,6 @@ static size_t WriteData(char* ptr, size_t size, size_t nmemb, void* userdata) } HttpResponse* response = context->m_response; - auto& headersHandler = context->m_request->GetHeadersReceivedEventHandler(); - if (context->m_numBytesResponseReceived == 0 && headersHandler) - { - headersHandler(context->m_request, context->m_response); - } size_t sizeToWrite = size * nmemb; if (context->m_rateLimiter) @@ -284,6 +281,16 @@ static size_t WriteHeader(char* ptr, size_t size, size_t nmemb, void* userdata) curl_easy_getinfo(context->m_curlHandle, CURLINFO_RESPONSE_CODE, &responseCode); response->SetResponseCode(static_cast(responseCode)); AWS_LOGSTREAM_DEBUG(CURL_HTTP_CLIENT_TAG, "Returned http response code " << responseCode); + + if (!context->m_headersReceivedEventFired) + { + auto& headersHandler = context->m_request->GetHeadersReceivedEventHandler(); + if (headersHandler) + { + headersHandler(context->m_request, context->m_response); + } + context->m_headersReceivedEventFired = true; + } } return size * nmemb; diff --git a/tests/aws-cpp-sdk-s3-integration-tests/BucketAndObjectOperationTest.cpp b/tests/aws-cpp-sdk-s3-integration-tests/BucketAndObjectOperationTest.cpp index b32f73aff260..dd18c70329f2 100644 --- a/tests/aws-cpp-sdk-s3-integration-tests/BucketAndObjectOperationTest.cpp +++ b/tests/aws-cpp-sdk-s3-integration-tests/BucketAndObjectOperationTest.cpp @@ -2690,6 +2690,32 @@ namespace EXPECT_FALSE(outcome.IsSuccess()); } + TEST_F(BucketAndObjectOperationTest, TestHeadersReceivedEventHandlerFiresOnEmptyBodyResponse) { + const String fullBucketName = CalculateBucketName(BASE_PUT_OBJECTS_BUCKET_NAME.c_str()); + SCOPED_TRACE(Aws::String("FullBucketName ") + fullBucketName); + + // PutObject returns 200 with headers but an empty body + Aws::S3::Model::PutObjectRequest request; + request.SetBucket(fullBucketName); + request.SetKey("headers-received-handler-test"); + + auto body = Aws::MakeShared(ALLOCATION_TAG, "test content"); + request.SetBody(body); + + std::atomic handlerFired{false}; + Aws::Http::HttpResponseCode capturedCode{Aws::Http::HttpResponseCode::REQUEST_NOT_MADE}; + request.SetHeadersReceivedEventHandler( + [&handlerFired, &capturedCode](const Aws::Http::HttpRequest*, Aws::Http::HttpResponse* response) { + handlerFired = true; + capturedCode = response->GetResponseCode(); + }); + + auto outcome = Client->PutObject(request); + AWS_EXPECT_SUCCESS(outcome); + EXPECT_TRUE(handlerFired.load()) << "HeadersReceivedEventHandler must fire even when response body is empty"; + EXPECT_EQ(Aws::Http::HttpResponseCode::OK, capturedCode); + } + TEST_F(BucketAndObjectOperationTest, ShouldSkipResponseValidationOnCompositeChecksums) { const auto fullBucketName = CalculateBucketName(BASE_PUT_MULTIPART_COMPOSITE_CHECKSUM_BUCKET_NAME.c_str()); m_bucketsToDelete.insert(fullBucketName);