From 2dda96305411d51abe81b7e77810645bcf18b72e Mon Sep 17 00:00:00 2001 From: David Ho Date: Thu, 9 Jul 2026 17:42:11 -0700 Subject: [PATCH 1/8] Fix PutObject hang when using CRT HTTP client with chunkedEncodingEnabled(false) --- .../internal/signer/FlexibleChecksummer.java | 7 +- .../signer/io/ChecksumSubscriber.java | 4 +- .../internal/signer/io/InMemoryPublisher.java | 25 ++- .../signer/io/InMemoryPublisherTest.java | 149 ++++++++++++++++++ .../pipeline/stages/AsyncSigningStage.java | 15 ++ .../stages/AsyncSigningStageTest.java | 46 ++++++ .../services/s3/PutObjectIntegrationTest.java | 17 ++ 7 files changed, 252 insertions(+), 11 deletions(-) create mode 100644 core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisherTest.java diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/FlexibleChecksummer.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/FlexibleChecksummer.java index 6c1539462788..2a0108426ef7 100644 --- a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/FlexibleChecksummer.java +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/FlexibleChecksummer.java @@ -81,9 +81,10 @@ public CompletableFuture> checksum(Publisher p } payload.subscribe(checksumSubscriber); - CompletableFuture> result = checksumSubscriber.completeFuture(); - result.thenRun(() -> addChecksums(request)); - return result; + return checksumSubscriber.completeFuture().thenApply(checksummedPayload -> { + addChecksums(request); + return checksummedPayload; + }); } private void addChecksums(SdkHttpRequest.Builder request) { diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriber.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriber.java index 3e1a878a1d0f..e7391e0b334c 100644 --- a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriber.java +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriber.java @@ -40,6 +40,7 @@ public final class ChecksumSubscriber implements Subscriber { private volatile Subscription subscription; private final List bufferedPayload = new ArrayList<>(); + private long totalBytes = 0; public ChecksumSubscriber(Collection consumers) { this.checksums.addAll(consumers); @@ -85,6 +86,7 @@ private void updateChecksumsAndBuffer(ByteBuffer buffer) { buffer.get(copyBuffer); checksums.forEach(c -> c.update(copyBuffer, 0, remaining)); bufferedPayload.add(ByteBuffer.wrap(copyBuffer)); + totalBytes += remaining; } @@ -95,7 +97,7 @@ public void onError(Throwable throwable) { @Override public void onComplete() { - checksumming.complete(new InMemoryPublisher(bufferedPayload)); + checksumming.complete(new InMemoryPublisher(bufferedPayload, totalBytes)); } public CompletableFuture> completeFuture() { diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisher.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisher.java index f094ce61ad11..892467459d0a 100644 --- a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisher.java +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisher.java @@ -18,24 +18,32 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; -import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.http.async.SdkHttpContentPublisher; import software.amazon.awssdk.utils.Validate; /** * Temporarily used for buffering all data into memory. */ @SdkInternalApi -public class InMemoryPublisher implements Publisher { +public class InMemoryPublisher implements SdkHttpContentPublisher { private final AtomicBoolean subscribed = new AtomicBoolean(false); private final List data; + private final long length; - public InMemoryPublisher(List data) { + public InMemoryPublisher(List data, long length) { this.data = new ArrayList<>(Validate.noNullElements(data, "Data must not contain null elements.")); + this.length = length; + } + + @Override + public Optional contentLength() { + return Optional.of(length); } @Override @@ -70,12 +78,15 @@ public void request(long n) { private void fulfillDemand() { do { - if (sending.compareAndSet(false, true)) { - try { + if (!sending.compareAndSet(false, true)) { + return; + } + try { + while (!done.get() && demand.get() > 0) { send(); - } finally { - sending.set(false); } + } finally { + sending.set(false); } } while (!done.get() && demand.get() > 0); } diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisherTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisherTest.java new file mode 100644 index 000000000000..fbe74b48dc67 --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisherTest.java @@ -0,0 +1,149 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.http.auth.aws.internal.signer.io; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.Test; +import org.reactivestreams.Subscriber; +import org.reactivestreams.Subscription; + +public class InMemoryPublisherTest { + + @Test + public void contentLength_returnsCorrectValue() { + List data = Arrays.asList( + ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8)), + ByteBuffer.wrap(" world".getBytes(StandardCharsets.UTF_8)) + ); + InMemoryPublisher publisher = new InMemoryPublisher(data, 11); + + assertThat(publisher.contentLength()).hasValue(11L); + } + + @Test + public void subscribe_deliversAllData() throws Exception { + byte[] bytes = "test data".getBytes(StandardCharsets.UTF_8); + List data = Arrays.asList(ByteBuffer.wrap(bytes)); + InMemoryPublisher publisher = new InMemoryPublisher(data, bytes.length); + + List received = new ArrayList<>(); + CountDownLatch completed = new CountDownLatch(1); + + publisher.subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(Long.MAX_VALUE); + } + + @Override + public void onNext(ByteBuffer byteBuffer) { + received.add(byteBuffer); + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + completed.countDown(); + } + }); + + assertThat(completed.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(received).hasSize(1); + } + + @Test + public void subscribe_reEntrantRequestFromOnNext_doesNotDeadlock() throws Exception { + List data = Arrays.asList( + ByteBuffer.wrap("a".getBytes(StandardCharsets.UTF_8)), + ByteBuffer.wrap("b".getBytes(StandardCharsets.UTF_8)), + ByteBuffer.wrap("c".getBytes(StandardCharsets.UTF_8)) + ); + InMemoryPublisher publisher = new InMemoryPublisher(data, 3); + + List received = new ArrayList<>(); + CountDownLatch completed = new CountDownLatch(1); + AtomicBoolean error = new AtomicBoolean(false); + + publisher.subscribe(new Subscriber() { + private Subscription subscription; + + @Override + public void onSubscribe(Subscription s) { + this.subscription = s; + s.request(1); + } + + @Override + public void onNext(ByteBuffer byteBuffer) { + received.add(byteBuffer); + // Re-entrant request — this is what ByteBufferStoringSubscriber does + subscription.request(1); + } + + @Override + public void onError(Throwable t) { + error.set(true); + completed.countDown(); + } + + @Override + public void onComplete() { + completed.countDown(); + } + }); + + assertThat(completed.await(5, TimeUnit.SECONDS)) + .as("Should complete without deadlocking") + .isTrue(); + assertThat(error.get()).isFalse(); + assertThat(received).hasSize(3); + } + + @Test + public void subscribe_secondSubscription_getsError() { + List data = Arrays.asList(ByteBuffer.wrap("x".getBytes(StandardCharsets.UTF_8))); + InMemoryPublisher publisher = new InMemoryPublisher(data, 1); + + publisher.subscribe(new Subscriber() { + @Override public void onSubscribe(Subscription s) { s.request(1); } + @Override public void onNext(ByteBuffer b) { } + @Override public void onError(Throwable t) { } + @Override public void onComplete() { } + }); + + AtomicBoolean gotError = new AtomicBoolean(false); + publisher.subscribe(new Subscriber() { + @Override public void onSubscribe(Subscription s) { } + @Override public void onNext(ByteBuffer b) { } + @Override public void onError(Throwable t) { gotError.set(true); } + @Override public void onComplete() { } + }); + + assertThat(gotError.get()).isTrue(); + } +} diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStage.java index 5156395c581f..16b78e7e3a35 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStage.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStage.java @@ -21,6 +21,7 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; import org.reactivestreams.Publisher; +import org.reactivestreams.Subscriber; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.async.AsyncRequestBody; @@ -38,6 +39,7 @@ import software.amazon.awssdk.core.signer.Signer; import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.async.SdkHttpContentPublisher; import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; @@ -154,6 +156,19 @@ private static void updateAsyncRequestBodyInContexts(RequestExecutionContext con Publisher signedPayload = optionalPayload.get(); if (signedPayload instanceof AsyncRequestBody) { newAsyncRequestBody = (AsyncRequestBody) signedPayload; + } else if (signedPayload instanceof SdkHttpContentPublisher) { + SdkHttpContentPublisher contentPublisher = (SdkHttpContentPublisher) signedPayload; + newAsyncRequestBody = new AsyncRequestBody() { + @Override + public Optional contentLength() { + return contentPublisher.contentLength(); + } + + @Override + public void subscribe(Subscriber s) { + contentPublisher.subscribe(s); + } + }; } else { newAsyncRequestBody = AsyncRequestBody.fromPublisher(signedPayload); } diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStageTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStageTest.java index 02a74672db4b..e1b461fdb4ea 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStageTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStageTest.java @@ -596,6 +596,52 @@ private RequestExecutionContext createContext(SelectedAuthScheme selec return context; } + @Test + public void execute_signerReturnsSdkHttpContentPublisher_preservesContentLength() throws Exception { + AsyncRequestBody asyncPayload = AsyncRequestBody.fromString("async request body"); + + SelectedAuthScheme selectedAuthScheme = new SelectedAuthScheme<>( + CompletableFuture.completedFuture(identity), + httpSigner, + AuthSchemeOption.builder() + .schemeId("my.auth#myAuth") + .build()); + RequestExecutionContext context = createContext(selectedAuthScheme, asyncPayload, null); + + SdkHttpRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); + + // Return a payload that implements SdkHttpContentPublisher (not AsyncRequestBody) + software.amazon.awssdk.http.async.SdkHttpContentPublisher contentPublisher = + new software.amazon.awssdk.http.async.SdkHttpContentPublisher() { + @Override + public java.util.Optional contentLength() { + return java.util.Optional.of(42L); + } + + @Override + public void subscribe(org.reactivestreams.Subscriber s) { + s.onSubscribe(new org.reactivestreams.Subscription() { + @Override public void request(long n) { s.onComplete(); } + @Override public void cancel() { } + }); + } + }; + + when(httpSigner.signAsync(ArgumentMatchers.>any())) + .thenReturn( + CompletableFuture.completedFuture(AsyncSignedRequest.builder() + .request(signedRequest) + .payload(contentPublisher) + .build())); + + SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); + stage.execute(request, context).join(); + + // Verify content length is preserved through the wrapping + assertThat(context.requestProvider()).isNotNull(); + assertThat(context.requestProvider().contentLength()).hasValue(42L); + } + private interface TestAsyncRequestBodySigner extends Signer, AsyncRequestBodySigner { } diff --git a/services/s3/src/it/java/software/amazon/awssdk/services/s3/PutObjectIntegrationTest.java b/services/s3/src/it/java/software/amazon/awssdk/services/s3/PutObjectIntegrationTest.java index dbe6681d027b..43e58f919e26 100644 --- a/services/s3/src/it/java/software/amazon/awssdk/services/s3/PutObjectIntegrationTest.java +++ b/services/s3/src/it/java/software/amazon/awssdk/services/s3/PutObjectIntegrationTest.java @@ -57,6 +57,7 @@ import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.http.ContentStreamProvider; +import software.amazon.awssdk.http.crt.AwsCrtAsyncHttpClient; import software.amazon.awssdk.metrics.MetricCollection; import software.amazon.awssdk.metrics.MetricPublisher; import software.amazon.awssdk.services.s3.model.GetObjectResponse; @@ -94,6 +95,22 @@ public static Stream s3Clients() { Arguments.of(crtClientBuilder().build())); } + @Test + public void crtAsyncClient_chunkedEncodingDisabled_shouldNotHang() throws Exception { + try (S3AsyncClient s3Async = S3AsyncClient.builder() + .region(DEFAULT_REGION) + .credentialsProvider(CREDENTIALS_PROVIDER_CHAIN) + .httpClientBuilder(AwsCrtAsyncHttpClient.builder()) + .serviceConfiguration(S3Configuration.builder() + .chunkedEncodingEnabled(false) + .build()) + .build()) { + s3Async.putObject(r -> r.bucket(BUCKET).key(SYNC_KEY), + AsyncRequestBody.fromString("TESTING")) + .get(30, java.util.concurrent.TimeUnit.SECONDS); + } + } + @Test public void objectInputStreamsAreClosed() { TestContentProvider provider = new TestContentProvider(CONTENT); From 678f840cb7ca97e4e2d0eba7f232e88222d34073 Mon Sep 17 00:00:00 2001 From: David Ho Date: Thu, 9 Jul 2026 20:53:37 -0700 Subject: [PATCH 2/8] Fix spotbugs --- .../http/auth/aws/internal/signer/io/ChecksumSubscriber.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriber.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriber.java index e7391e0b334c..3c0e98028b58 100644 --- a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriber.java +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriber.java @@ -40,7 +40,6 @@ public final class ChecksumSubscriber implements Subscriber { private volatile Subscription subscription; private final List bufferedPayload = new ArrayList<>(); - private long totalBytes = 0; public ChecksumSubscriber(Collection consumers) { this.checksums.addAll(consumers); @@ -86,7 +85,6 @@ private void updateChecksumsAndBuffer(ByteBuffer buffer) { buffer.get(copyBuffer); checksums.forEach(c -> c.update(copyBuffer, 0, remaining)); bufferedPayload.add(ByteBuffer.wrap(copyBuffer)); - totalBytes += remaining; } @@ -97,6 +95,7 @@ public void onError(Throwable throwable) { @Override public void onComplete() { + long totalBytes = bufferedPayload.stream().mapToLong(ByteBuffer::remaining).sum(); checksumming.complete(new InMemoryPublisher(bufferedPayload, totalBytes)); } From c1660c91fbc91c502b13bb4e702d6de29fac8b39 Mon Sep 17 00:00:00 2001 From: David Ho Date: Fri, 10 Jul 2026 00:16:16 -0700 Subject: [PATCH 3/8] Don't set Content-Length if Transfer-Encoding present --- .../http/pipeline/stages/MakeAsyncHttpRequestStage.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeAsyncHttpRequestStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeAsyncHttpRequestStage.java index d9845304457f..b7512a00dc37 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeAsyncHttpRequestStage.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeAsyncHttpRequestStage.java @@ -18,6 +18,7 @@ import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.SDK_HTTP_EXECUTION_ATTRIBUTES; import static software.amazon.awssdk.core.internal.http.timers.TimerUtils.resolveTimeoutInMillis; import static software.amazon.awssdk.http.Header.CONTENT_LENGTH; +import static software.amazon.awssdk.http.Header.TRANSFER_ENCODING; import java.nio.ByteBuffer; import java.time.Duration; @@ -246,6 +247,10 @@ private boolean shouldSetContentLength(SdkHttpFullRequest request, SdkHttpConten return false; } + if (request.firstMatchingHeader(TRANSFER_ENCODING).isPresent()) { + return false; + } + return Optional.ofNullable(requestProvider).flatMap(SdkHttpContentPublisher::contentLength).isPresent(); } From b214b78823ed1dbefe909c409f176ca0e729d630 Mon Sep 17 00:00:00 2001 From: David Ho Date: Fri, 10 Jul 2026 10:28:06 -0700 Subject: [PATCH 4/8] Changelog --- .changes/next-release/bugfix-AmazonS3-b7503bb.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changes/next-release/bugfix-AmazonS3-b7503bb.json diff --git a/.changes/next-release/bugfix-AmazonS3-b7503bb.json b/.changes/next-release/bugfix-AmazonS3-b7503bb.json new file mode 100644 index 000000000000..453439d6bd81 --- /dev/null +++ b/.changes/next-release/bugfix-AmazonS3-b7503bb.json @@ -0,0 +1,6 @@ +{ + "type": "bugfix", + "category": "Amazon S3", + "contributor": "", + "description": "Fix bug where S3 PutObject would hang indefinitely when using AwsCrtAsyncHttpClient with chunkedEncodingEnabled(false)" +} From c9db5d756f51bb0ec01e163f6c1c39988c677a9e Mon Sep 17 00:00:00 2001 From: David Ho Date: Fri, 10 Jul 2026 11:25:14 -0700 Subject: [PATCH 5/8] Add tests and update javadoc --- .../internal/signer/io/InMemoryPublisher.java | 3 +- .../signer/io/InMemoryPublisherTckTest.java | 55 +++++++++++++++++++ .../stages/MakeAsyncHttpRequestStageTest.java | 44 +++++++++++++++ .../services/s3/PutObjectIntegrationTest.java | 8 ++- 4 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisherTckTest.java diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisher.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisher.java index 892467459d0a..8437cc833988 100644 --- a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisher.java +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisher.java @@ -28,7 +28,8 @@ import software.amazon.awssdk.utils.Validate; /** - * Temporarily used for buffering all data into memory. + * A content-length-aware publisher that replays buffered data. Used by {@link ChecksumSubscriber} to replay the payload after + * checksumming. */ @SdkInternalApi public class InMemoryPublisher implements SdkHttpContentPublisher { diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisherTckTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisherTckTest.java new file mode 100644 index 000000000000..69f9065e7a80 --- /dev/null +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisherTckTest.java @@ -0,0 +1,55 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.http.auth.aws.internal.signer.io; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import org.reactivestreams.Publisher; +import org.reactivestreams.tck.PublisherVerification; +import org.reactivestreams.tck.TestEnvironment; + +/** + * TCK verification test for {@link InMemoryPublisher}. + */ +public class InMemoryPublisherTckTest extends PublisherVerification { + + public InMemoryPublisherTckTest() { + super(new TestEnvironment(1000)); + } + + @Override + public Publisher createPublisher(long elements) { + List data = new ArrayList<>(); + long totalLength = 0; + for (long i = 0; i < elements; i++) { + byte[] content = {(byte) (i % 127)}; + data.add(ByteBuffer.wrap(content)); + totalLength += content.length; + } + return new InMemoryPublisher(data, totalLength); + } + + @Override + public Publisher createFailedPublisher() { + return null; + } + + @Override + public long maxElementsFromPublisher() { + return 1024L; + } +} diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeAsyncHttpRequestStageTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeAsyncHttpRequestStageTest.java index df8126db2343..96723fb76d8a 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeAsyncHttpRequestStageTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeAsyncHttpRequestStageTest.java @@ -46,6 +46,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.client.config.SdkAdvancedAsyncClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.http.ExecutionContext; @@ -56,6 +57,7 @@ import software.amazon.awssdk.core.internal.http.TransformingAsyncResponseHandler; import software.amazon.awssdk.core.internal.http.timers.ClientExecutionAndRequestTimerTestUtils; import software.amazon.awssdk.core.internal.util.AsyncResponseHandlerTestUtils; +import software.amazon.awssdk.http.Header; import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpMethod; import software.amazon.awssdk.http.async.AsyncExecuteRequest; @@ -237,6 +239,48 @@ public void execute_handlerFutureCompletedExceptionally_doesNotAttemptSynchronou } } + @Test + public void execute_requestHasTransferEncodingHeader_doesNotAddContentLength() { + stage = new MakeAsyncHttpRequestStage<>( + combinedAsyncResponseHandler(AsyncResponseHandlerTestUtils.noOpResponseHandler(), + AsyncResponseHandlerTestUtils.noOpResponseHandler()), + clientDependencies(null)); + + SdkHttpFullRequest sdkHttpRequest = SdkHttpFullRequest.builder() + .method(SdkHttpMethod.POST) + .host("service.us-east-1.amazonaws.com") + .protocol("https") + .putHeader(Header.TRANSFER_ENCODING, "chunked") + .build(); + + AsyncRequestBody requestBody = AsyncRequestBody.fromString("hello world"); + + ExecutionContext executionContext = ExecutionContext.builder() + .executionAttributes(new ExecutionAttributes()) + .build(); + + RequestExecutionContext context = RequestExecutionContext.builder() + .originalRequest(ValidSdkObjects.sdkRequest()) + .executionContext(executionContext) + .requestProvider(requestBody) + .build(); + + CompletableFuture requestFuture = CompletableFuture.completedFuture(sdkHttpRequest); + + try { + stage.execute(requestFuture, context); + } catch (Exception e) { + // ignored, we only care about the request sent to the HTTP client + } finally { + ArgumentCaptor httpRequestCaptor = ArgumentCaptor.forClass(AsyncExecuteRequest.class); + verify(sdkAsyncHttpClient).execute(httpRequestCaptor.capture()); + + SdkHttpFullRequest capturedRequest = (SdkHttpFullRequest) httpRequestCaptor.getValue().request(); + assertThat(capturedRequest.firstMatchingHeader(Header.CONTENT_LENGTH)).isNotPresent(); + assertThat(capturedRequest.firstMatchingHeader(Header.TRANSFER_ENCODING)).hasValue("chunked"); + } + } + private HttpClientDependencies clientDependencies(Duration timeout) { SdkClientConfiguration configuration = SdkClientConfiguration.builder() .option(SdkAdvancedAsyncClientOption.FUTURE_COMPLETION_EXECUTOR, Runnable::run) diff --git a/services/s3/src/it/java/software/amazon/awssdk/services/s3/PutObjectIntegrationTest.java b/services/s3/src/it/java/software/amazon/awssdk/services/s3/PutObjectIntegrationTest.java index 43e58f919e26..875f03353116 100644 --- a/services/s3/src/it/java/software/amazon/awssdk/services/s3/PutObjectIntegrationTest.java +++ b/services/s3/src/it/java/software/amazon/awssdk/services/s3/PutObjectIntegrationTest.java @@ -36,6 +36,7 @@ import java.util.List; import java.util.Random; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; import org.apache.commons.lang3.RandomStringUtils; @@ -105,9 +106,10 @@ public void crtAsyncClient_chunkedEncodingDisabled_shouldNotHang() throws Except .chunkedEncodingEnabled(false) .build()) .build()) { - s3Async.putObject(r -> r.bucket(BUCKET).key(SYNC_KEY), - AsyncRequestBody.fromString("TESTING")) - .get(30, java.util.concurrent.TimeUnit.SECONDS); + PutObjectResponse response = s3Async.putObject(r -> r.bucket(BUCKET).key(SYNC_KEY), + AsyncRequestBody.fromString("TESTING")) + .get(30, TimeUnit.SECONDS); + assertThat(response.eTag()).isNotNull(); } } From 4f397af1d9e3922ec4796679b60b22b0b3b63169 Mon Sep 17 00:00:00 2001 From: David Ho Date: Fri, 10 Jul 2026 12:27:57 -0700 Subject: [PATCH 6/8] Validate non-positive request(n) per Reactive Streams spec in InMemoryPublisher --- .../http/auth/aws/internal/signer/io/InMemoryPublisher.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisher.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisher.java index 8437cc833988..295a90de1b73 100644 --- a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisher.java +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisher.java @@ -65,6 +65,12 @@ public void subscribe(Subscriber s) { @Override public void request(long n) { + if (n <= 0) { + finish(() -> s.onError( + new IllegalArgumentException("n > 0 required but it was " + n))); + return; + } + if (done.get()) { return; } From b6347419e433306db33c3238dc85a0435097cf72 Mon Sep 17 00:00:00 2001 From: David Ho Date: Mon, 13 Jul 2026 12:43:52 -0700 Subject: [PATCH 7/8] Scope PR to just hanging fix --- .../internal/signer/FlexibleChecksummer.java | 7 ++- .../signer/io/ChecksumSubscriber.java | 3 +- .../internal/signer/io/InMemoryPublisher.java | 17 ++----- .../signer/io/InMemoryPublisherTckTest.java | 7 +-- .../signer/io/InMemoryPublisherTest.java | 46 +++++++++++++------ .../pipeline/stages/AsyncSigningStage.java | 15 ------ .../stages/MakeAsyncHttpRequestStage.java | 5 -- 7 files changed, 42 insertions(+), 58 deletions(-) diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/FlexibleChecksummer.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/FlexibleChecksummer.java index 2a0108426ef7..6c1539462788 100644 --- a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/FlexibleChecksummer.java +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/FlexibleChecksummer.java @@ -81,10 +81,9 @@ public CompletableFuture> checksum(Publisher p } payload.subscribe(checksumSubscriber); - return checksumSubscriber.completeFuture().thenApply(checksummedPayload -> { - addChecksums(request); - return checksummedPayload; - }); + CompletableFuture> result = checksumSubscriber.completeFuture(); + result.thenRun(() -> addChecksums(request)); + return result; } private void addChecksums(SdkHttpRequest.Builder request) { diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriber.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriber.java index 3c0e98028b58..3e1a878a1d0f 100644 --- a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriber.java +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/ChecksumSubscriber.java @@ -95,8 +95,7 @@ public void onError(Throwable throwable) { @Override public void onComplete() { - long totalBytes = bufferedPayload.stream().mapToLong(ByteBuffer::remaining).sum(); - checksumming.complete(new InMemoryPublisher(bufferedPayload, totalBytes)); + checksumming.complete(new InMemoryPublisher(bufferedPayload)); } public CompletableFuture> completeFuture() { diff --git a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisher.java b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisher.java index 295a90de1b73..5a7fecd9c96b 100644 --- a/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisher.java +++ b/core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisher.java @@ -18,33 +18,24 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import software.amazon.awssdk.annotations.SdkInternalApi; -import software.amazon.awssdk.http.async.SdkHttpContentPublisher; import software.amazon.awssdk.utils.Validate; /** - * A content-length-aware publisher that replays buffered data. Used by {@link ChecksumSubscriber} to replay the payload after - * checksumming. + * Temporarily used for buffering all data into memory. */ @SdkInternalApi -public class InMemoryPublisher implements SdkHttpContentPublisher { +public class InMemoryPublisher implements Publisher { private final AtomicBoolean subscribed = new AtomicBoolean(false); private final List data; - private final long length; - public InMemoryPublisher(List data, long length) { + public InMemoryPublisher(List data) { this.data = new ArrayList<>(Validate.noNullElements(data, "Data must not contain null elements.")); - this.length = length; - } - - @Override - public Optional contentLength() { - return Optional.of(length); } @Override diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisherTckTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisherTckTest.java index 69f9065e7a80..922a1d0e551a 100644 --- a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisherTckTest.java +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisherTckTest.java @@ -34,13 +34,10 @@ public InMemoryPublisherTckTest() { @Override public Publisher createPublisher(long elements) { List data = new ArrayList<>(); - long totalLength = 0; for (long i = 0; i < elements; i++) { - byte[] content = {(byte) (i % 127)}; - data.add(ByteBuffer.wrap(content)); - totalLength += content.length; + data.add(ByteBuffer.wrap(new byte[]{(byte) (i % 127)})); } - return new InMemoryPublisher(data, totalLength); + return new InMemoryPublisher(data); } @Override diff --git a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisherTest.java b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisherTest.java index fbe74b48dc67..9078bf29c6fa 100644 --- a/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisherTest.java +++ b/core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/io/InMemoryPublisherTest.java @@ -31,22 +31,11 @@ public class InMemoryPublisherTest { - @Test - public void contentLength_returnsCorrectValue() { - List data = Arrays.asList( - ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8)), - ByteBuffer.wrap(" world".getBytes(StandardCharsets.UTF_8)) - ); - InMemoryPublisher publisher = new InMemoryPublisher(data, 11); - - assertThat(publisher.contentLength()).hasValue(11L); - } - @Test public void subscribe_deliversAllData() throws Exception { byte[] bytes = "test data".getBytes(StandardCharsets.UTF_8); List data = Arrays.asList(ByteBuffer.wrap(bytes)); - InMemoryPublisher publisher = new InMemoryPublisher(data, bytes.length); + InMemoryPublisher publisher = new InMemoryPublisher(data); List received = new ArrayList<>(); CountDownLatch completed = new CountDownLatch(1); @@ -83,7 +72,7 @@ public void subscribe_reEntrantRequestFromOnNext_doesNotDeadlock() throws Except ByteBuffer.wrap("b".getBytes(StandardCharsets.UTF_8)), ByteBuffer.wrap("c".getBytes(StandardCharsets.UTF_8)) ); - InMemoryPublisher publisher = new InMemoryPublisher(data, 3); + InMemoryPublisher publisher = new InMemoryPublisher(data); List received = new ArrayList<>(); CountDownLatch completed = new CountDownLatch(1); @@ -127,7 +116,7 @@ public void onComplete() { @Test public void subscribe_secondSubscription_getsError() { List data = Arrays.asList(ByteBuffer.wrap("x".getBytes(StandardCharsets.UTF_8))); - InMemoryPublisher publisher = new InMemoryPublisher(data, 1); + InMemoryPublisher publisher = new InMemoryPublisher(data); publisher.subscribe(new Subscriber() { @Override public void onSubscribe(Subscription s) { s.request(1); } @@ -146,4 +135,33 @@ public void subscribe_secondSubscription_getsError() { assertThat(gotError.get()).isTrue(); } + + @Test + public void subscribe_requestNonPositive_signalsError() throws Exception { + List data = Arrays.asList(ByteBuffer.wrap("x".getBytes(StandardCharsets.UTF_8))); + InMemoryPublisher publisher = new InMemoryPublisher(data); + + AtomicBoolean gotError = new AtomicBoolean(false); + CountDownLatch completed = new CountDownLatch(1); + + publisher.subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(0); + } + + @Override public void onNext(ByteBuffer b) { } + + @Override + public void onError(Throwable t) { + gotError.set(t instanceof IllegalArgumentException); + completed.countDown(); + } + + @Override public void onComplete() { completed.countDown(); } + }); + + assertThat(completed.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(gotError.get()).isTrue(); + } } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStage.java index 16b78e7e3a35..5156395c581f 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStage.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStage.java @@ -21,7 +21,6 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.async.AsyncRequestBody; @@ -39,7 +38,6 @@ import software.amazon.awssdk.core.signer.Signer; import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpRequest; -import software.amazon.awssdk.http.async.SdkHttpContentPublisher; import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption; import software.amazon.awssdk.http.auth.spi.signer.AsyncSignRequest; import software.amazon.awssdk.http.auth.spi.signer.AsyncSignedRequest; @@ -156,19 +154,6 @@ private static void updateAsyncRequestBodyInContexts(RequestExecutionContext con Publisher signedPayload = optionalPayload.get(); if (signedPayload instanceof AsyncRequestBody) { newAsyncRequestBody = (AsyncRequestBody) signedPayload; - } else if (signedPayload instanceof SdkHttpContentPublisher) { - SdkHttpContentPublisher contentPublisher = (SdkHttpContentPublisher) signedPayload; - newAsyncRequestBody = new AsyncRequestBody() { - @Override - public Optional contentLength() { - return contentPublisher.contentLength(); - } - - @Override - public void subscribe(Subscriber s) { - contentPublisher.subscribe(s); - } - }; } else { newAsyncRequestBody = AsyncRequestBody.fromPublisher(signedPayload); } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeAsyncHttpRequestStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeAsyncHttpRequestStage.java index b7512a00dc37..d9845304457f 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeAsyncHttpRequestStage.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeAsyncHttpRequestStage.java @@ -18,7 +18,6 @@ import static software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute.SDK_HTTP_EXECUTION_ATTRIBUTES; import static software.amazon.awssdk.core.internal.http.timers.TimerUtils.resolveTimeoutInMillis; import static software.amazon.awssdk.http.Header.CONTENT_LENGTH; -import static software.amazon.awssdk.http.Header.TRANSFER_ENCODING; import java.nio.ByteBuffer; import java.time.Duration; @@ -247,10 +246,6 @@ private boolean shouldSetContentLength(SdkHttpFullRequest request, SdkHttpConten return false; } - if (request.firstMatchingHeader(TRANSFER_ENCODING).isPresent()) { - return false; - } - return Optional.ofNullable(requestProvider).flatMap(SdkHttpContentPublisher::contentLength).isPresent(); } From b58a6ff875b5b494f26506bd83eb581217be383a Mon Sep 17 00:00:00 2001 From: David Ho Date: Mon, 13 Jul 2026 12:48:56 -0700 Subject: [PATCH 8/8] Scope PR to just hanging fix --- .../stages/AsyncSigningStageTest.java | 46 ------------------- .../stages/MakeAsyncHttpRequestStageTest.java | 44 ------------------ 2 files changed, 90 deletions(-) diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStageTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStageTest.java index e1b461fdb4ea..02a74672db4b 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStageTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/AsyncSigningStageTest.java @@ -596,52 +596,6 @@ private RequestExecutionContext createContext(SelectedAuthScheme selec return context; } - @Test - public void execute_signerReturnsSdkHttpContentPublisher_preservesContentLength() throws Exception { - AsyncRequestBody asyncPayload = AsyncRequestBody.fromString("async request body"); - - SelectedAuthScheme selectedAuthScheme = new SelectedAuthScheme<>( - CompletableFuture.completedFuture(identity), - httpSigner, - AuthSchemeOption.builder() - .schemeId("my.auth#myAuth") - .build()); - RequestExecutionContext context = createContext(selectedAuthScheme, asyncPayload, null); - - SdkHttpRequest signedRequest = ValidSdkObjects.sdkHttpFullRequest().build(); - - // Return a payload that implements SdkHttpContentPublisher (not AsyncRequestBody) - software.amazon.awssdk.http.async.SdkHttpContentPublisher contentPublisher = - new software.amazon.awssdk.http.async.SdkHttpContentPublisher() { - @Override - public java.util.Optional contentLength() { - return java.util.Optional.of(42L); - } - - @Override - public void subscribe(org.reactivestreams.Subscriber s) { - s.onSubscribe(new org.reactivestreams.Subscription() { - @Override public void request(long n) { s.onComplete(); } - @Override public void cancel() { } - }); - } - }; - - when(httpSigner.signAsync(ArgumentMatchers.>any())) - .thenReturn( - CompletableFuture.completedFuture(AsyncSignedRequest.builder() - .request(signedRequest) - .payload(contentPublisher) - .build())); - - SdkHttpFullRequest request = ValidSdkObjects.sdkHttpFullRequest().build(); - stage.execute(request, context).join(); - - // Verify content length is preserved through the wrapping - assertThat(context.requestProvider()).isNotNull(); - assertThat(context.requestProvider().contentLength()).hasValue(42L); - } - private interface TestAsyncRequestBodySigner extends Signer, AsyncRequestBodySigner { } diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeAsyncHttpRequestStageTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeAsyncHttpRequestStageTest.java index 96723fb76d8a..df8126db2343 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeAsyncHttpRequestStageTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/MakeAsyncHttpRequestStageTest.java @@ -46,7 +46,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.client.config.SdkAdvancedAsyncClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.http.ExecutionContext; @@ -57,7 +56,6 @@ import software.amazon.awssdk.core.internal.http.TransformingAsyncResponseHandler; import software.amazon.awssdk.core.internal.http.timers.ClientExecutionAndRequestTimerTestUtils; import software.amazon.awssdk.core.internal.util.AsyncResponseHandlerTestUtils; -import software.amazon.awssdk.http.Header; import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpMethod; import software.amazon.awssdk.http.async.AsyncExecuteRequest; @@ -239,48 +237,6 @@ public void execute_handlerFutureCompletedExceptionally_doesNotAttemptSynchronou } } - @Test - public void execute_requestHasTransferEncodingHeader_doesNotAddContentLength() { - stage = new MakeAsyncHttpRequestStage<>( - combinedAsyncResponseHandler(AsyncResponseHandlerTestUtils.noOpResponseHandler(), - AsyncResponseHandlerTestUtils.noOpResponseHandler()), - clientDependencies(null)); - - SdkHttpFullRequest sdkHttpRequest = SdkHttpFullRequest.builder() - .method(SdkHttpMethod.POST) - .host("service.us-east-1.amazonaws.com") - .protocol("https") - .putHeader(Header.TRANSFER_ENCODING, "chunked") - .build(); - - AsyncRequestBody requestBody = AsyncRequestBody.fromString("hello world"); - - ExecutionContext executionContext = ExecutionContext.builder() - .executionAttributes(new ExecutionAttributes()) - .build(); - - RequestExecutionContext context = RequestExecutionContext.builder() - .originalRequest(ValidSdkObjects.sdkRequest()) - .executionContext(executionContext) - .requestProvider(requestBody) - .build(); - - CompletableFuture requestFuture = CompletableFuture.completedFuture(sdkHttpRequest); - - try { - stage.execute(requestFuture, context); - } catch (Exception e) { - // ignored, we only care about the request sent to the HTTP client - } finally { - ArgumentCaptor httpRequestCaptor = ArgumentCaptor.forClass(AsyncExecuteRequest.class); - verify(sdkAsyncHttpClient).execute(httpRequestCaptor.capture()); - - SdkHttpFullRequest capturedRequest = (SdkHttpFullRequest) httpRequestCaptor.getValue().request(); - assertThat(capturedRequest.firstMatchingHeader(Header.CONTENT_LENGTH)).isNotPresent(); - assertThat(capturedRequest.firstMatchingHeader(Header.TRANSFER_ENCODING)).hasValue("chunked"); - } - } - private HttpClientDependencies clientDependencies(Duration timeout) { SdkClientConfiguration configuration = SdkClientConfiguration.builder() .option(SdkAdvancedAsyncClientOption.FUTURE_COMPLETION_EXECUTOR, Runnable::run)