diff --git a/.changes/next-release/feature-AmazonS3-71a2dda.json b/.changes/next-release/feature-AmazonS3-71a2dda.json new file mode 100644 index 000000000000..5b93a711daea --- /dev/null +++ b/.changes/next-release/feature-AmazonS3-71a2dda.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "Amazon S3", + "contributor": "", + "description": "Add support for the following in the CRT S3 client:\n\n - Enabling/disabling accelerate endpoints\n - Using pathstyle addressing" +} diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java index 5d83e87bee13..71cc15362049 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3CrtAsyncClientBuilder.java @@ -176,6 +176,17 @@ default S3CrtAsyncClientBuilder httpConfiguration(Consumer copyObject(CopyObjectRequest copyOb } private static S3AsyncClient initializeS3AsyncClient(DefaultS3CrtClientBuilder builder) { + ClientOverrideConfiguration.Builder overrideConfigurationBuilder = + ClientOverrideConfiguration.builder() + // Disable checksum, retry policy and signer because they are handled in crt + .putAdvancedOption(SdkAdvancedClientOption.SIGNER, new NoOpSigner()) + .putExecutionAttribute(SdkExecutionAttribute.HTTP_RESPONSE_CHECKSUM_VALIDATION, + ChecksumValidation.FORCE_SKIP) + .retryPolicy(RetryPolicy.none()) + .addExecutionInterceptor(new ValidateRequestInterceptor()) + .addExecutionInterceptor(new AttachHttpAttributesExecutionInterceptor()); + + if (builder.executionInterceptors != null) { + builder.executionInterceptors.forEach(overrideConfigurationBuilder::addExecutionInterceptor); + } + return S3AsyncClient.builder() - // Disable checksum, retry policy and signer because they are handled in crt + // Disable checksum, it is handled in CRT .serviceConfiguration(S3Configuration.builder() .checksumValidationEnabled(false) .build()) .region(builder.region) .endpointOverride(builder.endpointOverride) .credentialsProvider(builder.credentialsProvider) - .overrideConfiguration(o -> o.putAdvancedOption(SdkAdvancedClientOption.SIGNER, - new NoOpSigner()) - .putExecutionAttribute( - SdkExecutionAttribute.HTTP_RESPONSE_CHECKSUM_VALIDATION, - ChecksumValidation.FORCE_SKIP) - .retryPolicy(RetryPolicy.none()) - .addExecutionInterceptor(new ValidateRequestInterceptor()) - .addExecutionInterceptor(new AttachHttpAttributesExecutionInterceptor())) + .overrideConfiguration(overrideConfigurationBuilder.build()) + .accelerate(builder.accelerate) + .forcePathStyle(builder.forcePathStyle) .httpClientBuilder(initializeS3CrtAsyncHttpClient(builder)) .build(); } @@ -123,6 +136,10 @@ public static final class DefaultS3CrtClientBuilder implements S3CrtAsyncClientB private URI endpointOverride; private Boolean checksumValidationEnabled; private S3CrtHttpConfiguration httpConfiguration; + private Boolean accelerate; + private Boolean forcePathStyle; + + private List executionInterceptors; public AwsCredentialsProvider credentialsProvider() { return credentialsProvider; @@ -206,6 +223,27 @@ public S3CrtAsyncClientBuilder httpConfiguration(S3CrtHttpConfiguration configur return this; } + @Override + public S3CrtAsyncClientBuilder accelerate(Boolean accelerate) { + this.accelerate = accelerate; + return this; + } + + @Override + public S3CrtAsyncClientBuilder forcePathStyle(Boolean forcePathStyle) { + this.forcePathStyle = forcePathStyle; + return this; + } + + @SdkTestInternalApi + S3CrtAsyncClientBuilder addExecutionInterceptor(ExecutionInterceptor executionInterceptor) { + if (executionInterceptors == null) { + this.executionInterceptors = new ArrayList<>(); + } + executionInterceptors.add(executionInterceptor); + return this; + } + @Override public S3CrtAsyncClient build() { return new DefaultS3CrtAsyncClient(this); diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClientTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClientTest.java index 51a6234150ac..5c0da7b22f9f 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClientTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/crt/DefaultS3CrtAsyncClientTest.java @@ -15,15 +15,23 @@ package software.amazon.awssdk.services.s3.internal.crt; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import software.amazon.awssdk.auth.signer.AwsS3V4Signer; import software.amazon.awssdk.core.async.AsyncRequestBody; import software.amazon.awssdk.core.async.AsyncResponseTransformer; +import software.amazon.awssdk.core.interceptor.Context; +import software.amazon.awssdk.core.interceptor.ExecutionAttributes; +import software.amazon.awssdk.core.interceptor.ExecutionInterceptor; +import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute; import software.amazon.awssdk.services.s3.S3AsyncClient; +import software.amazon.awssdk.services.s3.endpoints.S3ClientContextParams; +import software.amazon.awssdk.utils.AttributeMap; class DefaultS3CrtAsyncClientTest { @@ -40,6 +48,37 @@ void requestSignerOverrideProvided_shouldThrowException() { } } + @Test + void clientContextParamsSetOnBuilder_propagatedToInterceptors() { + AtomicReference clientContexParams = new AtomicReference<>(); + + ExecutionInterceptor paramsCaptor = new ExecutionInterceptor() { + @Override + public void beforeExecution(Context.BeforeExecution context, ExecutionAttributes executionAttributes) { + clientContexParams.set(executionAttributes.getAttribute(SdkInternalExecutionAttribute.CLIENT_CONTEXT_PARAMS)); + throw new RuntimeException("BOOM"); + } + }; + + DefaultS3CrtAsyncClient.DefaultS3CrtClientBuilder builder = + (DefaultS3CrtAsyncClient.DefaultS3CrtClientBuilder) S3CrtAsyncClient.builder(); + + builder.addExecutionInterceptor(paramsCaptor); + + try (S3AsyncClient s3AsyncClient = builder.accelerate(false) + .forcePathStyle(true) + .build()) { + + assertThatThrownBy(s3AsyncClient.listBuckets()::join).hasMessageContaining("BOOM"); + AttributeMap attributeMap = clientContexParams.get(); + + assertThat(attributeMap.get(S3ClientContextParams.ACCELERATE)).isFalse(); + assertThat(attributeMap.get(S3ClientContextParams.FORCE_PATH_STYLE)).isTrue(); + assertThat(attributeMap.get(S3ClientContextParams.USE_ARN_REGION)).isFalse(); + assertThat(attributeMap.get(S3ClientContextParams.DISABLE_MULTI_REGION_ACCESS_POINTS)).isFalse(); + } + } + @ParameterizedTest @ValueSource(longs = {0, -1L}) void invalidConfig_shouldThrowException(long value) {