1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.client5.http.impl.io;
29
30 import java.net.InetAddress;
31 import java.net.InetSocketAddress;
32 import java.net.Socket;
33 import java.util.Collections;
34 import java.util.concurrent.ExecutorService;
35 import java.util.concurrent.Executors;
36 import java.util.concurrent.Future;
37 import java.util.concurrent.TimeUnit;
38 import java.util.concurrent.TimeoutException;
39
40 import javax.net.ssl.SSLSocket;
41
42 import org.apache.hc.client5.http.DnsResolver;
43 import org.apache.hc.client5.http.HttpRoute;
44 import org.apache.hc.client5.http.SchemePortResolver;
45 import org.apache.hc.client5.http.config.ConnectionConfig;
46 import org.apache.hc.client5.http.config.TlsConfig;
47 import org.apache.hc.client5.http.io.ConnectionEndpoint;
48 import org.apache.hc.client5.http.io.DetachedSocketFactory;
49 import org.apache.hc.client5.http.io.LeaseRequest;
50 import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
51 import org.apache.hc.client5.http.protocol.HttpClientContext;
52 import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
53 import org.apache.hc.core5.http.HttpHost;
54 import org.apache.hc.core5.http.config.Lookup;
55 import org.apache.hc.core5.http.io.SocketConfig;
56 import org.apache.hc.core5.pool.PoolEntry;
57 import org.apache.hc.core5.pool.StrictConnPool;
58 import org.apache.hc.core5.util.TimeValue;
59 import org.apache.hc.core5.util.Timeout;
60 import org.junit.jupiter.api.Assertions;
61 import org.junit.jupiter.api.BeforeEach;
62 import org.junit.jupiter.api.Test;
63 import org.mockito.Mock;
64 import org.mockito.Mockito;
65 import org.mockito.MockitoAnnotations;
66
67
68
69
70 class TestPoolingHttpClientConnectionManager {
71
72 @Mock
73 private ManagedHttpClientConnection conn;
74 @Mock
75 private Lookup<TlsSocketStrategy> tlsSocketStrategyLookup;
76 @Mock
77 private DetachedSocketFactory detachedSocketFactory;
78 @Mock
79 private TlsSocketStrategy tlsSocketStrategy;
80 @Mock
81 private Socket socket;
82 @Mock
83 private SSLSocket upgradedSocket;
84 @Mock
85 private SchemePortResolver schemePortResolver;
86 @Mock
87 private DnsResolver dnsResolver;
88 @Mock
89 private Future<PoolEntry<HttpRoute, ManagedHttpClientConnection>> future;
90 @Mock
91 private StrictConnPool<HttpRoute, ManagedHttpClientConnection> pool;
92
93 private PoolingHttpClientConnectionManager mgr;
94
95 @BeforeEach
96 void setup() {
97 MockitoAnnotations.openMocks(this);
98 mgr = new PoolingHttpClientConnectionManager(new DefaultHttpClientConnectionOperator(
99 detachedSocketFactory, schemePortResolver, dnsResolver, tlsSocketStrategyLookup), pool,
100 null);
101 }
102
103 @Test
104 void testLeaseRelease() throws Exception {
105 final HttpHost target = new HttpHost("localhost", 80);
106 final HttpRoute route = new HttpRoute(target);
107
108 final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry = new PoolEntry<>(route, TimeValue.NEG_ONE_MILLISECOND);
109 entry.assignConnection(conn);
110
111 Mockito.when(conn.isOpen()).thenReturn(true);
112 Mockito.when(conn.isConsistent()).thenReturn(true);
113 Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry);
114 Mockito.when(pool.lease(
115 Mockito.eq(route),
116 Mockito.eq(null),
117 Mockito.any(),
118 Mockito.eq(null)))
119 .thenReturn(future);
120
121 final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
122 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ofSeconds(1));
123 Assertions.assertNotNull(endpoint1);
124 Assertions.assertNotSame(conn, endpoint1);
125
126 mgr.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
127
128 Mockito.verify(pool).release(entry, true);
129 }
130
131 @Test
132 void testReleaseRouteIncomplete() throws Exception {
133 final HttpHost target = new HttpHost("localhost", 80);
134 final HttpRoute route = new HttpRoute(target);
135
136 final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry = new PoolEntry<>(route, TimeValue.NEG_ONE_MILLISECOND);
137
138 Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry);
139 Mockito.when(pool.lease(
140 Mockito.eq(route),
141 Mockito.eq(null),
142 Mockito.any(),
143 Mockito.eq(null)))
144 .thenReturn(future);
145
146 final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
147 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ofSeconds(1));
148 Assertions.assertNotNull(endpoint1);
149 Assertions.assertNotSame(conn, endpoint1);
150
151 mgr.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
152
153 Mockito.verify(pool).release(entry, false);
154 }
155
156 @Test
157 void testLeaseFutureTimeout() throws Exception {
158 final HttpHost target = new HttpHost("localhost", 80);
159 final HttpRoute route = new HttpRoute(target);
160
161 Mockito.when(future.get(1, TimeUnit.SECONDS)).thenThrow(new TimeoutException());
162 Mockito.when(pool.lease(
163 Mockito.eq(route),
164 Mockito.eq(null),
165 Mockito.any(),
166 Mockito.eq(null)))
167 .thenReturn(future);
168
169 final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
170 Assertions.assertThrows(TimeoutException.class, () ->
171 connRequest1.get(Timeout.ofSeconds(1)));
172 }
173
174 @Test
175 void testReleaseReusable() throws Exception {
176 final HttpHost target = new HttpHost("localhost", 80);
177 final HttpRoute route = new HttpRoute(target);
178
179 final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry = new PoolEntry<>(route, TimeValue.NEG_ONE_MILLISECOND);
180 entry.assignConnection(conn);
181
182 Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry);
183 Mockito.when(pool.lease(
184 Mockito.eq(route),
185 Mockito.eq(null),
186 Mockito.any(),
187 Mockito.eq(null)))
188 .thenReturn(future);
189 Mockito.when(conn.isOpen()).thenReturn(true);
190 Mockito.when(conn.isConsistent()).thenReturn(true);
191
192 final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
193 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ofSeconds(1));
194 Assertions.assertNotNull(endpoint1);
195 Assertions.assertTrue(endpoint1.isConnected());
196
197 mgr.release(endpoint1, "some state", TimeValue.NEG_ONE_MILLISECOND);
198
199 Mockito.verify(pool).release(entry, true);
200 Assertions.assertEquals("some state", entry.getState());
201 }
202
203 @Test
204 void testReleaseNonReusable() throws Exception {
205 final HttpHost target = new HttpHost("localhost", 80);
206 final HttpRoute route = new HttpRoute(target);
207
208 final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry = new PoolEntry<>(route, TimeValue.NEG_ONE_MILLISECOND);
209 entry.assignConnection(conn);
210
211 Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry);
212 Mockito.when(pool.lease(
213 Mockito.eq(route),
214 Mockito.eq(null),
215 Mockito.any(),
216 Mockito.eq(null)))
217 .thenReturn(future);
218 Mockito.when(conn.isOpen()).thenReturn(Boolean.FALSE);
219
220 final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
221 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ofSeconds(1));
222 Assertions.assertNotNull(endpoint1);
223 Assertions.assertFalse(endpoint1.isConnected());
224
225 mgr.release(endpoint1, "some state", TimeValue.NEG_ONE_MILLISECOND);
226
227 Mockito.verify(pool).release(entry, false);
228 Assertions.assertNull(entry.getState());
229 }
230
231 @Test
232 void testTargetConnect() throws Exception {
233 final HttpHost target = new HttpHost("https", "somehost", 443);
234 final InetAddress remote = InetAddress.getByAddress(new byte[] {10, 0, 0, 1});
235 final InetAddress local = InetAddress.getByAddress(new byte[]{127, 0, 0, 1});
236 final HttpRoute route = new HttpRoute(target, local, true);
237
238 final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry = new PoolEntry<>(route, TimeValue.NEG_ONE_MILLISECOND);
239 entry.assignConnection(conn);
240
241 Mockito.when(conn.isOpen()).thenReturn(false);
242 Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry);
243 Mockito.when(pool.lease(
244 Mockito.eq(route),
245 Mockito.eq(null),
246 Mockito.any(),
247 Mockito.eq(null)))
248 .thenReturn(future);
249
250 final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
251 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ofSeconds(1));
252 Assertions.assertNotNull(endpoint1);
253
254 final HttpClientContext context = HttpClientContext.create();
255 final SocketConfig sconfig = SocketConfig.custom().build();
256
257 mgr.setDefaultSocketConfig(sconfig);
258
259 final ConnectionConfig connectionConfig = ConnectionConfig.custom()
260 .setConnectTimeout(234, TimeUnit.MILLISECONDS)
261 .build();
262 mgr.setDefaultConnectionConfig(connectionConfig);
263 final TlsConfig tlsConfig = TlsConfig.custom()
264 .setHandshakeTimeout(345, TimeUnit.MILLISECONDS)
265 .build();
266 mgr.setDefaultTlsConfig(tlsConfig);
267
268 Mockito.when(dnsResolver.resolve("somehost", 8443)).thenReturn(Collections.singletonList(new InetSocketAddress(remote, 8443)));
269 Mockito.when(schemePortResolver.resolve(target.getSchemeName(), target)).thenReturn(8443);
270 Mockito.when(detachedSocketFactory.create(Mockito.any(), Mockito.any())).thenReturn(socket);
271
272 Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy);
273 Mockito.when(tlsSocketStrategy.upgrade(
274 Mockito.same(socket),
275 Mockito.eq("somehost"),
276 Mockito.anyInt(),
277 Mockito.any(),
278 Mockito.any())).thenReturn(upgradedSocket);
279
280 mgr.connect(endpoint1, null, context);
281
282 Mockito.verify(dnsResolver, Mockito.times(1)).resolve("somehost", 8443);
283 Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target.getSchemeName(), target);
284 Mockito.verify(detachedSocketFactory, Mockito.times(1)).create("https", null);
285 Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8443), 234);
286 Mockito.verify(tlsSocketStrategy).upgrade(socket, "somehost", 443, tlsConfig, context);
287
288 mgr.connect(endpoint1, TimeValue.ofMilliseconds(123), context);
289
290 Mockito.verify(dnsResolver, Mockito.times(2)).resolve("somehost", 8443);
291 Mockito.verify(schemePortResolver, Mockito.times(2)).resolve(target.getSchemeName(), target);
292 Mockito.verify(detachedSocketFactory, Mockito.times(2)).create("https", null);
293 Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8443), 123);
294 Mockito.verify(tlsSocketStrategy, Mockito.times(2)).upgrade(socket, "somehost", 443, tlsConfig, context);
295 }
296
297 @Test
298 void testProxyConnectAndUpgrade() throws Exception {
299 final HttpHost target = new HttpHost("https", "somehost", 443);
300 final HttpHost proxy = new HttpHost("someproxy", 8080);
301 final InetAddress remote = InetAddress.getByAddress(new byte[] {10, 0, 0, 1});
302 final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
303 final HttpRoute route = new HttpRoute(target, local, proxy, true);
304
305 final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry = new PoolEntry<>(route, TimeValue.NEG_ONE_MILLISECOND);
306 entry.assignConnection(conn);
307
308 Mockito.when(conn.isOpen()).thenReturn(false);
309 Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry);
310 Mockito.when(pool.lease(
311 Mockito.eq(route),
312 Mockito.eq(null),
313 Mockito.any(),
314 Mockito.eq(null)))
315 .thenReturn(future);
316
317 final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
318 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ofSeconds(1));
319 Assertions.assertNotNull(endpoint1);
320
321 final HttpClientContext context = HttpClientContext.create();
322 final SocketConfig sconfig = SocketConfig.custom().build();
323
324 mgr.setDefaultSocketConfig(sconfig);
325
326 final ConnectionConfig connectionConfig = ConnectionConfig.custom()
327 .setConnectTimeout(234, TimeUnit.MILLISECONDS)
328 .build();
329 mgr.setDefaultConnectionConfig(connectionConfig);
330 final TlsConfig tlsConfig = TlsConfig.custom()
331 .setHandshakeTimeout(345, TimeUnit.MILLISECONDS)
332 .build();
333 mgr.setDefaultTlsConfig(tlsConfig);
334
335 Mockito.when(dnsResolver.resolve("someproxy", 8080)).thenReturn(Collections.singletonList(new InetSocketAddress(remote, 8080)));
336 Mockito.when(schemePortResolver.resolve(proxy.getSchemeName(), proxy)).thenReturn(8080);
337 Mockito.when(schemePortResolver.resolve(target.getSchemeName(), target)).thenReturn(8443);
338 Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy);
339 Mockito.when(detachedSocketFactory.create(Mockito.any(), Mockito.any())).thenReturn(socket);
340
341 mgr.connect(endpoint1, null, context);
342
343 Mockito.verify(dnsResolver, Mockito.times(1)).resolve("someproxy", 8080);
344 Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(proxy.getSchemeName(), proxy);
345 Mockito.verify(detachedSocketFactory, Mockito.times(1)).create("http", null);
346 Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8080), 234);
347
348 Mockito.when(conn.isOpen()).thenReturn(true);
349 Mockito.when(conn.getSocket()).thenReturn(socket);
350
351 mgr.upgrade(endpoint1, context);
352
353 Mockito.verify(tlsSocketStrategy, Mockito.times(1)).upgrade(
354 socket, "somehost", 443, tlsConfig, context);
355 }
356
357 @Test
358 void testIsShutdownInitially() {
359 Assertions.assertFalse(mgr.isClosed(), "Connection manager should not be shutdown initially.");
360 }
361
362 @Test
363 void testShutdownIdempotency() {
364 mgr.close();
365 Assertions.assertTrue(mgr.isClosed(), "Connection manager should remain shutdown after the first call to shutdown.");
366 mgr.close();
367 Assertions.assertTrue(mgr.isClosed(), "Connection manager should still be shutdown after subsequent calls to shutdown.");
368 }
369
370 @Test
371 void testLeaseAfterShutdown() {
372 mgr.close();
373 Assertions.assertThrows(IllegalArgumentException.class, () -> {
374
375 mgr.lease("some-id", new HttpRoute(new HttpHost("localhost")), null);
376 }, "Attempting to lease a connection after shutdown should throw an exception.");
377 }
378
379
380 @Test
381 void testIsShutdown() {
382
383 Mockito.when(pool.isShutdown()).thenReturn(false, true);
384
385
386 Assertions.assertFalse(mgr.isClosed(), "Connection manager should not be shutdown initially.");
387
388
389 mgr.close();
390
391
392 Assertions.assertTrue(mgr.isClosed(), "Connection manager should be shutdown after close() is called.");
393 }
394
395
396 @Test
397 void testConcurrentShutdown() throws InterruptedException {
398 final ExecutorService executor = Executors.newFixedThreadPool(2);
399
400 executor.submit(() -> mgr.close());
401 executor.submit(() -> mgr.close());
402 executor.shutdown();
403 executor.awaitTermination(1, TimeUnit.SECONDS);
404
405 Assertions.assertTrue(mgr.isClosed(), "Connection manager should be shutdown after concurrent calls to shutdown.");
406 }
407
408
409 }