|
5 | 5 | import java.net.InetSocketAddress; |
6 | 6 | import java.net.PasswordAuthentication; |
7 | 7 | import java.net.ProxySelector; |
| 8 | +import java.net.URI; |
8 | 9 | import java.net.http.HttpClient; |
9 | 10 | import java.nio.CharBuffer; |
10 | 11 | import java.time.Duration; |
| 12 | +import java.util.HashMap; |
| 13 | +import java.util.Map; |
11 | 14 | import java.util.concurrent.ConcurrentHashMap; |
12 | 15 | import java.util.concurrent.ConcurrentMap; |
13 | 16 | import java.util.concurrent.Executor; |
|
26 | 29 |
|
27 | 30 | class HttpClientFactory |
28 | 31 | { |
| 32 | + private static final long IDLE_TIMEOUT_MS = Long.getLong( "com.enonic.lib.http.client.idle.timeout", 30000 ); |
| 33 | + |
29 | 34 | private static final long DEFAULT_CONNECT_TIMEOUT_MS = 10_000; |
30 | 35 |
|
31 | 36 | private static final int DEFAULT_PROXY_PORT = 8080; |
32 | 37 |
|
33 | | - private static final ConcurrentMap<String, HttpClient> CACHE = new ConcurrentHashMap<>(); |
| 38 | + private static final ConcurrentMap<String, HttpClientWrapper> CACHE = new ConcurrentHashMap<>(); |
34 | 39 |
|
35 | 40 | private static final Executor SHARED_WORKERS_EXECUTOR = Executors.newCachedThreadPool( new SharedWorkerThreadFactory() ); |
36 | 41 |
|
37 | 42 | private HttpClientFactory() |
38 | 43 | { |
39 | 44 | } |
40 | 45 |
|
| 46 | + private static class HttpClientWrapper |
| 47 | + { |
| 48 | + final HttpClient client; |
| 49 | + |
| 50 | + final Map<String, Long> lastAccess; |
| 51 | + |
| 52 | + public HttpClientWrapper( final HttpClient client, Map<String, Long> lastAccess ) |
| 53 | + { |
| 54 | + this.client = client; |
| 55 | + this.lastAccess = lastAccess; |
| 56 | + } |
| 57 | + } |
| 58 | + |
41 | 59 | static class ClientParams |
42 | 60 | { |
43 | 61 | final Duration connectTimeout; |
@@ -212,9 +230,54 @@ static void clearCache() |
212 | 230 | CACHE.clear(); |
213 | 231 | } |
214 | 232 |
|
215 | | - static HttpClient getHttpClient( ClientParams params ) |
| 233 | + static HttpClient getHttpClient( final ClientParams params, final URI uri ) |
216 | 234 | { |
217 | | - return CACHE.computeIfAbsent( cacheKey( params ), c -> createClient( params ) ); |
| 235 | + return CACHE.compute( cacheKey( params ), ( key, old ) -> { |
| 236 | + if ( IDLE_TIMEOUT_MS <= 0 ) |
| 237 | + { |
| 238 | + return old != null ? old : new HttpClientWrapper( createClient( params ), null ); |
| 239 | + } |
| 240 | + final String keyForRequest = keyForRequest( uri, params.proxy ); |
| 241 | + final long currentTimeMillis = System.currentTimeMillis(); |
| 242 | + |
| 243 | + final Long lastAccess = old != null ? old.lastAccess.get( keyForRequest ) : null; |
| 244 | + |
| 245 | + if ( old != null && ( lastAccess == null || lastAccess > currentTimeMillis - IDLE_TIMEOUT_MS ) ) |
| 246 | + { |
| 247 | + old.lastAccess.put( keyForRequest, currentTimeMillis ); |
| 248 | + return old; |
| 249 | + } |
| 250 | + else |
| 251 | + { |
| 252 | + return new HttpClientWrapper( createClient( params ), new HashMap<>( Map.of( keyForRequest, currentTimeMillis ) ) ); |
| 253 | + } |
| 254 | + } ).client; |
| 255 | + } |
| 256 | + |
| 257 | + private static String keyForRequest( final URI uri, final InetSocketAddress proxy ) |
| 258 | + { |
| 259 | + final boolean isSecure = Utils.isSecure( uri ); |
| 260 | + final String host = uri.getHost(); |
| 261 | + final int port = uri.getPort() == -1 ? ( isSecure ? 443 : 80 ) : uri.getPort(); |
| 262 | + |
| 263 | + final StringBuilder keyBuilder = new StringBuilder( isSecure ? "S:" : "C:" ); |
| 264 | + |
| 265 | + if ( proxy == null ) |
| 266 | + { |
| 267 | + keyBuilder.append( "H:" ).append( host ).append( ":" ).append( port ); |
| 268 | + } |
| 269 | + else |
| 270 | + { |
| 271 | + if ( isSecure ) |
| 272 | + { |
| 273 | + keyBuilder.append( "T:H:" ).append( host ).append( ":" ).append( port ).append( ";" ); |
| 274 | + } |
| 275 | + final String proxyHost = proxy.getHostString(); |
| 276 | + final int proxyPort = proxy.getPort(); |
| 277 | + |
| 278 | + keyBuilder.append( "P:" ).append( proxyHost ).append( ":" ).append( proxyPort ); |
| 279 | + } |
| 280 | + return keyBuilder.toString(); |
218 | 281 | } |
219 | 282 |
|
220 | 283 | private static String cacheKey( final ClientParams params ) |
|
0 commit comments