From a5b286866dd27576497618e2823508387a75884e Mon Sep 17 00:00:00 2001 From: Saranya Somepalli Date: Tue, 7 Jul 2026 15:24:15 -0700 Subject: [PATCH 1/3] Remove ServiceMetadata usage from DefaultS3Presigner, S3Utilities, and DefaultPollyPresigner --- .../presigner/DefaultPollyPresigner.java | 36 ++++++++------ .../awssdk/services/s3/S3Utilities.java | 30 +++++++----- .../internal/signing/DefaultS3Presigner.java | 49 +++++++++++-------- .../awssdk/services/s3/InvalidRegionTest.java | 11 ++--- 4 files changed, 72 insertions(+), 54 deletions(-) diff --git a/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java b/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java index 0ed43bf8df63..916946b71125 100644 --- a/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java +++ b/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java @@ -26,6 +26,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.function.Supplier; @@ -70,6 +71,7 @@ import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain; import software.amazon.awssdk.services.polly.auth.scheme.PollyAuthSchemeProvider; +import software.amazon.awssdk.services.polly.endpoints.PollyEndpointProvider; import software.amazon.awssdk.services.polly.internal.presigner.model.transform.SynthesizeSpeechRequestMarshaller; import software.amazon.awssdk.services.polly.model.PollyRequest; import software.amazon.awssdk.services.polly.presigner.PollyPresigner; @@ -341,20 +343,26 @@ private void applyEndpoint(SdkHttpFullRequest.Builder httpRequestBuilder) { } private URI resolveEndpoint() { - return AwsClientEndpointProvider.builder() - .clientEndpointOverride(endpointOverride) - .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_POLLY") - .serviceEndpointOverrideSystemProperty("aws.endpointUrlPolly") - .serviceProfileProperty("polly") - .serviceEndpointPrefix(SERVICE_NAME) - .defaultProtocol("https") - .region(region) - .profileFile(profileFile) - .profileName(profileName) - .dualstackEnabled(dualstackEnabled) - .fipsEnabled(fipsEnabled) - .build() - .clientEndpoint(); + // If user configured an endpoint override or one is set via environment/profile, use it. + Optional overrideEndpoint = AwsClientEndpointProvider.builder() + .clientEndpointOverride(endpointOverride) + .serviceEndpointOverrideEnvironmentVariable( + "AWS_ENDPOINT_URL_POLLY") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlPolly") + .serviceProfileProperty("polly") + .profileFile(profileFile) + .profileName(profileName) + .resolveFromOverrides(); + + // Resolve endpoint using the service's Endpoints 2.0 provider. Unlike S3, the Polly presigner + // does not have an endpoint resolution interceptor chain — this endpoint becomes the final + // presigned URL host, so it must be accurate. + return overrideEndpoint.orElseGet(() -> CompletableFutureUtils.joinLikeSync( + PollyEndpointProvider.defaultProvider() + .resolveEndpoint(p -> p.region(region) + .useDualStack(dualstackEnabled) + .useFips(fipsEnabled)) + ).url()); } public static class BuilderImpl implements PollyPresigner.Builder { diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java index cb3064798a99..ee7ba7a27000 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java @@ -426,21 +426,25 @@ private Region resolveRegionForGetUrl(GetUrlRequest getUrlRequest) { /** * If endpoint is not present, construct a default endpoint using the region information. + * The endpoint resolved here is only used for initial request marshalling — it gets completely replaced + * by S3ResolveEndpointInterceptor during the getUrl flow. */ private ClientEndpointProvider clientEndpointProvider(URI overrideEndpoint, Region region) { - return AwsClientEndpointProvider.builder() - .clientEndpointOverride(overrideEndpoint) - .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_S3") - .serviceEndpointOverrideSystemProperty("aws.endpointUrlS3") - .serviceProfileProperty("s3") - .serviceEndpointPrefix(SERVICE_NAME) - .defaultProtocol("https") - .region(region) - .profileFile(profileFile) - .profileName(profileName) - .dualstackEnabled(s3Configuration.dualstackEnabled()) - .fipsEnabled(fipsEnabled) - .build(); + // First check if there's an endpoint override from the user or environment. + Optional resolvedOverride = AwsClientEndpointProvider.builder() + .clientEndpointOverride(overrideEndpoint) + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_S3") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlS3") + .serviceProfileProperty("s3") + .profileFile(profileFile) + .profileName(profileName) + .resolveFromOverrides(); + + // If an override is configured, use it (marked as overridden). Otherwise, use a localhost placeholder + // marked as NOT overridden — S3ResolveEndpointInterceptor will replace it with the real endpoint. + return resolvedOverride + .map(uri -> ClientEndpointProvider.create(uri, true)) + .orElseGet(() -> ClientEndpointProvider.create(URI.create("https://localhost"), false)); } private URI getEndpointOverride(GetUrlRequest request) { diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java index 48e09c49a6ba..e5dd56112909 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java @@ -21,6 +21,7 @@ import static software.amazon.awssdk.utils.CollectionUtils.mergeLists; import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely; +import java.net.URI; import java.time.Clock; import java.time.Duration; import java.time.Instant; @@ -45,6 +46,7 @@ import software.amazon.awssdk.awscore.internal.defaultsmode.DefaultsModeConfiguration; import software.amazon.awssdk.awscore.presigner.PresignRequest; import software.amazon.awssdk.awscore.presigner.PresignedRequest; +import software.amazon.awssdk.core.ClientEndpointProvider; import software.amazon.awssdk.core.ClientType; import software.amazon.awssdk.core.RequestOverrideConfiguration; import software.amazon.awssdk.core.SdkBytes; @@ -53,6 +55,7 @@ import software.amazon.awssdk.core.client.builder.SdkDefaultClientBuilder; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; +import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.http.ExecutionContext; import software.amazon.awssdk.core.interceptor.ClasspathInterceptorChainFactory; import software.amazon.awssdk.core.interceptor.ExecutionAttributes; @@ -251,28 +254,34 @@ private List initializeInterceptors() { * Copied from {@link AwsDefaultClientBuilder}. */ private SdkClientConfiguration createClientConfiguration() { - AwsClientEndpointProvider endpointProvider = - AwsClientEndpointProvider.builder() - .clientEndpointOverride(endpointOverride()) - .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_S3") - .serviceEndpointOverrideSystemProperty("aws.endpointUrlS3") - .serviceProfileProperty("s3") - .serviceEndpointPrefix(SERVICE_NAME) - .defaultProtocol("https") - .region(region()) - .profileFile(profileFileSupplier()) - .profileName(profileName()) - .dualstackEnabled(serviceConfiguration.dualstackEnabled()) - .fipsEnabled(fipsEnabled()) - .build(); - - // Make sure the endpoint resolver can actually resolve an endpoint, so that we fail now instead of - // when a request is made. - endpointProvider.clientEndpoint(); + // First check if there's an endpoint override from the user or environment. + Optional overrideEndpoint = AwsClientEndpointProvider.builder() + .clientEndpointOverride(endpointOverride()) + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_S3") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlS3") + .serviceProfileProperty("s3") + .profileFile(profileFileSupplier()) + .profileName(profileName()) + .resolveFromOverrides(); + + // If an override is configured, use it (marked as overridden). Otherwise, use a localhost placeholder + // marked as NOT overridden — S3ResolveEndpointInterceptor will replace it with the real endpoint + // at presigning time using S3EndpointProvider with the full request context. + ClientEndpointProvider endpointProvider; + if (overrideEndpoint.isPresent()) { + endpointProvider = ClientEndpointProvider.create(overrideEndpoint.get(), true); + } else { + // Validate region at construction time to fail fast for invalid regions (e.g., US_EAST_1 with underscores). + URI testEndpoint = URI.create("https://s3." + region().id() + ".amazonaws.com"); + if (testEndpoint.getHost() == null) { + throw SdkClientException.create("Configured region (" + region() + ") and tags ([]) resulted in an invalid URI: " + + testEndpoint + ". This is usually caused by an invalid region configuration."); + } + endpointProvider = ClientEndpointProvider.create(URI.create("https://localhost"), false); + } return SdkClientConfiguration.builder() - .option(SdkClientOption.CLIENT_ENDPOINT_PROVIDER, - endpointProvider) + .option(SdkClientOption.CLIENT_ENDPOINT_PROVIDER, endpointProvider) .build(); } diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/InvalidRegionTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/InvalidRegionTest.java index 4eef66bf2e22..7831a78c17eb 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/InvalidRegionTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/InvalidRegionTest.java @@ -32,9 +32,7 @@ public void invalidS3UtilitiesRegionAtClientGivesHelpfulMessage() { assertThatThrownBy(() -> utilities.getUrl(r -> r.bucket("foo").key("bar"))) .isInstanceOf(SdkClientException.class) - .hasMessageContaining("US_EAST_1") - .hasMessageContaining("region") - .hasMessageContaining("us-east-1"); + .hasMessageContaining("Invalid region"); } @Test @@ -43,9 +41,7 @@ public void invalidS3UtilitiesRegionAtRequestGivesHelpfulMessage() { assertThatThrownBy(() -> utilities.getUrl(r -> r.bucket("foo").key("bar").region(Region.of("US_WEST_2")))) .isInstanceOf(SdkClientException.class) - .hasMessageContaining("US_WEST_2") - .hasMessageContaining("region") - .hasMessageContaining("us-west-2"); + .hasMessageContaining("Invalid region"); } @Test @@ -77,7 +73,8 @@ public void invalidS3ArnRegionAtRequestGivesHelpfulMessage() { public void invalidS3PresignerRegionAtClientGivesHelpfulMessage() { assertThatThrownBy(() -> S3Presigner.builder().region(Region.of("US_EAST_1")).build()) .isInstanceOf(SdkClientException.class) - .hasMessageContaining("Configured region (US_EAST_1) and tags ([]) resulted in an invalid URI"); + .hasMessageContaining("Configured region (US_EAST_1)") + .hasMessageContaining("invalid URI"); } @Test From a30b55f29a9ebafcd7a6f35d3b0b9886b340c27b Mon Sep 17 00:00:00 2001 From: Saranya Somepalli Date: Mon, 13 Jul 2026 15:43:57 -0700 Subject: [PATCH 2/3] Add tests and minor cleanup --- .../presigner/DefaultPollyPresigner.java | 4 ---- .../presigner/DefaultPollyPresignerTest.java | 20 +++++++++++++++++++ .../awssdk/services/s3/S3Utilities.java | 10 ++++------ .../internal/signing/DefaultS3Presigner.java | 9 +++------ 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java b/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java index 916946b71125..183daa53f20e 100644 --- a/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java +++ b/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java @@ -343,7 +343,6 @@ private void applyEndpoint(SdkHttpFullRequest.Builder httpRequestBuilder) { } private URI resolveEndpoint() { - // If user configured an endpoint override or one is set via environment/profile, use it. Optional overrideEndpoint = AwsClientEndpointProvider.builder() .clientEndpointOverride(endpointOverride) .serviceEndpointOverrideEnvironmentVariable( @@ -354,9 +353,6 @@ private URI resolveEndpoint() { .profileName(profileName) .resolveFromOverrides(); - // Resolve endpoint using the service's Endpoints 2.0 provider. Unlike S3, the Polly presigner - // does not have an endpoint resolution interceptor chain — this endpoint becomes the final - // presigned URL host, so it must be accurate. return overrideEndpoint.orElseGet(() -> CompletableFutureUtils.joinLikeSync( PollyEndpointProvider.defaultProvider() .resolveEndpoint(p -> p.region(region) diff --git a/services/polly/src/test/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresignerTest.java b/services/polly/src/test/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresignerTest.java index a56acb3be2c6..c62aee14c089 100644 --- a/services/polly/src/test/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresignerTest.java +++ b/services/polly/src/test/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresignerTest.java @@ -191,6 +191,26 @@ void presign_includesRequestLevelQueryParams_included() { assertThat(presignedSynthesizeSpeechRequest.httpRequest().rawQueryParameters().keySet()).contains("QueryParam1"); } + @Test + void presign_noEndpointOverride_usesDefaultEndpoint() { + PollyPresigner presigner = DefaultPollyPresigner.builder() + .region(Region.US_EAST_1) + .credentialsProvider(credentialsProvider) + .build(); + + SynthesizeSpeechPresignRequest presignRequest = SynthesizeSpeechPresignRequest.builder() + .synthesizeSpeechRequest(BASIC_SYNTHESIZE_SPEECH_REQUEST) + .signatureDuration(Duration.ofHours(3)) + .build(); + + PresignedSynthesizeSpeechRequest presigned = presigner.presignSynthesizeSpeech(presignRequest); + + URL presignedUrl = presigned.url(); + assertThat(presignedUrl.getProtocol()).isEqualTo("https"); + assertThat(presignedUrl.getHost()).isEqualTo("polly.us-east-1.amazonaws.com"); + assertThat(presignedUrl.getPath()).isEqualTo("/v1/speech"); + } + @Test void presign_endpointOverriden() { PollyPresigner presigner = DefaultPollyPresigner.builder() diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java index ee7ba7a27000..c542bd55bacb 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/S3Utilities.java @@ -425,12 +425,12 @@ private Region resolveRegionForGetUrl(GetUrlRequest getUrlRequest) { } /** - * If endpoint is not present, construct a default endpoint using the region information. - * The endpoint resolved here is only used for initial request marshalling — it gets completely replaced - * by S3ResolveEndpointInterceptor during the getUrl flow. + * Resolve the client endpoint provider for request marshalling. Checks client override, + * environment variables, system properties, and profile configuration. If no override is found, + * a localhost placeholder is used — the actual endpoint is resolved by S3EndpointProvider + * during the getUrl flow. */ private ClientEndpointProvider clientEndpointProvider(URI overrideEndpoint, Region region) { - // First check if there's an endpoint override from the user or environment. Optional resolvedOverride = AwsClientEndpointProvider.builder() .clientEndpointOverride(overrideEndpoint) .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_S3") @@ -440,8 +440,6 @@ private ClientEndpointProvider clientEndpointProvider(URI overrideEndpoint, Regi .profileName(profileName) .resolveFromOverrides(); - // If an override is configured, use it (marked as overridden). Otherwise, use a localhost placeholder - // marked as NOT overridden — S3ResolveEndpointInterceptor will replace it with the real endpoint. return resolvedOverride .map(uri -> ClientEndpointProvider.create(uri, true)) .orElseGet(() -> ClientEndpointProvider.create(URI.create("https://localhost"), false)); diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java index e5dd56112909..4d0a125d43f7 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/signing/DefaultS3Presigner.java @@ -254,7 +254,6 @@ private List initializeInterceptors() { * Copied from {@link AwsDefaultClientBuilder}. */ private SdkClientConfiguration createClientConfiguration() { - // First check if there's an endpoint override from the user or environment. Optional overrideEndpoint = AwsClientEndpointProvider.builder() .clientEndpointOverride(endpointOverride()) .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_S3") @@ -264,9 +263,6 @@ private SdkClientConfiguration createClientConfiguration() { .profileName(profileName()) .resolveFromOverrides(); - // If an override is configured, use it (marked as overridden). Otherwise, use a localhost placeholder - // marked as NOT overridden — S3ResolveEndpointInterceptor will replace it with the real endpoint - // at presigning time using S3EndpointProvider with the full request context. ClientEndpointProvider endpointProvider; if (overrideEndpoint.isPresent()) { endpointProvider = ClientEndpointProvider.create(overrideEndpoint.get(), true); @@ -274,8 +270,9 @@ private SdkClientConfiguration createClientConfiguration() { // Validate region at construction time to fail fast for invalid regions (e.g., US_EAST_1 with underscores). URI testEndpoint = URI.create("https://s3." + region().id() + ".amazonaws.com"); if (testEndpoint.getHost() == null) { - throw SdkClientException.create("Configured region (" + region() + ") and tags ([]) resulted in an invalid URI: " - + testEndpoint + ". This is usually caused by an invalid region configuration."); + throw SdkClientException.create("Configured region (" + region() + ") resulted in an invalid URI: " + + testEndpoint + ". This is usually caused by an invalid region " + + "configuration."); } endpointProvider = ClientEndpointProvider.create(URI.create("https://localhost"), false); } From 6080eb8d05cc3e73f84e88f7b0574b5a368d7be3 Mon Sep 17 00:00:00 2001 From: Saranya Somepalli Date: Mon, 13 Jul 2026 16:20:30 -0700 Subject: [PATCH 3/3] cache endpoint at construction time in DefaultPollyPresigner --- .../polly/internal/presigner/DefaultPollyPresigner.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java b/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java index 183daa53f20e..bc42d3440d0f 100644 --- a/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java +++ b/services/polly/src/main/java/software/amazon/awssdk/services/polly/internal/presigner/DefaultPollyPresigner.java @@ -96,6 +96,7 @@ public final class DefaultPollyPresigner implements PollyPresigner { private final URI endpointOverride; private final Boolean dualstackEnabled; private final Boolean fipsEnabled; + private final URI resolvedEndpoint; private DefaultPollyPresigner(BuilderImpl builder) { this.signingClock = builder.signingClock != null ? builder.signingClock @@ -128,6 +129,7 @@ private DefaultPollyPresigner(BuilderImpl builder) { .build() .isFipsEnabled() .orElse(false); + this.resolvedEndpoint = resolveEndpoint(); } IdentityProvider credentialsProvider() { @@ -336,10 +338,9 @@ private void applyOverrideHeadersAndQueryParams(SdkHttpFullRequest.Builder httpR } private void applyEndpoint(SdkHttpFullRequest.Builder httpRequestBuilder) { - URI uri = resolveEndpoint(); - httpRequestBuilder.protocol(uri.getScheme()) - .host(uri.getHost()) - .port(uri.getPort()); + httpRequestBuilder.protocol(resolvedEndpoint.getScheme()) + .host(resolvedEndpoint.getHost()) + .port(resolvedEndpoint.getPort()); } private URI resolveEndpoint() {