diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/RestTemplateTimeoutProperties.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/RestTemplateTimeoutProperties.java index 394420ddb..4fc8de3fc 100644 --- a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/RestTemplateTimeoutProperties.java +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/RestTemplateTimeoutProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,6 +45,8 @@ public class RestTemplateTimeoutProperties { private int socketTimeout = 3 * 60 * 1000; + private long idleTimeout = 30 * 60 * 1000L; + public int getConnectTimeout() { return connectTimeout; } @@ -69,6 +71,14 @@ public void setSocketTimeout(int socketTimeout) { this.socketTimeout = socketTimeout; } + public long getIdleTimeout() { + return idleTimeout; + } + + public void setIdleTimeout(long idleTimeout) { + this.idleTimeout = idleTimeout; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -81,18 +91,18 @@ public boolean equals(Object o) { RestTemplateTimeoutProperties that = (RestTemplateTimeoutProperties) o; return connectTimeout == that.connectTimeout && connectRequestTimeout == that.connectRequestTimeout - && socketTimeout == that.socketTimeout; + && socketTimeout == that.socketTimeout && idleTimeout == that.idleTimeout; } @Override public int hashCode() { - return Objects.hash(connectTimeout, connectRequestTimeout, socketTimeout); + return Objects.hash(connectTimeout, connectRequestTimeout, socketTimeout, idleTimeout); } @Override public String toString() { return "RestTemplateTimeoutProperties{" + ", connectTimeout=" + connectTimeout + ", connectRequestTimeout=" - + connectRequestTimeout + ", socketTimeout=" + socketTimeout + '}'; + + connectRequestTimeout + ", socketTimeout=" + socketTimeout + ", idleTimeout=" + idleTimeout + '}'; } } diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/DefaultEurekaClientHttpRequestFactorySupplier.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/DefaultEurekaClientHttpRequestFactorySupplier.java index 36bf1b35a..2592994bf 100644 --- a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/DefaultEurekaClientHttpRequestFactorySupplier.java +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/DefaultEurekaClientHttpRequestFactorySupplier.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2022 the original author or authors. + * Copyright 2013-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.cloud.netflix.eureka.http; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; @@ -29,8 +30,10 @@ import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder; import org.apache.hc.core5.http.io.SocketConfig; +import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; +import org.springframework.beans.factory.DisposableBean; import org.springframework.cloud.netflix.eureka.RestTemplateTimeoutProperties; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; @@ -45,7 +48,10 @@ * @author Jiwon Jeon * @since 3.0.0 */ -public class DefaultEurekaClientHttpRequestFactorySupplier implements EurekaClientHttpRequestFactorySupplier { +public class DefaultEurekaClientHttpRequestFactorySupplier + implements EurekaClientHttpRequestFactorySupplier, DisposableBean { + + private final AtomicReference ref = new AtomicReference<>(); private final RestTemplateTimeoutProperties restTemplateTimeoutProperties; @@ -64,7 +70,18 @@ public DefaultEurekaClientHttpRequestFactorySupplier(RestTemplateTimeoutProperti @Override public ClientHttpRequestFactory get(SSLContext sslContext, @Nullable HostnameVerifier hostnameVerifier) { - HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); + TimeValue timeValue; + + if (restTemplateTimeoutProperties != null) { + timeValue = TimeValue.ofMilliseconds(restTemplateTimeoutProperties.getIdleTimeout()); + } + else { + timeValue = TimeValue.of(30, TimeUnit.SECONDS); + } + + HttpClientBuilder httpClientBuilder = HttpClients.custom().evictExpiredConnections() + .evictIdleConnections(timeValue); + if (sslContext != null || hostnameVerifier != null || restTemplateTimeoutProperties != null) { httpClientBuilder.setConnectionManager( buildConnectionManager(sslContext, hostnameVerifier, restTemplateTimeoutProperties)); @@ -73,7 +90,11 @@ public ClientHttpRequestFactory get(SSLContext sslContext, @Nullable HostnameVer httpClientBuilder.setDefaultRequestConfig(buildRequestConfig()); } - CloseableHttpClient httpClient = httpClientBuilder.build(); + if (ref.get() == null) { + ref.compareAndSet(null, httpClientBuilder.build()); + } + + CloseableHttpClient httpClient = ref.get(); HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); requestFactory.setHttpClient(httpClient); return requestFactory; @@ -108,4 +129,13 @@ private RequestConfig buildRequestConfig() { .build(); } + @Override + public void destroy() throws Exception { + CloseableHttpClient httpClient = ref.get(); + + if (httpClient != null) { + httpClient.close(); + } + } + }