diff --git a/.changes/next-release/feature-AWSSDKforJavav2-29af98e.json b/.changes/next-release/feature-AWSSDKforJavav2-29af98e.json new file mode 100644 index 000000000000..b5b7c104bec1 --- /dev/null +++ b/.changes/next-release/feature-AWSSDKforJavav2-29af98e.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "AWS SDK for Java v2", + "contributor": "L-Applin", + "description": "Enable configuration of the connection timeout for AwsCrtAsyncHttpClient" +} diff --git a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtAsyncHttpClient.java b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtAsyncHttpClient.java index 2e0985070340..6ce1098b260f 100644 --- a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtAsyncHttpClient.java +++ b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtAsyncHttpClient.java @@ -89,7 +89,8 @@ private AwsCrtAsyncHttpClient(DefaultBuilder builder, AttributeMap config) { .withCipherPreference(builder.cipherPreference) .withVerifyPeer(!config.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES)); TlsContext clientTlsContext = new TlsContext(clientTlsContextOptions)) { - + Duration connectionTimeout = config.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT); + setConnectionTimeout(clientSocketOptions, connectionTimeout); this.bootstrap = registerOwnedResource(clientBootstrap); this.socketOptions = registerOwnedResource(clientSocketOptions); this.tlsContext = registerOwnedResource(clientTlsContext); @@ -101,6 +102,15 @@ private AwsCrtAsyncHttpClient(DefaultBuilder builder, AttributeMap config) { } } + private void setConnectionTimeout(SocketOptions clientSocketOptions, Duration connectionTimeout) { + if (connectionTimeout == null) { + return; + } + + long connectionTimeoutMillis = connectionTimeout.toMillis(); + clientSocketOptions.connectTimeoutMs = (int) Long.min(connectionTimeoutMillis, Integer.MAX_VALUE); + } + private HttpMonitoringOptions revolveHttpMonitoringOptions(ConnectionHealthChecksConfiguration config) { if (config == null) { return null; @@ -325,10 +335,10 @@ public interface Builder extends SdkAsyncHttpClient.Builder - * eg: you can set a throughput threshold for the a connection to be considered healthy. + * eg: you can set a throughput threshold for the connection to be considered healthy. * If the connection falls below this threshold for a configurable amount of time, * then the connection is considered unhealthy and will be shut down. * @@ -343,6 +353,14 @@ Builder connectionHealthChecksConfiguration(Consumer proxyConf proxyConfigurationBuilderConsumer.accept(builder); return proxyConfiguration(builder.build()); } + + @Override + public Builder connectionTimeout(Duration connectionTimeout) { + standardOptions.put(SdkHttpConfigurationOption.CONNECTION_TIMEOUT, connectionTimeout); + return this; + } } } diff --git a/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientBuilderTest.java b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientBuilderTest.java new file mode 100644 index 000000000000..4edc3d097816 --- /dev/null +++ b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientBuilderTest.java @@ -0,0 +1,75 @@ +/* + * 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.crt; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.testng.Assert.fail; + +import java.lang.reflect.Field; +import java.time.Duration; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.platform.commons.support.ReflectionSupport; +import software.amazon.awssdk.crt.io.SocketOptions; +import software.amazon.awssdk.http.SdkHttpConfigurationOption; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; + +class AwsCrtHttpClientBuilderTest { + + @ParameterizedTest + @ArgumentsSource(TimeoutArgumentProvider.class) + void createBuilder_withConnectionTimeout_updateClientTimeout(Duration timeout, int millis) throws Exception { + SdkAsyncHttpClient client = AwsCrtAsyncHttpClient.builder().connectionTimeout(timeout).build(); + validateTimeoutValue(client, millis); + client.close(); + } + + @Test + void createBuilder_withoutConnectionTimeout_shouldUseDefaultValue() throws Exception { + SdkAsyncHttpClient client = AwsCrtAsyncHttpClient.create(); + int globalDefaultConnectionTimeout = + (int) SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT).toMillis(); + validateTimeoutValue(client, globalDefaultConnectionTimeout); + client.close(); + } + + private void validateTimeoutValue(SdkAsyncHttpClient client, int expected) throws Exception { + Field socketOptionsField = AwsCrtAsyncHttpClient.class.getDeclaredField("socketOptions"); + SocketOptions options = (SocketOptions) ReflectionSupport.tryToReadFieldValue(socketOptionsField, client) + .ifFailure(e -> fail("Cannot read field socketOptions from class " + + "AwsCrtAsyncHttpClient", e)) + .get(); + assertThat(options.connectTimeoutMs).isEqualTo(expected); + } + + private static class TimeoutArgumentProvider implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext context) { + return Stream.of( + arguments(Duration.ofSeconds(0), 0), + arguments(Duration.ofSeconds(1), 1_000), + arguments(Duration.ofSeconds(10), 10_000), + arguments(Duration.ofDays(40), Integer.MAX_VALUE) + ); + } + } +}