From a1a8182d498ab713eba733acbdee97a93648a6ce Mon Sep 17 00:00:00 2001 From: Alan Zimmer <48699787+alzimmermsft@users.noreply.github.com> Date: Fri, 11 Jun 2021 10:51:21 -0700 Subject: [PATCH 1/4] Additional HttpClientOption Configurations --- .../netty/NettyAsyncHttpClientProvider.java | 13 +++ .../okhttp/OkHttpAsyncClientProvider.java | 12 +++ .../azure/core/util/HttpClientOptions.java | 84 +++++++++++++++++++ 3 files changed, 109 insertions(+) diff --git a/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/NettyAsyncHttpClientProvider.java b/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/NettyAsyncHttpClientProvider.java index c0e6f71fc43b2..772e40009be25 100644 --- a/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/NettyAsyncHttpClientProvider.java +++ b/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/NettyAsyncHttpClientProvider.java @@ -6,6 +6,7 @@ import com.azure.core.http.HttpClient; import com.azure.core.http.HttpClientProvider; import com.azure.core.util.HttpClientOptions; +import reactor.netty.resources.ConnectionProvider; /** * An {@link HttpClientProvider} that provides an implementation of HttpClient based on Netty. @@ -27,6 +28,18 @@ public HttpClient createInstance(HttpClientOptions clientOptions) { .writeTimeout(clientOptions.getWriteTimeout()) .responseTimeout(clientOptions.getResponseTimeout()) .readTimeout(clientOptions.getReadTimeout()); + + int maximumConnectionPoolSize = clientOptions.getMaximumConnectionPoolSize(); + + ConnectionProvider.Builder connectionProviderBuilder = ConnectionProvider.builder("azure-sdk") + .maxIdleTime(clientOptions.getConnectionIdleTimeout()); + + // Only configure the maximum connections if it has been set in the options. + if (maximumConnectionPoolSize > 0) { + connectionProviderBuilder = connectionProviderBuilder.maxConnections(maximumConnectionPoolSize); + } + + builder = builder.connectionProvider(connectionProviderBuilder.build()); } return builder.build(); diff --git a/sdk/core/azure-core-http-okhttp/src/main/java/com/azure/core/http/okhttp/OkHttpAsyncClientProvider.java b/sdk/core/azure-core-http-okhttp/src/main/java/com/azure/core/http/okhttp/OkHttpAsyncClientProvider.java index dde8852a69cc5..54d73ef487b57 100644 --- a/sdk/core/azure-core-http-okhttp/src/main/java/com/azure/core/http/okhttp/OkHttpAsyncClientProvider.java +++ b/sdk/core/azure-core-http-okhttp/src/main/java/com/azure/core/http/okhttp/OkHttpAsyncClientProvider.java @@ -6,6 +6,9 @@ import com.azure.core.http.HttpClient; import com.azure.core.util.HttpClientOptions; import com.azure.core.http.HttpClientProvider; +import okhttp3.ConnectionPool; + +import java.util.concurrent.TimeUnit; /** * An {@link HttpClientProvider} that provides an implementation of HttpClient based on OkHttp. @@ -26,6 +29,15 @@ public HttpClient createInstance(HttpClientOptions clientOptions) { .configuration(clientOptions.getConfiguration()) .writeTimeout(clientOptions.getWriteTimeout()) .readTimeout(clientOptions.getReadTimeout()); + + int maximumConnectionPoolSize = (clientOptions.getMaximumConnectionPoolSize() == 0) + ? 5 // By default OkHttp uses a maximum idle connection count of 5. + : clientOptions.getMaximumConnectionPoolSize(); + + ConnectionPool connectionPool = new ConnectionPool(maximumConnectionPoolSize, + clientOptions.getConnectionIdleTimeout().toMillis(), TimeUnit.MILLISECONDS); + + builder = builder.connectionPool(connectionPool); } return builder.build(); diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/HttpClientOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/util/HttpClientOptions.java index bb9a1c9abfe47..b4f1402e46808 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/HttpClientOptions.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/util/HttpClientOptions.java @@ -6,11 +6,14 @@ import com.azure.core.annotation.Fluent; import com.azure.core.http.HttpClient; import com.azure.core.http.ProxyOptions; +import com.azure.core.util.logging.ClientLogger; import java.time.Duration; /** * General configuration options for {@link HttpClient HttpClients}. + *
+ * {@link HttpClient} implementations may not support all configuration options in this class. */ @Fluent public final class HttpClientOptions extends ClientOptions { @@ -18,11 +21,15 @@ public final class HttpClientOptions extends ClientOptions { private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(60); private static final Duration NO_TIMEOUT = Duration.ZERO; + private final ClientLogger logger = new ClientLogger(HttpClientOptions.class); + private ProxyOptions proxyOptions; private Configuration configuration; private Duration writeTimeout; private Duration responseTimeout; private Duration readTimeout; + private int maximumConnectionPoolSize; + private Duration connectionIdleTimeout; @Override public HttpClientOptions setApplicationId(String applicationId) { @@ -88,6 +95,8 @@ public Configuration getConfiguration() { * If {@code writeTimeout} is {@code null} a 60 second timeout will be used, if it is a {@link Duration} less than * or equal to zero then no write timeout will be applied. When applying the timeout the greater of one millisecond * and the value of {@code writeTimeout} will be used. + *
+ * By default the write timeout is 60 seconds. * * @param writeTimeout Write operation timeout duration. * @return The updated HttpClientOptions object. @@ -99,6 +108,8 @@ public HttpClientOptions setWriteTimeout(Duration writeTimeout) { /** * Gets the write timeout for a request to be sent. + *
+ * By default the write timeout is 60 seconds. * * @return The write timeout of a request to be sent. */ @@ -115,6 +126,8 @@ public Duration getWriteTimeout() { * If {@code responseTimeout} is {@code null} a 60 second timeout will be used, if it is a {@link Duration} less * than or equal to zero then no timeout will be applied to the response. When applying the timeout the greater of * one millisecond and the value of {@code responseTimeout} will be used. + *
+ * By default the response timeout is 60 seconds. * * @param responseTimeout Response timeout duration. * @return The updated HttpClientOptions object. @@ -133,6 +146,8 @@ public HttpClientOptions responseTimeout(Duration responseTimeout) { * If {@code responseTimeout} is {@code null} a 60 second timeout will be used, if it is a {@link Duration} less * than or equal to zero then no timeout will be applied to the response. When applying the timeout the greater of * one millisecond and the value of {@code responseTimeout} will be used. + *
+ * By default the response timeout is 60 seconds. * * @param responseTimeout Response timeout duration. * @return The updated HttpClientOptions object. @@ -144,6 +159,8 @@ public HttpClientOptions setResponseTimeout(Duration responseTimeout) { /** * Gets the response timeout duration used when waiting for a server to reply. + *
+ * By default the response timeout is 60 seconds. * * @return The response timeout duration. */ @@ -161,6 +178,8 @@ public Duration getResponseTimeout() { * If {@code readTimeout} is {@code null} a 60 second timeout will be used, if it is a {@link Duration} less than or * equal to zero then no timeout period will be applied to response read. When applying the timeout the greater of * one millisecond and the value of {@code readTimeout} will be used. + *
+ * By default the read timeout is 60 seconds. * * @param readTimeout Read timeout duration. * @return The updated HttpClientOptions object. @@ -180,6 +199,8 @@ public HttpClientOptions readTimeout(Duration readTimeout) { * If {@code readTimeout} is {@code null} a 60 second timeout will be used, if it is a {@link Duration} less than or * equal to zero then no timeout period will be applied to response read. When applying the timeout the greater of * one millisecond and the value of {@code readTimeout} will be used. + *
+ * By default the read timeout is 60 seconds. * * @param readTimeout Read timeout duration. * @return The updated HttpClientOptions object. @@ -191,6 +212,8 @@ public HttpClientOptions setReadTimeout(Duration readTimeout) { /** * Gets the read timeout duration used when reading the server response. + *
+ * By default the read timeout is 60 seconds. * * @return The read timeout duration. */ @@ -198,6 +221,67 @@ public Duration getReadTimeout() { return getTimeout(readTimeout); } + /** + * Sets the maximum connection pool size used by the underlying HTTP client. + *
+ * By default the maximum connection pool size is determined by the underlying HTTP client. + * + * @param maximumConnectionPoolSize The maximum connection pool size. + * @return The updated HttpClientOptions object. + * @throws IllegalArgumentException If {@code maximumConnectionPoolSize} is less than {@code 1}. + */ + public HttpClientOptions setMaximumConnectionPoolSize(int maximumConnectionPoolSize) { + if (maximumConnectionPoolSize <= 0) { + throw logger.logExceptionAsError( + new IllegalArgumentException("'maximumConnectionPoolSize' cannot be less than 1.")); + } + + this.maximumConnectionPoolSize = maximumConnectionPoolSize; + return this; + } + + /** + * Gets the maximum connection pool size used by the underlying HTTP client. + *
+ * By default the maximum connection pool size is determined by the underlying HTTP client. + * + * @return The maximum connection pool size. + */ + public int getMaximumConnectionPoolSize() { + return maximumConnectionPoolSize; + } + + /** + * Sets the duration of time before an idle connection. + *
+ * The connection idle timeout begins once the connection has completed its last network request. Every time the + * connection is used the idle timeout will reset. + *
+ * If {@code connectionIdleTimeout} is {@code null} a 60 second timeout will be used, if it is a {@link Duration} + * less than or equal to zero then no timeout period will be applied. When applying the timeout the greater of one + * millisecond and the value of {@code connectionIdleTimeout} will be used. + *
+ * By default the connection idle timeout is 60 seconds. + * + * @param connectionIdleTimeout The connection idle timeout duration. + * @return The updated HttpClientOptions object. + */ + public HttpClientOptions setConnectionIdleTimeout(Duration connectionIdleTimeout) { + this.connectionIdleTimeout = connectionIdleTimeout; + return this; + } + + /** + * Gets the duration of time before an idle connection is closed. + *
+ * By default the connection idle timeout is 60 seconds. + * + * @return The connection idel timeout duration. + */ + public Duration getConnectionIdleTimeout() { + return getTimeout(connectionIdleTimeout); + } + private static Duration getTimeout(Duration timeout) { // Timeout is null, use the 60 second default. if (timeout == null) { From fd2c334b1ba6cc20a34317e0f6492376337e3115 Mon Sep 17 00:00:00 2001 From: Alan Zimmer <48699787+alzimmermsft@users.noreply.github.com> Date: Fri, 11 Jun 2021 11:52:47 -0700 Subject: [PATCH 2/4] Fix linting issue --- .../azure/core/http/netty/NettyAsyncHttpClientProvider.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/NettyAsyncHttpClientProvider.java b/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/NettyAsyncHttpClientProvider.java index 772e40009be25..fbbe26b42a4a2 100644 --- a/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/NettyAsyncHttpClientProvider.java +++ b/sdk/core/azure-core-http-netty/src/main/java/com/azure/core/http/netty/NettyAsyncHttpClientProvider.java @@ -31,12 +31,12 @@ public HttpClient createInstance(HttpClientOptions clientOptions) { int maximumConnectionPoolSize = clientOptions.getMaximumConnectionPoolSize(); - ConnectionProvider.Builder connectionProviderBuilder = ConnectionProvider.builder("azure-sdk") - .maxIdleTime(clientOptions.getConnectionIdleTimeout()); + ConnectionProvider.Builder connectionProviderBuilder = ConnectionProvider.builder("azure-sdk"); + connectionProviderBuilder.maxIdleTime(clientOptions.getConnectionIdleTimeout()); // Only configure the maximum connections if it has been set in the options. if (maximumConnectionPoolSize > 0) { - connectionProviderBuilder = connectionProviderBuilder.maxConnections(maximumConnectionPoolSize); + connectionProviderBuilder.maxConnections(maximumConnectionPoolSize); } builder = builder.connectionProvider(connectionProviderBuilder.build()); From c15c9f6f2eba7ff0296f1fe63269359b7c781b5c Mon Sep 17 00:00:00 2001 From: Alan Zimmer <48699787+alzimmermsft@users.noreply.github.com> Date: Tue, 15 Jun 2021 11:15:47 -0700 Subject: [PATCH 3/4] Added tests and documentation updates --- .../okhttp/OkHttpAsyncClientProvider.java | 2 +- .../azure/core/util/HttpClientOptions.java | 34 +++-- .../core/util/HttpClientOptionsTests.java | 117 ++++++++++++++++++ 3 files changed, 144 insertions(+), 9 deletions(-) create mode 100644 sdk/core/azure-core/src/test/java/com/azure/core/util/HttpClientOptionsTests.java diff --git a/sdk/core/azure-core-http-okhttp/src/main/java/com/azure/core/http/okhttp/OkHttpAsyncClientProvider.java b/sdk/core/azure-core-http-okhttp/src/main/java/com/azure/core/http/okhttp/OkHttpAsyncClientProvider.java index 54d73ef487b57..4876a4db82d9c 100644 --- a/sdk/core/azure-core-http-okhttp/src/main/java/com/azure/core/http/okhttp/OkHttpAsyncClientProvider.java +++ b/sdk/core/azure-core-http-okhttp/src/main/java/com/azure/core/http/okhttp/OkHttpAsyncClientProvider.java @@ -4,8 +4,8 @@ package com.azure.core.http.okhttp; import com.azure.core.http.HttpClient; -import com.azure.core.util.HttpClientOptions; import com.azure.core.http.HttpClientProvider; +import com.azure.core.util.HttpClientOptions; import okhttp3.ConnectionPool; import java.util.concurrent.TimeUnit; diff --git a/sdk/core/azure-core/src/main/java/com/azure/core/util/HttpClientOptions.java b/sdk/core/azure-core/src/main/java/com/azure/core/util/HttpClientOptions.java index b4f1402e46808..1179cfaf943b7 100644 --- a/sdk/core/azure-core/src/main/java/com/azure/core/util/HttpClientOptions.java +++ b/sdk/core/azure-core/src/main/java/com/azure/core/util/HttpClientOptions.java @@ -28,7 +28,7 @@ public final class HttpClientOptions extends ClientOptions { private Duration writeTimeout; private Duration responseTimeout; private Duration readTimeout; - private int maximumConnectionPoolSize; + private Integer maximumConnectionPoolSize; private Duration connectionIdleTimeout; @Override @@ -224,14 +224,23 @@ public Duration getReadTimeout() { /** * Sets the maximum connection pool size used by the underlying HTTP client. *
- * By default the maximum connection pool size is determined by the underlying HTTP client. + * Modifying the maximum connection pool size may have effects on the performance of an application. Increasing the + * maximum connection pool will result in more connections being available for an application but may result in + * more contention for network resources. It is recommended to perform performance analysis on different maximum + * connection pool sizes to find the right configuration for an application. + *
+ * This maximum connection pool size is not a global configuration but an instance level configuration for each + * {@link HttpClient} created using this {@link HttpClientOptions}. + *
+ * By default the maximum connection pool size is determined by the underlying HTTP client. Setting the maximum + * connection pool size resets the configuration to use the default determined by the underlying HTTP client. * * @param maximumConnectionPoolSize The maximum connection pool size. * @return The updated HttpClientOptions object. - * @throws IllegalArgumentException If {@code maximumConnectionPoolSize} is less than {@code 1}. + * @throws IllegalArgumentException If {@code maximumConnectionPoolSize} is not null and is less than {@code 1}. */ - public HttpClientOptions setMaximumConnectionPoolSize(int maximumConnectionPoolSize) { - if (maximumConnectionPoolSize <= 0) { + public HttpClientOptions setMaximumConnectionPoolSize(Integer maximumConnectionPoolSize) { + if (maximumConnectionPoolSize != null && maximumConnectionPoolSize <= 0) { throw logger.logExceptionAsError( new IllegalArgumentException("'maximumConnectionPoolSize' cannot be less than 1.")); } @@ -243,11 +252,20 @@ public HttpClientOptions setMaximumConnectionPoolSize(int maximumConnectionPoolS /** * Gets the maximum connection pool size used by the underlying HTTP client. *
- * By default the maximum connection pool size is determined by the underlying HTTP client. + * Modifying the maximum connection pool size may have effects on the performance of an application. Increasing the + * maximum connection pool will result in more connections being available for an application but may result in + * more contention for network resources. It is recommended to perform performance analysis on different maximum + * connection pool sizes to find the right configuration for an application. + *
+ * This maximum connection pool size is not a global configuration but an instance level configuration for each + * {@link HttpClient} created using this {@link HttpClientOptions}. + *
+ * By default the maximum connection pool size is determined by the underlying HTTP client. Setting the maximum + * connection pool size resets the configuration to use the default determined by the underlying HTTP client. * * @return The maximum connection pool size. */ - public int getMaximumConnectionPoolSize() { + public Integer getMaximumConnectionPoolSize() { return maximumConnectionPoolSize; } @@ -276,7 +294,7 @@ public HttpClientOptions setConnectionIdleTimeout(Duration connectionIdleTimeout *
* By default the connection idle timeout is 60 seconds.
*
- * @return The connection idel timeout duration.
+ * @return The connection idle timeout duration.
*/
public Duration getConnectionIdleTimeout() {
return getTimeout(connectionIdleTimeout);
diff --git a/sdk/core/azure-core/src/test/java/com/azure/core/util/HttpClientOptionsTests.java b/sdk/core/azure-core/src/test/java/com/azure/core/util/HttpClientOptionsTests.java
new file mode 100644
index 0000000000000..afad48e5d9edb
--- /dev/null
+++ b/sdk/core/azure-core/src/test/java/com/azure/core/util/HttpClientOptionsTests.java
@@ -0,0 +1,117 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.core.util;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.time.Duration;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Tests {@link HttpClientOptions}.
+ */
+public class HttpClientOptionsTests {
+ @ParameterizedTest
+ @MethodSource("timeoutSupplier")
+ public void nullTimeoutDefaultsTo60Seconds(BiFunction
* Modifying the maximum connection pool size may have effects on the performance of an application. Increasing the
- * maximum connection pool will result in more connections being available for an application but may result in
- * more contention for network resources. It is recommended to perform performance analysis on different maximum
+ * maximum connection pool will result in more connections being available for an application but may result in more
+ * contention for network resources. It is recommended to perform performance analysis on different maximum
* connection pool sizes to find the right configuration for an application.
*
* This maximum connection pool size is not a global configuration but an instance level configuration for each
* {@link HttpClient} created using this {@link HttpClientOptions}.
*
* By default the maximum connection pool size is determined by the underlying HTTP client. Setting the maximum
- * connection pool size resets the configuration to use the default determined by the underlying HTTP client.
+ * connection pool size to null resets the configuration to use the default determined by the underlying HTTP
+ * client.
*
* @param maximumConnectionPoolSize The maximum connection pool size.
* @return The updated HttpClientOptions object.
@@ -253,15 +254,16 @@ public HttpClientOptions setMaximumConnectionPoolSize(Integer maximumConnectionP
* Gets the maximum connection pool size used by the underlying HTTP client.
*
* Modifying the maximum connection pool size may have effects on the performance of an application. Increasing the
- * maximum connection pool will result in more connections being available for an application but may result in
- * more contention for network resources. It is recommended to perform performance analysis on different maximum
+ * maximum connection pool will result in more connections being available for an application but may result in more
+ * contention for network resources. It is recommended to perform performance analysis on different maximum
* connection pool sizes to find the right configuration for an application.
*
* This maximum connection pool size is not a global configuration but an instance level configuration for each
* {@link HttpClient} created using this {@link HttpClientOptions}.
*
* By default the maximum connection pool size is determined by the underlying HTTP client. Setting the maximum
- * connection pool size resets the configuration to use the default determined by the underlying HTTP client.
+ * connection pool size to null resets the configuration to use the default determined by the underlying HTTP
+ * client.
*
* @return The maximum connection pool size.
*/