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