Skip to content

Commit 5afb2ee

Browse files
committed
Add the ability to configure connection timeout, keep-alive settings, and advanced SocketOptions on the AwsCrtAsyncHttpClient. This is necessary for any clients with long-running connections that exceed default socket timeouts of services along the call path, and need to enable keep-alive settings which the CRT client supports, but the Java client wasn't exposing to callers
1 parent d6b02d5 commit 5afb2ee

File tree

5 files changed

+299
-8
lines changed

5 files changed

+299
-8
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "nikp",
5+
"description": "Add the ability to configure connection timeout, and enabling keep-alive with interval/timeout settings on the AwsCrtAsyncHttpClient to support long-running connections"
6+
}

http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtAsyncHttpClient.java

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import software.amazon.awssdk.utils.AttributeMap;
4747
import software.amazon.awssdk.utils.IoUtils;
4848
import software.amazon.awssdk.utils.Logger;
49+
import software.amazon.awssdk.utils.NumericUtils;
4950
import software.amazon.awssdk.utils.Validate;
5051

5152
/**
@@ -76,6 +77,14 @@ public final class AwsCrtAsyncHttpClient implements SdkAsyncHttpClient {
7677
private final int maxConnectionsPerEndpoint;
7778
private boolean isClosed = false;
7879

80+
private static final Duration CRT_SDK_DEFAULT_CONNECTION_TIMEOUT = Duration.ofSeconds(3);
81+
// Override default connection timeout for Crt client to be in line with the CRT default:
82+
// https://github.com/awslabs/aws-crt-java/blob/main/src/main/java/software/amazon/awssdk/crt/io/SocketOptions.java#L79
83+
private static final AttributeMap CRT_HTTP_DEFAULTS =
84+
AttributeMap.builder()
85+
.put(SdkHttpConfigurationOption.CONNECTION_TIMEOUT, CRT_SDK_DEFAULT_CONNECTION_TIMEOUT)
86+
.build();
87+
7988
private AwsCrtAsyncHttpClient(DefaultBuilder builder, AttributeMap config) {
8089
int maxConns = config.get(SdkHttpConfigurationOption.MAX_CONNECTIONS);
8190

@@ -84,7 +93,7 @@ private AwsCrtAsyncHttpClient(DefaultBuilder builder, AttributeMap config) {
8493
Validate.isPositive(builder.readBufferSize, "readBufferSize");
8594

8695
try (ClientBootstrap clientBootstrap = new ClientBootstrap(null, null);
87-
SocketOptions clientSocketOptions = new SocketOptions();
96+
SocketOptions clientSocketOptions = buildSocketOptions(builder, config);
8897
TlsContextOptions clientTlsContextOptions = TlsContextOptions.createDefaultClient() // NOSONAR
8998
.withCipherPreference(builder.cipherPreference)
9099
.withVerifyPeer(!config.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES));
@@ -138,6 +147,24 @@ private HttpProxyOptions buildProxyOptions(ProxyConfiguration proxyConfiguration
138147
return clientProxyOptions;
139148
}
140149

150+
private SocketOptions buildSocketOptions(DefaultBuilder builder, AttributeMap config) {
151+
SocketOptions clientSocketOptions = new SocketOptions();
152+
153+
Duration connectionTimeout = config.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT);
154+
if (connectionTimeout != null) {
155+
clientSocketOptions.connectTimeoutMs = NumericUtils.saturatedCast(connectionTimeout.toMillis());
156+
}
157+
158+
TcpKeepAliveConfiguration tcpKeepAliveConfiguration = builder.tcpKeepAliveConfiguration;
159+
if (tcpKeepAliveConfiguration != null) {
160+
clientSocketOptions.keepAliveIntervalSecs = NumericUtils.saturatedCast(tcpKeepAliveConfiguration.keepAliveInterval().getSeconds());
161+
clientSocketOptions.keepAliveTimeoutSecs = NumericUtils.saturatedCast(tcpKeepAliveConfiguration.keepAliveTimeout().getSeconds());
162+
163+
}
164+
165+
return clientSocketOptions;
166+
}
167+
141168
/**
142169
* Marks a Native CrtResource as owned by the current Java Object.
143170
*
@@ -312,10 +339,10 @@ public interface Builder extends SdkAsyncHttpClient.Builder<AwsCrtAsyncHttpClien
312339
Builder proxyConfiguration(Consumer<ProxyConfiguration.Builder> proxyConfigurationBuilderConsumer);
313340

314341
/**
315-
* Configure the health checks for for all connections established by this client.
342+
* Configure the health checks for all connections established by this client.
316343
*
317344
* <p>
318-
* eg: you can set a throughput threshold for the a connection to be considered healthy.
345+
* eg: you can set a throughput threshold for a connection to be considered healthy.
319346
* If the connection falls below this threshold for a configurable amount of time,
320347
* then the connection is considered unhealthy and will be shut down.
321348
*
@@ -325,10 +352,10 @@ public interface Builder extends SdkAsyncHttpClient.Builder<AwsCrtAsyncHttpClien
325352
Builder connectionHealthChecksConfiguration(ConnectionHealthChecksConfiguration healthChecksConfiguration);
326353

327354
/**
328-
* A convenience method to configure the health checks for for all connections established by this client.
355+
* A convenience method to configure the health checks for all connections established by this client.
329356
*
330357
* <p>
331-
* eg: you can set a throughput threshold for the a connection to be considered healthy.
358+
* eg: you can set a throughput threshold for a connection to be considered healthy.
332359
* If the connection falls below this threshold for a configurable amount of time,
333360
* then the connection is considered unhealthy and will be shut down.
334361
*
@@ -340,9 +367,46 @@ Builder connectionHealthChecksConfiguration(Consumer<ConnectionHealthChecksConfi
340367
healthChecksConfigurationBuilder);
341368

342369
/**
343-
* Configure the maximum amount of time that a connection should be allowed to remain open while idle.
370+
* The amount of time to wait when initially establishing a connection before giving up and timing out. The maximum
371+
* possible value, in ms, is the value of {@link Integer#MAX_VALUE}, any longer duration will be reduced to the maximum
372+
* possible value. If not specified, the connection timeout duration will be set to value defined in
373+
* {@link AwsCrtAsyncHttpClient#CRT_SDK_DEFAULT_CONNECTION_TIMEOUT}.
344374
*/
345375
Builder connectionMaxIdleTime(Duration connectionMaxIdleTime);
376+
377+
/**
378+
* Configure connection socket timeout
379+
*/
380+
Builder connectionTimeout(Duration connectionTimeout);
381+
382+
/**
383+
* Configure whether to enable TCP Keep-alive and relevant configuration for all connections established by this client.
384+
*
385+
* <p>
386+
* By default, keepAlive is disabled and this is not required.
387+
* tcpKeepAlive is enabled by providing this configuration and specifying
388+
* periodic keepalive packet intervals and timeouts
389+
* This may be required for certain connections for longer durations than default socket timeouts
390+
*
391+
* @param tcpKeepAliveConfiguration The TCP keep-alive configuration to use
392+
* @return The builder of the method chaining.
393+
*/
394+
Builder tcpKeepAliveConfiguration(TcpKeepAliveConfiguration tcpKeepAliveConfiguration);
395+
396+
/**
397+
* Configure whether to enable TCP Keep-alive and relevant configuration for all connections established by this client.
398+
*
399+
* <p>
400+
* By default, keepAlive is disabled and this is not required.
401+
* tcpKeepAlive is enabled by providing this configuration and specifying
402+
* periodic keepalive packet intervals and timeouts
403+
* This may be required for certain connections for longer durations than default socket timeouts
404+
*
405+
* @param tcpKeepAliveConfigurationBuilder The TCP keep-alive configuration builder to use
406+
* @return The builder of the method chaining.
407+
*/
408+
Builder tcpKeepAliveConfiguration(Consumer<TcpKeepAliveConfiguration.Builder>
409+
tcpKeepAliveConfigurationBuilder);
346410
}
347411

348412
/**
@@ -355,20 +419,23 @@ private static final class DefaultBuilder implements Builder {
355419
private int readBufferSize = DEFAULT_STREAM_WINDOW_SIZE;
356420
private ProxyConfiguration proxyConfiguration;
357421
private ConnectionHealthChecksConfiguration connectionHealthChecksConfiguration;
422+
private TcpKeepAliveConfiguration tcpKeepAliveConfiguration;
358423

359424
private DefaultBuilder() {
360425
}
361426

362427
@Override
363428
public SdkAsyncHttpClient build() {
364429
return new AwsCrtAsyncHttpClient(this, standardOptions.build()
430+
.merge(CRT_HTTP_DEFAULTS)
365431
.merge(SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS));
366432
}
367433

368434
@Override
369435
public SdkAsyncHttpClient buildWithDefaults(AttributeMap serviceDefaults) {
370436
return new AwsCrtAsyncHttpClient(this, standardOptions.build()
371437
.merge(serviceDefaults)
438+
.merge(CRT_HTTP_DEFAULTS)
372439
.merge(SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS));
373440
}
374441

@@ -417,10 +484,32 @@ public Builder connectionHealthChecksConfiguration(Consumer<ConnectionHealthChec
417484

418485
@Override
419486
public Builder connectionMaxIdleTime(Duration connectionMaxIdleTime) {
487+
Validate.isPositive(connectionMaxIdleTime, "connectionMaxIdleTime");
420488
standardOptions.put(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT, connectionMaxIdleTime);
421489
return this;
422490
}
423491

492+
@Override
493+
public Builder connectionTimeout(Duration connectionTimeout) {
494+
Validate.isPositive(connectionTimeout, "connectionTimeout");
495+
standardOptions.put(SdkHttpConfigurationOption.CONNECTION_TIMEOUT, connectionTimeout);
496+
return this;
497+
}
498+
499+
@Override
500+
public Builder tcpKeepAliveConfiguration(TcpKeepAliveConfiguration tcpKeepAliveConfiguration) {
501+
this.tcpKeepAliveConfiguration = tcpKeepAliveConfiguration;
502+
return this;
503+
}
504+
505+
@Override
506+
public Builder tcpKeepAliveConfiguration(Consumer<TcpKeepAliveConfiguration.Builder>
507+
tcpKeepAliveConfigurationBuilder) {
508+
TcpKeepAliveConfiguration.Builder builder = TcpKeepAliveConfiguration.builder();
509+
tcpKeepAliveConfigurationBuilder.accept(builder);
510+
return tcpKeepAliveConfiguration(builder.build());
511+
}
512+
424513
@Override
425514
public Builder proxyConfiguration(Consumer<ProxyConfiguration.Builder> proxyConfigurationBuilderConsumer) {
426515
ProxyConfiguration.Builder builder = ProxyConfiguration.builder();

http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/ConnectionHealthChecksConfiguration.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121
import software.amazon.awssdk.utils.Validate;
2222

2323
/**
24-
* Configuration that defines health checks for for all connections established by
25-
* the{@link ConnectionHealthChecksConfiguration}.
24+
* Configuration that defines health checks for all connections established by
25+
* the {@link ConnectionHealthChecksConfiguration}.
2626
*
2727
* <b>NOTE:</b> This is a Preview API and is subject to change so it should not be used in production.
2828
*/
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http.crt;
17+
18+
import java.time.Duration;
19+
import software.amazon.awssdk.annotations.SdkPreviewApi;
20+
import software.amazon.awssdk.annotations.SdkPublicApi;
21+
import software.amazon.awssdk.utils.Validate;
22+
23+
/**
24+
* Configuration that defines keep-alive options for all connections established by
25+
* the {@link TcpKeepAliveConfiguration}.
26+
*
27+
* <b>NOTE:</b> This is a Preview API and is subject to change so it should not be used in production.
28+
*/
29+
@SdkPublicApi
30+
@SdkPreviewApi
31+
public final class TcpKeepAliveConfiguration {
32+
33+
private final Duration keepAliveInterval;
34+
private final Duration keepAliveTimeout;
35+
36+
private TcpKeepAliveConfiguration(DefaultTcpKeepAliveConfigurationBuilder builder) {
37+
this.keepAliveInterval = Validate.isPositive(builder.keepAliveInterval,
38+
"keepAliveInterval");
39+
this.keepAliveTimeout = Validate.isPositive(builder.keepAliveTimeout,
40+
"keepAliveTimeout");
41+
}
42+
43+
/**
44+
* @return number of seconds between TCP keepalive packets being sent to the peer
45+
*/
46+
public Duration keepAliveInterval() {
47+
return keepAliveInterval;
48+
}
49+
50+
/**
51+
* @return number of seconds to wait for a keepalive response before considering the connection timed out
52+
*/
53+
public Duration keepAliveTimeout() {
54+
return keepAliveTimeout;
55+
}
56+
57+
public static Builder builder() {
58+
return new DefaultTcpKeepAliveConfigurationBuilder();
59+
}
60+
61+
/**
62+
* A builder for {@link TcpKeepAliveConfiguration}.
63+
*
64+
* <p>All implementations of this interface are mutable and not thread safe.</p>
65+
*/
66+
public interface Builder {
67+
/**
68+
* Sets the Duration between TCP keepalive packets being sent to the peer
69+
* @param keepAliveInterval Duration between TCP keepalive packets being sent to the peer
70+
* @return Builder
71+
*/
72+
Builder keepAliveInterval(Duration keepAliveInterval);
73+
74+
/**
75+
* Sets the Duration to wait for a keepalive response before considering the connection timed out
76+
* @param keepAliveTimeout Duration to wait for a keepalive response before considering the connection timed out
77+
* @return Builder
78+
*/
79+
Builder keepAliveTimeout(Duration keepAliveTimeout);
80+
81+
TcpKeepAliveConfiguration build();
82+
}
83+
84+
/**
85+
* An SDK-internal implementation of {@link Builder}.
86+
*/
87+
private static final class DefaultTcpKeepAliveConfigurationBuilder implements Builder {
88+
private Duration keepAliveInterval;
89+
private Duration keepAliveTimeout;
90+
91+
private DefaultTcpKeepAliveConfigurationBuilder() {
92+
}
93+
94+
/**
95+
* Sets the Duration between TCP keepalive packets being sent to the peer
96+
* @param keepAliveInterval Duration between TCP keepalive packets being sent to the peer
97+
* @return Builder
98+
*/
99+
@Override
100+
public Builder keepAliveInterval(Duration keepAliveInterval) {
101+
this.keepAliveInterval = keepAliveInterval;
102+
return this;
103+
}
104+
105+
/**
106+
* Sets the Duration to wait for a keepalive response before considering the connection timed out
107+
* @param keepAliveTimeout Duration to wait for a keepalive response before considering the connection timed out
108+
* @return Builder
109+
*/
110+
@Override
111+
public Builder keepAliveTimeout(Duration keepAliveTimeout) {
112+
this.keepAliveTimeout = keepAliveTimeout;
113+
return this;
114+
}
115+
116+
@Override
117+
public TcpKeepAliveConfiguration build() {
118+
return new TcpKeepAliveConfiguration(this);
119+
}
120+
}
121+
}

0 commit comments

Comments
 (0)