From 9e9a93f7a3ec62e35b2cac72bcdee1206e002bf0 Mon Sep 17 00:00:00 2001 From: sbaluja Date: Tue, 5 May 2026 13:47:56 -0400 Subject: [PATCH 1/4] CurlHttpClient.cpp: move HeadersReceivedEventHandler to WriteHeader callback --- .../source/http/curl/CurlHttpClient.cpp | 19 +++++++++----- .../BucketAndObjectOperationTest.cpp | 26 +++++++++++++++++++ 2 files changed, 39 insertions(+), 6 deletions(-) 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); From 301c1bf6432c9452873cbb0cfbf4e98c9a676724 Mon Sep 17 00:00:00 2001 From: sbaluja Date: Wed, 6 May 2026 13:03:54 -0400 Subject: [PATCH 2/4] Fix integ test --- .../BucketAndObjectOperationTest.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/aws-cpp-sdk-s3-integration-tests/BucketAndObjectOperationTest.cpp b/tests/aws-cpp-sdk-s3-integration-tests/BucketAndObjectOperationTest.cpp index dd18c70329f2..bf0953f6551a 100644 --- a/tests/aws-cpp-sdk-s3-integration-tests/BucketAndObjectOperationTest.cpp +++ b/tests/aws-cpp-sdk-s3-integration-tests/BucketAndObjectOperationTest.cpp @@ -2692,7 +2692,15 @@ namespace TEST_F(BucketAndObjectOperationTest, TestHeadersReceivedEventHandlerFiresOnEmptyBodyResponse) { const String fullBucketName = CalculateBucketName(BASE_PUT_OBJECTS_BUCKET_NAME.c_str()); - SCOPED_TRACE(Aws::String("FullBucketName ") + fullBucketName); + 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; From edc627e4c4b04730b222c100af8536677e904346 Mon Sep 17 00:00:00 2001 From: sbaluja Date: Mon, 11 May 2026 11:46:05 -0400 Subject: [PATCH 3/4] Ensure header callback is called everytime headers are received --- .../source/http/curl/CurlHttpClient.cpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) 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 5964872258c3..657193f2e11b 100644 --- a/src/aws-cpp-sdk-core/source/http/curl/CurlHttpClient.cpp +++ b/src/aws-cpp-sdk-core/source/http/curl/CurlHttpClient.cpp @@ -138,8 +138,7 @@ struct CurlWriteCallbackContext m_request(request), m_response(response), m_rateLimiter(rateLimiter), - m_numBytesResponseReceived(0), - m_headersReceivedEventFired(false) + m_numBytesResponseReceived(0) {} const CurlHttpClient* m_client; @@ -148,7 +147,6 @@ 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"; @@ -281,15 +279,10 @@ 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) { - auto& headersHandler = context->m_request->GetHeadersReceivedEventHandler(); - if (headersHandler) - { - headersHandler(context->m_request, context->m_response); - } - context->m_headersReceivedEventFired = true; + headersHandler(context->m_request, context->m_response); } } From d064bb76f568580bff2d5c287866e3b803e93838 Mon Sep 17 00:00:00 2001 From: sbaluja Date: Mon, 11 May 2026 15:03:55 -0400 Subject: [PATCH 4/4] Fix same bug for windows http client --- .../source/http/windows/WinSyncHttpClient.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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);