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..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 @@ -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; @@ -94,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 @@ -126,6 +129,7 @@ private DefaultPollyPresigner(BuilderImpl builder) { .build() .isFipsEnabled() .orElse(false); + this.resolvedEndpoint = resolveEndpoint(); } IdentityProvider credentialsProvider() { @@ -334,27 +338,28 @@ 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() { - 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(); + Optional overrideEndpoint = AwsClientEndpointProvider.builder() + .clientEndpointOverride(endpointOverride) + .serviceEndpointOverrideEnvironmentVariable( + "AWS_ENDPOINT_URL_POLLY") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlPolly") + .serviceProfileProperty("polly") + .profileFile(profileFile) + .profileName(profileName) + .resolveFromOverrides(); + + 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/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 cb3064798a99..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,22 +425,24 @@ private Region resolveRegionForGetUrl(GetUrlRequest getUrlRequest) { } /** - * If endpoint is not present, construct a default endpoint using the region information. + * 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) { - 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(); + Optional resolvedOverride = AwsClientEndpointProvider.builder() + .clientEndpointOverride(overrideEndpoint) + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_S3") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlS3") + .serviceProfileProperty("s3") + .profileFile(profileFile) + .profileName(profileName) + .resolveFromOverrides(); + + 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..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 @@ -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,31 @@ 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(); + Optional overrideEndpoint = AwsClientEndpointProvider.builder() + .clientEndpointOverride(endpointOverride()) + .serviceEndpointOverrideEnvironmentVariable("AWS_ENDPOINT_URL_S3") + .serviceEndpointOverrideSystemProperty("aws.endpointUrlS3") + .serviceProfileProperty("s3") + .profileFile(profileFileSupplier()) + .profileName(profileName()) + .resolveFromOverrides(); + + 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() + ") 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