From 1f8981ee22c59390e25f0ee16361857d942f938c Mon Sep 17 00:00:00 2001 From: Nik Pinski Date: Sun, 2 Oct 2022 21:39:05 -0700 Subject: [PATCH] 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 --- .../feature-AWSSDKforJavav2-c2b1dfc.json | 6 + .../http/crt/AwsCrtAsyncHttpClient.java | 166 +++++++++++++++++- .../ConnectionHealthChecksConfiguration.java | 4 +- .../http/crt/SocketOptionsConfiguration.java | 125 +++++++++++++ .../http/crt/TcpKeepAliveConfiguration.java | 121 +++++++++++++ .../crt/SocketOptionsConfigurationTest.java | 46 +++++ .../crt/TcpKeepAliveConfigurationTest.java | 75 ++++++++ 7 files changed, 535 insertions(+), 8 deletions(-) create mode 100644 .changes/next-release/feature-AWSSDKforJavav2-c2b1dfc.json create mode 100644 http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/SocketOptionsConfiguration.java create mode 100644 http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/TcpKeepAliveConfiguration.java create mode 100644 http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/SocketOptionsConfigurationTest.java create mode 100644 http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/TcpKeepAliveConfigurationTest.java diff --git a/.changes/next-release/feature-AWSSDKforJavav2-c2b1dfc.json b/.changes/next-release/feature-AWSSDKforJavav2-c2b1dfc.json new file mode 100644 index 000000000000..4fab63a7a42d --- /dev/null +++ b/.changes/next-release/feature-AWSSDKforJavav2-c2b1dfc.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "AWS SDK for Java v2", + "contributor": "nikp", + "description": "Add the ability to configure connection timeout, keep-alive settings, and other advanced SocketOptions on the AwsCrtAsyncHttpClient to support long-running connections" +} 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..4647c67729f4 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 @@ -76,6 +76,14 @@ public final class AwsCrtAsyncHttpClient implements SdkAsyncHttpClient { private final int maxConnectionsPerEndpoint; private boolean isClosed = false; + private static final Duration CRT_SDK_DEFAULT_CONNECTION_TIMEOUT = Duration.ofSeconds(3); + // Override default connection timeout for Crt client to be in line with the CRT default: + // https://github.com/awslabs/aws-crt-java/blob/main/src/main/java/software/amazon/awssdk/crt/io/SocketOptions.java#L79 + private static final AttributeMap CRT_HTTP_DEFAULTS = + AttributeMap.builder() + .put(SdkHttpConfigurationOption.CONNECTION_TIMEOUT, CRT_SDK_DEFAULT_CONNECTION_TIMEOUT) + .build(); + private AwsCrtAsyncHttpClient(DefaultBuilder builder, AttributeMap config) { int maxConns = config.get(SdkHttpConfigurationOption.MAX_CONNECTIONS); @@ -83,8 +91,12 @@ private AwsCrtAsyncHttpClient(DefaultBuilder builder, AttributeMap config) { Validate.notNull(builder.cipherPreference, "cipherPreference"); Validate.isPositive(builder.readBufferSize, "readBufferSize"); + if (Boolean.TRUE.equals(builder.standardOptions.get(SdkHttpConfigurationOption.TCP_KEEPALIVE))) { + Validate.notNull(builder.tcpKeepAliveConfiguration, "tcpKeepAliveConfiguration must be provided when tcpKeepAlive is enabled"); + } + try (ClientBootstrap clientBootstrap = new ClientBootstrap(null, null); - SocketOptions clientSocketOptions = new SocketOptions(); + SocketOptions clientSocketOptions = buildSocketOptions(builder, config); TlsContextOptions clientTlsContextOptions = TlsContextOptions.createDefaultClient() // NOSONAR .withCipherPreference(builder.cipherPreference) .withVerifyPeer(!config.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES)); @@ -138,6 +150,34 @@ private HttpProxyOptions buildProxyOptions(ProxyConfiguration proxyConfiguration return clientProxyOptions; } + private SocketOptions buildSocketOptions(DefaultBuilder builder, AttributeMap config) { + SocketOptions clientSocketOptions = new SocketOptions(); + + Duration connectionTimeout = config.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT); + if (connectionTimeout != null) { + clientSocketOptions.connectTimeoutMs = (int) Long.min(connectionTimeout.toMillis(), Integer.MAX_VALUE); + } + + TcpKeepAliveConfiguration tcpKeepAliveConfiguration = builder.tcpKeepAliveConfiguration; + if (tcpKeepAliveConfiguration != null) { + long keepAliveIntervalSecs = tcpKeepAliveConfiguration.keepAliveInterval().getSeconds(); + long keepAliveTimeoutSecs = tcpKeepAliveConfiguration.keepAliveTimeout().getSeconds(); + + clientSocketOptions.keepAliveIntervalSecs = (int) Long.min(keepAliveIntervalSecs, Integer.MAX_VALUE); + clientSocketOptions.keepAliveTimeoutSecs = (int) Long.min(keepAliveTimeoutSecs, Integer.MAX_VALUE); + + } + + SocketOptionsConfiguration socketOptionsConfiguration = builder.socketOptionsConfiguration; + if (socketOptionsConfiguration != null) { + clientSocketOptions.domain = socketOptionsConfiguration.domain(); + clientSocketOptions.type = socketOptionsConfiguration.type(); + } + + + return clientSocketOptions; + } + /** * Marks a Native CrtResource as owned by the current Java Object. * @@ -312,10 +352,10 @@ public interface Builder extends SdkAsyncHttpClient.Builder proxyConfigurationBuilderConsumer); /** - * Configure the health checks for for all connections established by this client. + * Configure the health checks for all connections established by this client. * *

- * eg: you can set a throughput threshold for the a connection to be considered healthy. + * eg: you can set a throughput threshold for a 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. * @@ -325,10 +365,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 a 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. * @@ -340,9 +380,75 @@ Builder connectionHealthChecksConfiguration(Consumer + * By default, this is disabled. + *

+ * When enabled, the actual KeepAlive mechanism is dependent on the Operating System and therefore additional TCP + * KeepAlive values (like timeout, number of packets, etc) must be configured via the Operating System (sysctl on + * Linux/Mac, and Registry values on Windows). + * + * To enable, keepAlive interval and timeout must be additionally configured via {@link TcpKeepAliveConfiguration} + */ + Builder tcpKeepAlive(Boolean keepConnectionAlive); + + /** + * Configure TCP Keep-alive configuration for all connections established by this client. + * + *

+ * If tcpKeepAlive is enabled, this is required configuration + * and specify periodic keepalive packet intervals and including timeouts + * This may be required for certain connections for longer durations than default socket timeouts + * + * @param tcpKeepAliveConfiguration The TCP keep-alive configuration to use + * @return The builder of the method chaining. + */ + Builder tcpKeepAliveConfiguration(TcpKeepAliveConfiguration tcpKeepAliveConfiguration); + + /** + * Configure TCP Keep-alive configuration for all connections established by this client. + * + *

+ * If tcpKeepAlive is enabled, this is required configuration + * and specify periodic keepalive packet intervals and including timeouts + * This may be required for certain connections for longer durations than default socket timeouts + * + * @param tcpKeepAliveConfigurationBuilder The TCP keep-alive configuration builder to use + * @return The builder of the method chaining. + */ + Builder tcpKeepAliveConfiguration(Consumer + tcpKeepAliveConfigurationBuilder); + + /** + * Configure socket options configuration for alternative transports via socket domains and ty pes + * + * @param socketOptionsConfiguration The socket configuration to use + * @return The builder of the method chaining. + */ + Builder socketOptionsConfiguration(SocketOptionsConfiguration socketOptionsConfiguration); + + /** + * Configure socket options configuration for alternative transports via socket domains and ty pes + * + * @param socketOptionsConfigurationBuilder The socket configuration builder to use + * @return The builder of the method chaining. + */ + Builder socketOptionsConfiguration(Consumer + socketOptionsConfigurationBuilder); } /** @@ -355,6 +461,8 @@ private static final class DefaultBuilder implements Builder { private int readBufferSize = DEFAULT_STREAM_WINDOW_SIZE; private ProxyConfiguration proxyConfiguration; private ConnectionHealthChecksConfiguration connectionHealthChecksConfiguration; + private TcpKeepAliveConfiguration tcpKeepAliveConfiguration; + private SocketOptionsConfiguration socketOptionsConfiguration; private DefaultBuilder() { } @@ -362,6 +470,7 @@ private DefaultBuilder() { @Override public SdkAsyncHttpClient build() { return new AwsCrtAsyncHttpClient(this, standardOptions.build() + .merge(CRT_HTTP_DEFAULTS) .merge(SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS)); } @@ -369,6 +478,7 @@ public SdkAsyncHttpClient build() { public SdkAsyncHttpClient buildWithDefaults(AttributeMap serviceDefaults) { return new AwsCrtAsyncHttpClient(this, standardOptions.build() .merge(serviceDefaults) + .merge(CRT_HTTP_DEFAULTS) .merge(SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS)); } @@ -417,15 +527,59 @@ public Builder connectionHealthChecksConfiguration(Consumer + tcpKeepAliveConfigurationBuilder) { + TcpKeepAliveConfiguration.Builder builder = TcpKeepAliveConfiguration.builder(); + tcpKeepAliveConfigurationBuilder.accept(builder); + return tcpKeepAliveConfiguration(builder.build()); + } + @Override public Builder proxyConfiguration(Consumer proxyConfigurationBuilderConsumer) { ProxyConfiguration.Builder builder = ProxyConfiguration.builder(); proxyConfigurationBuilderConsumer.accept(builder); return proxyConfiguration(builder.build()); } + + + @Override + public Builder socketOptionsConfiguration(SocketOptionsConfiguration socketOptionsConfiguration) { + this.socketOptionsConfiguration = socketOptionsConfiguration; + return this; + } + + @Override + public Builder socketOptionsConfiguration(Consumer + socketOptionsConfigurationBuilder) { + SocketOptionsConfiguration.Builder builder = SocketOptionsConfiguration.builder(); + socketOptionsConfigurationBuilder.accept(builder); + return socketOptionsConfiguration(builder.build()); + } } } diff --git a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/ConnectionHealthChecksConfiguration.java b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/ConnectionHealthChecksConfiguration.java index f8b14366cdfa..e9df57bb44db 100644 --- a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/ConnectionHealthChecksConfiguration.java +++ b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/ConnectionHealthChecksConfiguration.java @@ -21,8 +21,8 @@ import software.amazon.awssdk.utils.Validate; /** - * Configuration that defines health checks for for all connections established by - * the{@link ConnectionHealthChecksConfiguration}. + * Configuration that defines health checks for all connections established by + * the {@link ConnectionHealthChecksConfiguration}. * * NOTE: This is a Preview API and is subject to change so it should not be used in production. */ diff --git a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/SocketOptionsConfiguration.java b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/SocketOptionsConfiguration.java new file mode 100644 index 000000000000..58591464e9d5 --- /dev/null +++ b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/SocketOptionsConfiguration.java @@ -0,0 +1,125 @@ +/* + * 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 software.amazon.awssdk.annotations.SdkPreviewApi; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.crt.io.SocketOptions; +import software.amazon.awssdk.utils.Validate; + +/** + * Configuration that defines socket options for all connections established by + * the {@link SocketOptionsConfiguration}. + * + * NOTE: This is a Preview API and is subject to change so it should not be used in production. + */ +@SdkPublicApi +@SdkPreviewApi +public final class SocketOptionsConfiguration { + + private final SocketOptions.SocketDomain domain; + private final SocketOptions.SocketType type; + + private SocketOptionsConfiguration(DefaultSocketOptionsConfigurationBuilder builder) { + this.domain = Validate.paramNotNull(builder.domain, + "domain"); + this.type = Validate.paramNotNull(builder.type, + "type"); + } + + /** + * @return socket domain + */ + public SocketOptions.SocketDomain domain() { + return domain; + } + + /** + * @return socket type + */ + public SocketOptions.SocketType type() { + return type; + } + + public static Builder builder() { + return new DefaultSocketOptionsConfigurationBuilder(); + } + + /** + * A builder for {@link SocketOptionsConfiguration}. + * + *

All implementations of this interface are mutable and not thread safe.

+ */ + public interface Builder { + + /** + * Sets the socket domain + * @param domain socket domain + * @return Builder + */ + Builder domain(SocketOptions.SocketDomain domain); + + /** + * Sets the socket type + * @param type socket type + * @return Builder + */ + Builder type(SocketOptions.SocketType type); + + SocketOptionsConfiguration build(); + } + + /** + * An SDK-internal implementation of {@link Builder}. + */ + private static final class DefaultSocketOptionsConfigurationBuilder implements Builder { + + private SocketOptions.SocketDomain domain = SocketOptions.SocketDomain.IPv6; + private SocketOptions.SocketType type = SocketOptions.SocketType.STREAM; + + private DefaultSocketOptionsConfigurationBuilder() { + } + + /** + * Sets the socket domain + * Default: {@link SocketOptions.SocketDomain#IPv6} + * @param domain socket domain + * @return Builder + */ + @Override + public Builder domain(SocketOptions.SocketDomain domain) { + this.domain = domain; + return this; + } + + /** + * Sets the socket type + * Default: {@link SocketOptions.SocketType#STREAM} + * @param type socket type + * @return Builder + */ + @Override + public Builder type(SocketOptions.SocketType type) { + this.type = type; + return this; + } + + @Override + public SocketOptionsConfiguration build() { + return new SocketOptionsConfiguration(this); + } + } +} diff --git a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/TcpKeepAliveConfiguration.java b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/TcpKeepAliveConfiguration.java new file mode 100644 index 000000000000..24e33467b2bf --- /dev/null +++ b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/TcpKeepAliveConfiguration.java @@ -0,0 +1,121 @@ +/* + * 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 java.time.Duration; +import software.amazon.awssdk.annotations.SdkPreviewApi; +import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.utils.Validate; + +/** + * Configuration that defines keep-alive options for all connections established by + * the {@link TcpKeepAliveConfiguration}. + * + * NOTE: This is a Preview API and is subject to change so it should not be used in production. + */ +@SdkPublicApi +@SdkPreviewApi +public final class TcpKeepAliveConfiguration { + + private final Duration keepAliveInterval; + private final Duration keepAliveTimeout; + + private TcpKeepAliveConfiguration(DefaultTcpKeepAliveConfigurationBuilder builder) { + this.keepAliveInterval = Validate.isPositive(builder.keepAliveInterval, + "keepAliveInterval"); + this.keepAliveTimeout = Validate.isPositive(builder.keepAliveTimeout, + "keepAliveTimeout"); + } + + /** + * @return number of seconds between TCP keepalive packets being sent to the peer + */ + public Duration keepAliveInterval() { + return keepAliveInterval; + } + + /** + * @return number of seconds to wait for a keepalive response before considering the connection timed out + */ + public Duration keepAliveTimeout() { + return keepAliveTimeout; + } + + public static Builder builder() { + return new DefaultTcpKeepAliveConfigurationBuilder(); + } + + /** + * A builder for {@link TcpKeepAliveConfiguration}. + * + *

All implementations of this interface are mutable and not thread safe.

+ */ + public interface Builder { + /** + * Sets the Duration between TCP keepalive packets being sent to the peer + * @param keepAliveInterval Duration between TCP keepalive packets being sent to the peer + * @return Builder + */ + Builder keepAliveInterval(Duration keepAliveInterval); + + /** + * Sets the Duration to wait for a keepalive response before considering the connection timed out + * @param keepAliveTimeout Duration to wait for a keepalive response before considering the connection timed out + * @return Builder + */ + Builder keepAliveTimeout(Duration keepAliveTimeout); + + TcpKeepAliveConfiguration build(); + } + + /** + * An SDK-internal implementation of {@link Builder}. + */ + private static final class DefaultTcpKeepAliveConfigurationBuilder implements Builder { + private Duration keepAliveInterval; + private Duration keepAliveTimeout; + + private DefaultTcpKeepAliveConfigurationBuilder() { + } + + /** + * Sets the Duration between TCP keepalive packets being sent to the peer + * @param keepAliveInterval Duration between TCP keepalive packets being sent to the peer + * @return Builder + */ + @Override + public Builder keepAliveInterval(Duration keepAliveInterval) { + this.keepAliveInterval = keepAliveInterval; + return this; + } + + /** + * Sets the Duration to wait for a keepalive response before considering the connection timed out + * @param keepAliveTimeout Duration to wait for a keepalive response before considering the connection timed out + * @return Builder + */ + @Override + public Builder keepAliveTimeout(Duration keepAliveTimeout) { + this.keepAliveTimeout = keepAliveTimeout; + return this; + } + + @Override + public TcpKeepAliveConfiguration build() { + return new TcpKeepAliveConfiguration(this); + } + } +} diff --git a/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/SocketOptionsConfigurationTest.java b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/SocketOptionsConfigurationTest.java new file mode 100644 index 000000000000..91b25df99fec --- /dev/null +++ b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/SocketOptionsConfigurationTest.java @@ -0,0 +1,46 @@ +/* + * 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 org.junit.jupiter.api.Test; +import software.amazon.awssdk.crt.io.SocketOptions; + +public class SocketOptionsConfigurationTest { + + @Test + public void builder_defaultPropertiesSet() { + SocketOptionsConfiguration socketOptionsConfiguration = + SocketOptionsConfiguration.builder() + .build(); + + assertThat(socketOptionsConfiguration.domain()).isEqualTo(SocketOptions.SocketDomain.IPv6); + assertThat(socketOptionsConfiguration.type()).isEqualTo(SocketOptions.SocketType.STREAM); + } + + @Test + public void builder_allPropertiesSet() { + SocketOptionsConfiguration socketOptionsConfiguration = + SocketOptionsConfiguration.builder() + .domain(SocketOptions.SocketDomain.IPv4) + .type(SocketOptions.SocketType.DGRAM) + .build(); + + assertThat(socketOptionsConfiguration.domain()).isEqualTo(SocketOptions.SocketDomain.IPv4); + assertThat(socketOptionsConfiguration.type()).isEqualTo(SocketOptions.SocketType.DGRAM); + } +} diff --git a/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/TcpKeepAliveConfigurationTest.java b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/TcpKeepAliveConfigurationTest.java new file mode 100644 index 000000000000..59668c9af352 --- /dev/null +++ b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/TcpKeepAliveConfigurationTest.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.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.Duration; +import org.junit.jupiter.api.Test; + +public class TcpKeepAliveConfigurationTest { + + @Test + public void builder_allPropertiesSet() { + TcpKeepAliveConfiguration tcpKeepAliveConfiguration = + TcpKeepAliveConfiguration.builder() + .keepAliveInterval(Duration.ofMinutes(1)) + .keepAliveTimeout(Duration.ofSeconds(1)) + .build(); + + assertThat(tcpKeepAliveConfiguration.keepAliveInterval()).isEqualTo(Duration.ofMinutes(1)); + assertThat(tcpKeepAliveConfiguration.keepAliveTimeout()).isEqualTo(Duration.ofSeconds(1)); + } + + @Test + public void builder_nullKeepAliveTimeout_shouldThrowException() { + assertThatThrownBy(() -> + TcpKeepAliveConfiguration.builder() + .keepAliveInterval(Duration.ofMinutes(1)) + .build()) + .hasMessageContaining("keepAliveTimeout"); + } + + @Test + public void builder_nullKeepAliveInterval_shouldThrowException() { + assertThatThrownBy(() -> + TcpKeepAliveConfiguration.builder() + .keepAliveTimeout(Duration.ofSeconds(1)) + .build()) + .hasMessageContaining("keepAliveInterval"); + } + + @Test + public void builder_nonPositiveKeepAliveTimeout_shouldThrowException() { + assertThatThrownBy(() -> + TcpKeepAliveConfiguration.builder() + .keepAliveInterval(Duration.ofMinutes(1)) + .keepAliveTimeout(Duration.ofSeconds(0)) + .build()) + .hasMessageContaining("keepAliveTimeout"); + } + + @Test + public void builder_nonPositiveKeepAliveInterval_shouldThrowException() { + assertThatThrownBy(() -> + TcpKeepAliveConfiguration.builder() + .keepAliveInterval(Duration.ofMinutes(0)) + .keepAliveTimeout(Duration.ofSeconds(1)) + .build()) + .hasMessageContaining("keepAliveInterval"); + } +}