Skip to content

Commit e20603d

Browse files
committed
Add ConnectionReuseDemo
1 parent ab404c4 commit e20603d

File tree

1 file changed

+220
-0
lines changed

1 file changed

+220
-0
lines changed
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
28+
package org.apache.hc.client5.http.examples;
29+
30+
import org.apache.hc.client5.http.HttpRoute;
31+
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
32+
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
33+
import org.apache.hc.client5.http.config.ConnectionConfig;
34+
import org.apache.hc.client5.http.config.RequestConfig;
35+
import org.apache.hc.client5.http.config.TlsConfig;
36+
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
37+
import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
38+
import org.apache.hc.client5.http.impl.nio.DefaultManagedAsyncClientConnection;
39+
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
40+
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
41+
import org.apache.hc.core5.concurrent.FutureCallback;
42+
import org.apache.hc.core5.http.Method;
43+
import org.apache.hc.core5.http.nio.command.StaleCheckCommand;
44+
import org.apache.hc.core5.http2.HttpVersionPolicy;
45+
import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
46+
import org.apache.hc.core5.pool.PoolReusePolicy;
47+
import org.apache.hc.core5.util.TimeValue;
48+
import org.apache.hc.core5.util.Timeout;
49+
import org.apache.hc.core5.util.VersionInfo;
50+
import org.apache.logging.log4j.LogManager;
51+
import org.apache.logging.log4j.Logger;
52+
import org.apache.logging.log4j.core.config.Configurator;
53+
import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
54+
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
55+
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
56+
import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
57+
58+
import java.net.URI;
59+
import java.util.concurrent.Future;
60+
import java.util.concurrent.TimeUnit;
61+
import java.util.stream.Collectors;
62+
import java.util.stream.IntStream;
63+
64+
import static java.util.concurrent.TimeUnit.SECONDS;
65+
import static org.apache.logging.log4j.Level.DEBUG;
66+
import static org.apache.logging.log4j.Level.INFO;
67+
import static org.apache.logging.log4j.Level.WARN;
68+
69+
/**
70+
* This example demonstrates connection reuse, with a specific focus on what happens when there are not enough requests
71+
* in flight to keep all the connections active. There are several ways to configure a connection pool (see
72+
* {@link PoolReusePolicy}, {@link PoolConcurrencyPolicy}, and
73+
* {@link HttpAsyncClientBuilder#evictIdleConnections(TimeValue)}), and there are also numerous settings that affect
74+
* connection expiry, including:
75+
* <ul>
76+
* <li>{@link ConnectionConfig#getTimeToLive()}</li>
77+
* <li>{@link ConnectionConfig#getIdleTimeout()}</li>
78+
* <li>{@link ConnectionConfig#getValidateAfterInactivity()}</li>
79+
* <li>{@link RequestConfig#getConnectionKeepAlive()}</li>
80+
* </ul>
81+
* This example can be used to experiment with different config values in order to answer questions like:
82+
* <ul>
83+
* <li>Which connections get reused? Which ones expire?</li>
84+
* <li>Where are the various connection expiry settings implemented?</li>
85+
* <li>Do expired connections get leased out? If so, what happens?</li>
86+
* <li>When the connection pool is too large, does it shrink? If so, what size does it converge on?</li>
87+
* <li>Does inactive connection validation through {@link StaleCheckCommand} add latency?</li>
88+
* </ul>
89+
*/
90+
public class ConnectionReuseDemo {
91+
private static final Logger LOG;
92+
93+
static {
94+
final ConfigurationBuilder<BuiltConfiguration> config = ConfigurationBuilderFactory.newConfigurationBuilder();
95+
config.setStatusLevel(WARN);
96+
config.setConfigurationName("ConnectionReuseDemo");
97+
98+
final AppenderComponentBuilder console = config.newAppender("APPLICATION", "CONSOLE")
99+
.add(config.newLayout("PatternLayout")
100+
.addAttribute("pattern", "%d{HH:mm:ss.SSS} %highlight{[%p]} (%t) %C{1}: %m%n"));
101+
102+
config.add(console)
103+
.add(config.newRootLogger(INFO).add(config.newAppenderRef("APPLICATION")))
104+
.add(config.newLogger(DefaultManagedAsyncClientConnection.class.getName(), DEBUG));
105+
106+
Configurator.initialize(config.build()).start();
107+
108+
LOG = LogManager.getLogger(ConnectionReuseDemo.class);
109+
}
110+
111+
public static void main(final String[] args) throws InterruptedException {
112+
final ClassLoader cl = ConnectionReuseDemo.class.getClassLoader();
113+
LOG.info("Running client {}, core {}",
114+
VersionInfo.loadVersionInfo("org.apache.hc.client5", cl).getRelease(),
115+
VersionInfo.loadVersionInfo("org.apache.hc.core5", cl).getRelease());
116+
117+
final PoolConcurrencyPolicy concurrencyPolicy = PoolConcurrencyPolicy.OFFLOCK;
118+
final PoolReusePolicy reusePolicy = PoolReusePolicy.FIFO;
119+
final Timeout idleTimeout = null;
120+
final TimeValue timeToLive = null;
121+
final TimeValue validateAfterInactivity = TimeValue.ofSeconds(2);
122+
final TimeValue evictIdleConnections = null;
123+
final TimeValue connectionKeepAlive = TimeValue.ofSeconds(5);
124+
125+
LOG.info("Pool type: {} ({})", concurrencyPolicy, reusePolicy);
126+
LOG.info("Connection config: idleTimeout={}, timeToLive={}, validateAfterInactivity={}",
127+
idleTimeout, timeToLive, validateAfterInactivity);
128+
LOG.info("evictIdleConnections: {}", evictIdleConnections);
129+
LOG.info("connectionKeepAlive: {}", connectionKeepAlive);
130+
131+
final PoolingAsyncClientConnectionManager mgr = PoolingAsyncClientConnectionManagerBuilder.create()
132+
.setMaxConnPerRoute(Integer.MAX_VALUE)
133+
.setMaxConnTotal(Integer.MAX_VALUE)
134+
.setConnPoolPolicy(reusePolicy)
135+
.setPoolConcurrencyPolicy(concurrencyPolicy)
136+
.setDefaultTlsConfig(TlsConfig.custom()
137+
.setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)
138+
.build())
139+
.setDefaultConnectionConfig(ConnectionConfig.custom()
140+
.setConnectTimeout(60, SECONDS)
141+
.setSocketTimeout(60, SECONDS)
142+
.setTimeToLive(timeToLive)
143+
.setIdleTimeout(idleTimeout)
144+
.setValidateAfterInactivity(validateAfterInactivity)
145+
.build())
146+
.build();
147+
148+
final CloseableHttpAsyncClient client = getBuilder(evictIdleConnections)
149+
.disableAutomaticRetries()
150+
.setConnectionManager(mgr)
151+
.setDefaultRequestConfig(RequestConfig.custom()
152+
.setConnectionKeepAlive(connectionKeepAlive)
153+
.build())
154+
.build();
155+
156+
client.start();
157+
158+
LOG.info("Sending warmup request");
159+
join(call(client));
160+
final HttpRoute route = mgr.getRoutes().iterator().next();
161+
mgr.getStats(route);
162+
163+
LOG.info("Expanding connection pool");
164+
IntStream.range(0, 10)
165+
.mapToObj(unused -> call(client))
166+
.collect(Collectors.toList())
167+
.forEach(ConnectionReuseDemo::join);
168+
169+
LOG.info("{} connections available. Walking connection pool...", mgr.getStats(route).getAvailable());
170+
for (int i = 0; i < 10; i++) {
171+
Thread.sleep(1_000);
172+
LOG.info("Sending request {}; {} connections available", i + 1, mgr.getStats(route).getAvailable());
173+
final long startTime = System.nanoTime();
174+
join(call(client));
175+
LOG.info("Request took {} ms", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
176+
}
177+
178+
LOG.info("Waiting for all connections to expire");
179+
Thread.sleep(6_000);
180+
181+
LOG.info("Sending one last request (should establish a new connection)");
182+
final int before = mgr.getStats(route).getAvailable();
183+
join(call(client));
184+
LOG.info("Connections available: {} -> {}", before, mgr.getStats(route).getAvailable());
185+
}
186+
187+
private static HttpAsyncClientBuilder getBuilder(final TimeValue evictIdleConnections) {
188+
if (evictIdleConnections != null) {
189+
return HttpAsyncClientBuilder.create().evictIdleConnections(evictIdleConnections);
190+
}
191+
return HttpAsyncClientBuilder.create();
192+
}
193+
194+
private static <T> void join(final Future<T> f) {
195+
try {
196+
f.get();
197+
} catch (final Throwable ignore) {
198+
}
199+
}
200+
201+
private static Future<SimpleHttpResponse> call(final CloseableHttpAsyncClient client) {
202+
final SimpleHttpRequest req = SimpleHttpRequest.create(Method.GET, URI.create("https://www.amazon.co.jp/"));
203+
return client.execute(req,
204+
new FutureCallback<SimpleHttpResponse>() {
205+
@Override
206+
public void completed(final SimpleHttpResponse result) {
207+
}
208+
209+
@Override
210+
public void failed(final Exception ex) {
211+
LOG.error("Request failed", ex);
212+
}
213+
214+
@Override
215+
public void cancelled() {
216+
LOG.error("Request cancelled");
217+
}
218+
});
219+
}
220+
}

0 commit comments

Comments
 (0)