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..657193f2e11b 100644 --- a/src/aws-cpp-sdk-core/source/http/curl/CurlHttpClient.cpp +++ b/src/aws-cpp-sdk-core/source/http/curl/CurlHttpClient.cpp @@ -194,11 +194,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 +279,11 @@ 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); + auto& headersHandler = context->m_request->GetHeadersReceivedEventHandler(); + if (headersHandler) + { + headersHandler(context->m_request, context->m_response); + } } return size * nmemb; diff --git a/src/aws-cpp-sdk-core/source/http/windows/WinSyncHttpClient.cpp b/src/aws-cpp-sdk-core/source/http/windows/WinSyncHttpClient.cpp index ee35bb5a81ff..afee408a170c 100644 --- a/src/aws-cpp-sdk-core/source/http/windows/WinSyncHttpClient.cpp +++ b/src/aws-cpp-sdk-core/source/http/windows/WinSyncHttpClient.cpp @@ -209,6 +209,12 @@ bool WinSyncHttpClient::BuildSuccessResponse(const std::shared_ptr& } } + auto& headersHandler = request->GetHeadersReceivedEventHandler(); + if (headersHandler) + { + headersHandler(request.get(), response.get()); + } + if (request->GetMethod() != HttpMethod::HTTP_HEAD) { if(!ContinueRequest(*request) || !IsRequestProcessingEnabled()) @@ -252,12 +258,6 @@ bool WinSyncHttpClient::BuildSuccessResponse(const std::shared_ptr& } } - auto& headersHandler = request->GetHeadersReceivedEventHandler(); - if (headersHandler) - { - headersHandler(request.get(), response.get()); - } - if (readLimiter != nullptr) { readLimiter->ApplyAndPayForCost(read); diff --git a/tests/aws-cpp-sdk-s3-integration-tests/BucketAndObjectOperationTest.cpp b/tests/aws-cpp-sdk-s3-integration-tests/BucketAndObjectOperationTest.cpp index b32f73aff260..bf0953f6551a 100644 --- a/tests/aws-cpp-sdk-s3-integration-tests/BucketAndObjectOperationTest.cpp +++ b/tests/aws-cpp-sdk-s3-integration-tests/BucketAndObjectOperationTest.cpp @@ -2690,6 +2690,40 @@ namespace EXPECT_FALSE(outcome.IsSuccess()); } + TEST_F(BucketAndObjectOperationTest, TestHeadersReceivedEventHandlerFiresOnEmptyBodyResponse) { + const String fullBucketName = CalculateBucketName(BASE_PUT_OBJECTS_BUCKET_NAME.c_str()); + SCOPED_TRACE(Aws::String("FullBucket" + "Name ") + fullBucketName); + CreateBucketRequest createBucketRequest; + createBucketRequest.SetBucket(fullBucketName); + createBucketRequest.SetACL(BucketCannedACL::private_); + CreateBucketOutcome createBucketOutcome = CreateBucket(createBucketRequest); + AWS_ASSERT_SUCCESS(createBucketOutcome); + ASSERT_TRUE(WaitForBucketToPropagate(fullBucketName, Client)); + TagTestBucket(fullBucketName, Client); + + // 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);