Add configuration setting for TTL of HTTP connections to IRestfulClientFactory (#6204)

* Add configuration setting for TTL of HTTP connections to IRestfulClientFactory

* Update changelog and pom.xml

* Modify changelog

---------

Co-authored-by: James Agnew <jamesagnew@gmail.com>
This commit is contained in:
acoteathn 2024-08-27 07:36:41 -07:00 committed by GitHub
parent 19af077c2a
commit 554bb08d01
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 98 additions and 12 deletions

View File

@ -37,6 +37,12 @@ public interface IRestfulClientFactory {
*/ */
public static final int DEFAULT_CONNECTION_REQUEST_TIMEOUT = 10000; public static final int DEFAULT_CONNECTION_REQUEST_TIMEOUT = 10000;
/**
* Default value for {@link #getConnectionTimeToLive()}
*/
public static final int DEFAULT_CONNECTION_TTL = 5000;
/** /**
* Default value for {@link #getServerValidationModeEnum()} * Default value for {@link #getServerValidationModeEnum()}
*/ */
@ -75,6 +81,17 @@ public interface IRestfulClientFactory {
*/ */
int getConnectTimeout(); int getConnectTimeout();
/**
* Gets the connection time to live, in milliseconds. This is the amount of time to keep connections alive for reuse.
* <p>
* The default value for this setting is defined by {@link #DEFAULT_CONNECTION_TTL}
* </p>
*/
default int getConnectionTimeToLive() {
return DEFAULT_CONNECTION_TTL;
}
/** /**
* Returns the HTTP client instance. This method will not return null. * Returns the HTTP client instance. This method will not return null.
* @param theUrl * @param theUrl
@ -179,6 +196,14 @@ public interface IRestfulClientFactory {
*/ */
void setConnectTimeout(int theConnectTimeout); void setConnectTimeout(int theConnectTimeout);
/**
* Sets the connection time to live, in milliseconds. This is the amount of time to keep connections alive for reuse.
* <p>
* The default value for this setting is defined by {@link #DEFAULT_CONNECTION_TTL}
* </p>
*/
default void setConnectionTimeToLive(int theConnectionTimeToLive) {}
/** /**
* Sets the Apache HTTP client instance to be used by any new restful clients created by this factory. If set to * Sets the Apache HTTP client instance to be used by any new restful clients created by this factory. If set to
* <code>null</code>, a new HTTP client with default settings will be created. * <code>null</code>, a new HTTP client with default settings will be created.

View File

@ -106,9 +106,9 @@ public class HapiFhirCliRestfulClientFactory extends RestfulClientFactory {
.register("https", sslConnectionSocketFactory) .register("https", sslConnectionSocketFactory)
.build(); .build();
connectionManager = connectionManager =
new PoolingHttpClientConnectionManager(registry, null, null, null, 5000, TimeUnit.MILLISECONDS); new PoolingHttpClientConnectionManager(registry, null, null, null, getConnectionTimeToLive(), TimeUnit.MILLISECONDS);
} else { } else {
connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); connectionManager = new PoolingHttpClientConnectionManager(getConnectionTimeToLive(), TimeUnit.MILLISECONDS);
} }
connectionManager.setMaxTotal(getPoolMaxTotal()); connectionManager.setMaxTotal(getPoolMaxTotal());

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory; import ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory;
import ca.uhn.fhir.rest.client.api.IRestfulClientFactory;
import ca.uhn.fhir.test.BaseFhirVersionParameterizedTest; import ca.uhn.fhir.test.BaseFhirVersionParameterizedTest;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
@ -10,6 +11,7 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
@ -79,4 +81,13 @@ public class ApacheRestfulClientFactoryTest extends BaseFhirVersionParameterized
assertEquals(SSLHandshakeException.class, e.getCause().getCause().getClass()); assertEquals(SSLHandshakeException.class, e.getCause().getCause().getClass());
} }
} }
@Test
public void testConnectionTimeToLive() {
ApacheRestfulClientFactory clientFactory = new ApacheRestfulClientFactory();
assertEquals(IRestfulClientFactory.DEFAULT_CONNECTION_TTL, clientFactory.getConnectionTimeToLive());
clientFactory.setConnectionTimeToLive(25000);
assertEquals(25000, clientFactory.getConnectionTimeToLive());
}
} }

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.cli.client;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.rest.client.api.IRestfulClientFactory;
import ca.uhn.fhir.test.BaseFhirVersionParameterizedTest; import ca.uhn.fhir.test.BaseFhirVersionParameterizedTest;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
@ -159,4 +160,15 @@ public class HapiFhirCliRestfulClientFactoryTest extends BaseFhirVersionParamete
} }
} }
@ParameterizedTest
@MethodSource("baseParamsProvider")
public void testConnectionTimeToLive(FhirVersionEnum theFhirVersion) {
FhirVersionParams fhirVersionParams = getFhirVersionParams(theFhirVersion);
HapiFhirCliRestfulClientFactory clientFactory = new HapiFhirCliRestfulClientFactory(fhirVersionParams.getFhirContext());
assertEquals(IRestfulClientFactory.DEFAULT_CONNECTION_TTL, clientFactory.getConnectionTimeToLive());
clientFactory.setConnectionTimeToLive(25000);
assertEquals(25000, clientFactory.getConnectionTimeToLive());
}
} }

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.rest.client.api.Header;
import ca.uhn.fhir.rest.client.api.IHttpClient; import ca.uhn.fhir.rest.client.api.IHttpClient;
import ca.uhn.fhir.rest.client.impl.RestfulClientFactory; import ca.uhn.fhir.rest.client.impl.RestfulClientFactory;
import okhttp3.Call; import okhttp3.Call;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -65,6 +66,7 @@ public class OkHttpRestfulClientFactory extends RestfulClientFactory {
myNativeClient = new OkHttpClient() myNativeClient = new OkHttpClient()
.newBuilder() .newBuilder()
.connectTimeout(getConnectTimeout(), TimeUnit.MILLISECONDS) .connectTimeout(getConnectTimeout(), TimeUnit.MILLISECONDS)
.connectionPool(new ConnectionPool(5, getConnectionTimeToLive(), TimeUnit.MILLISECONDS))
.readTimeout(getSocketTimeout(), TimeUnit.MILLISECONDS) .readTimeout(getSocketTimeout(), TimeUnit.MILLISECONDS)
.writeTimeout(getSocketTimeout(), TimeUnit.MILLISECONDS) .writeTimeout(getSocketTimeout(), TimeUnit.MILLISECONDS)
.build(); .build();

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.okhttp;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.okhttp.client.OkHttpRestfulClientFactory; import ca.uhn.fhir.okhttp.client.OkHttpRestfulClientFactory;
import ca.uhn.fhir.rest.client.api.IRestfulClientFactory;
import ca.uhn.fhir.test.BaseFhirVersionParameterizedTest; import ca.uhn.fhir.test.BaseFhirVersionParameterizedTest;
import okhttp3.Call; import okhttp3.Call;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -71,6 +72,13 @@ public class OkHttpRestfulClientFactoryTest extends BaseFhirVersionParameterized
assertEquals(1516, ((OkHttpClient) clientFactory.getNativeClient()).connectTimeoutMillis()); assertEquals(1516, ((OkHttpClient) clientFactory.getNativeClient()).connectTimeoutMillis());
} }
@Test
public void testConnectionTimeToLive() {
assertEquals(IRestfulClientFactory.DEFAULT_CONNECTION_TTL, clientFactory.getConnectionTimeToLive());
clientFactory.setConnectionTimeToLive(25000);
assertEquals(25000, clientFactory.getConnectionTimeToLive());
}
@ParameterizedTest @ParameterizedTest
@MethodSource("baseParamsProvider") @MethodSource("baseParamsProvider")
public void testNativeClientHttp(FhirVersionEnum theFhirVersion) throws Exception { public void testNativeClientHttp(FhirVersionEnum theFhirVersion) throws Exception {

View File

@ -103,7 +103,7 @@ public class ApacheRestfulClientFactory extends RestfulClientFactory {
.disableCookieManagement(); .disableCookieManagement();
PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); new PoolingHttpClientConnectionManager(getConnectionTimeToLive(), TimeUnit.MILLISECONDS);
connectionManager.setMaxTotal(getPoolMaxTotal()); connectionManager.setMaxTotal(getPoolMaxTotal());
connectionManager.setDefaultMaxPerRoute(getPoolMaxPerRoute()); connectionManager.setDefaultMaxPerRoute(getPoolMaxPerRoute());
builder.setConnectionManager(connectionManager); builder.setConnectionManager(connectionManager);

View File

@ -57,6 +57,7 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
private final Set<String> myValidatedServerBaseUrls = Collections.synchronizedSet(new HashSet<>()); private final Set<String> myValidatedServerBaseUrls = Collections.synchronizedSet(new HashSet<>());
private int myConnectionRequestTimeout = DEFAULT_CONNECTION_REQUEST_TIMEOUT; private int myConnectionRequestTimeout = DEFAULT_CONNECTION_REQUEST_TIMEOUT;
private int myConnectTimeout = DEFAULT_CONNECT_TIMEOUT; private int myConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
private int myConnectionTimeToLive = DEFAULT_CONNECTION_TTL;
private FhirContext myContext; private FhirContext myContext;
private final Map<Class<? extends IRestfulClient>, ClientInvocationHandlerFactory> myInvocationHandlers = private final Map<Class<? extends IRestfulClient>, ClientInvocationHandlerFactory> myInvocationHandlers =
new HashMap<>(); new HashMap<>();
@ -91,6 +92,11 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
return myConnectTimeout; return myConnectTimeout;
} }
@Override
public synchronized int getConnectionTimeToLive() {
return myConnectionTimeToLive;
}
/** /**
* Return the proxy username to authenticate with the HTTP proxy * Return the proxy username to authenticate with the HTTP proxy
*/ */
@ -210,6 +216,12 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
resetHttpClient(); resetHttpClient();
} }
@Override
public synchronized void setConnectionTimeToLive(int theConnectionTimeToLive) {
myConnectionTimeToLive = theConnectionTimeToLive;
resetHttpClient();
}
/** /**
* Sets the context associated with this client factory. Must not be called more than once. * Sets the context associated with this client factory. Must not be called more than once.
*/ */

View File

@ -0,0 +1,10 @@
---
type: add
issue: 6184
title: "Added a configuration setting for the TTL of HTTP connections to IRestfulClientFactory.
The following implementations have been updated to respect this new setting:
1. ApacheRestfulClientFactory
2. OkHttpRestfulClientFactory
3. HapiFhirCliRestfulClientFactory
Thanks to Alex Kopp and Alex Cote for the contribution!
"

24
pom.xml
View File

@ -919,15 +919,21 @@
<id>adriennesox</id> <id>adriennesox</id>
<name>Adrienne Sox</name> <name>Adrienne Sox</name>
<organization>Galileo, Inc.</organization> <organization>Galileo, Inc.</organization>
</developer> </developer>
<developer> <developer>
<id>melihaydogd</id> <id>melihaydogd</id>
<name>Ahmet Melih Aydoğdu</name> <name>Ahmet Melih Aydoğdu</name>
</developer> </developer>
<developer> <developer>
<id>alexrkopp</id> <id>alexrkopp</id>
<name>Alex Kopp</name> <name>Alex Kopp</name>
</developer> <organization>athenahealth</organization>
</developer>
<developer>
<id>acoteathn</id>
<name>Alex Cote</name>
<organization>athenahealth</organization>
</developer>
</developers> </developers>
<licenses> <licenses>