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.TimeUnit;
35
36 import javax.net.ssl.SSLSocket;
37
38 import org.apache.hc.client5.http.DnsResolver;
39 import org.apache.hc.client5.http.HttpRoute;
40 import org.apache.hc.client5.http.SchemePortResolver;
41 import org.apache.hc.client5.http.config.ConnectionConfig;
42 import org.apache.hc.client5.http.config.TlsConfig;
43 import org.apache.hc.client5.http.io.ConnectionEndpoint;
44 import org.apache.hc.client5.http.io.DetachedSocketFactory;
45 import org.apache.hc.client5.http.io.LeaseRequest;
46 import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
47 import org.apache.hc.client5.http.protocol.HttpClientContext;
48 import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
49 import org.apache.hc.core5.http.HttpHost;
50 import org.apache.hc.core5.http.config.Lookup;
51 import org.apache.hc.core5.http.io.HttpConnectionFactory;
52 import org.apache.hc.core5.http.io.SocketConfig;
53 import org.apache.hc.core5.io.CloseMode;
54 import org.apache.hc.core5.util.TimeValue;
55 import org.apache.hc.core5.util.Timeout;
56 import org.junit.jupiter.api.Assertions;
57 import org.junit.jupiter.api.BeforeEach;
58 import org.junit.jupiter.api.Test;
59 import org.mockito.Mock;
60 import org.mockito.Mockito;
61 import org.mockito.MockitoAnnotations;
62
63 class TestBasicHttpClientConnectionManager {
64
65 @Mock
66 private ManagedHttpClientConnection conn;
67 @Mock
68 private HttpConnectionFactory<ManagedHttpClientConnection> connFactory;
69 @Mock
70 private Lookup<TlsSocketStrategy> tlsSocketStrategyLookup;
71 @Mock
72 private DetachedSocketFactory detachedSocketFactory;
73 @Mock
74 private TlsSocketStrategy tlsSocketStrategy;
75 @Mock
76 private Socket socket;
77 @Mock
78 private SSLSocket upgradedSocket;
79 @Mock
80 private SchemePortResolver schemePortResolver;
81 @Mock
82 private DnsResolver dnsResolver;
83
84 private BasicHttpClientConnectionManager mgr;
85
86 @BeforeEach
87 void setup() {
88 MockitoAnnotations.openMocks(this);
89 mgr = new BasicHttpClientConnectionManager(new DefaultHttpClientConnectionOperator(
90 detachedSocketFactory, schemePortResolver, dnsResolver, tlsSocketStrategyLookup),
91 connFactory);
92 }
93
94 @Test
95 void testLeaseReleaseNonReusable() throws Exception {
96 final HttpHost target = new HttpHost("localhost", 80);
97 final HttpRoute route = new HttpRoute(target);
98
99 Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
100
101 final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
102 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
103 Assertions.assertNotNull(endpoint1);
104 Assertions.assertFalse(endpoint1.isConnected());
105
106 mgr.release(endpoint1, null, TimeValue.ofMilliseconds(100));
107
108 Assertions.assertNull(mgr.getRoute());
109 Assertions.assertNull(mgr.getState());
110
111 final LeaseRequest connRequest2 = mgr.lease("some-id", route, null);
112 final ConnectionEndpoint conn2 = connRequest2.get(Timeout.ZERO_MILLISECONDS);
113 Assertions.assertNotNull(conn2);
114 Assertions.assertFalse(conn2.isConnected());
115
116 Mockito.verify(connFactory, Mockito.times(2)).createConnection(Mockito.any());
117 }
118
119 @Test
120 void testLeaseReleaseReusable() throws Exception {
121 final HttpHost target = new HttpHost("somehost", 80);
122 final HttpRoute route = new HttpRoute(target);
123
124 Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
125
126 final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
127 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
128 Assertions.assertNotNull(endpoint1);
129
130 Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
131
132 Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE);
133
134 Mockito.when(conn.isConsistent()).thenReturn(Boolean.TRUE);
135
136 mgr.release(endpoint1, null, TimeValue.ofMilliseconds(100));
137
138 Assertions.assertEquals(route, mgr.getRoute());
139 Assertions.assertNull(mgr.getState());
140
141 final LeaseRequest connRequest2 = mgr.lease("some-id", route, null);
142 final ConnectionEndpoint conn2 = connRequest2.get(Timeout.ZERO_MILLISECONDS);
143 Assertions.assertNotNull(conn2);
144 Assertions.assertTrue(conn2.isConnected());
145
146 Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
147 }
148
149 @Test
150 void testLeaseReleaseReusableWithState() throws Exception {
151 final HttpHost target = new HttpHost("somehost", 80);
152 final HttpRoute route = new HttpRoute(target);
153
154 Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
155
156 final LeaseRequest connRequest1 = mgr.lease("some-id", route, "some state");
157 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
158 Assertions.assertNotNull(endpoint1);
159
160 Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
161
162 Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE);
163 Mockito.when(conn.isConsistent()).thenReturn(Boolean.TRUE);
164
165 mgr.release(endpoint1, "some other state", TimeValue.ofMilliseconds(10000));
166
167 Assertions.assertEquals(route, mgr.getRoute());
168 Assertions.assertEquals("some other state", mgr.getState());
169
170 final LeaseRequest connRequest2 = mgr.lease("some-id", route, "some other state");
171 final ConnectionEndpoint conn2 = connRequest2.get(Timeout.ZERO_MILLISECONDS);
172 Assertions.assertNotNull(conn2);
173 Assertions.assertTrue(conn2.isConnected());
174
175 Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
176 }
177
178 @Test
179 void testLeaseDifferentRoute() throws Exception {
180 final HttpHost target1 = new HttpHost("somehost", 80);
181 final HttpRoute route1 = new HttpRoute(target1);
182
183 Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
184
185 final LeaseRequest connRequest1 = mgr.lease("some-id", route1, null);
186 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
187 Assertions.assertNotNull(endpoint1);
188
189 Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
190
191 Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE);
192 Mockito.when(conn.isConsistent()).thenReturn(Boolean.TRUE, Boolean.FALSE);
193
194 mgr.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
195
196 Assertions.assertEquals(route1, mgr.getRoute());
197 Assertions.assertNull(mgr.getState());
198
199 final HttpHost target2 = new HttpHost("otherhost", 80);
200 final HttpRoute route2 = new HttpRoute(target2);
201 final LeaseRequest connRequest2 = mgr.lease("some-id", route2, null);
202 final ConnectionEndpoint conn2 = connRequest2.get(Timeout.ZERO_MILLISECONDS);
203 Assertions.assertNotNull(conn2);
204 Assertions.assertFalse(conn2.isConnected());
205
206 Mockito.verify(conn).close(CloseMode.GRACEFUL);
207 Mockito.verify(connFactory, Mockito.times(2)).createConnection(Mockito.any());
208 }
209
210 @Test
211 void testLeaseExpired() throws Exception {
212 final HttpHost target = new HttpHost("somehost", 80);
213 final HttpRoute route = new HttpRoute(target);
214
215 Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
216
217 final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
218 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
219 Assertions.assertNotNull(endpoint1);
220
221 Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
222
223 Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE);
224 Mockito.when(conn.isConsistent()).thenReturn(Boolean.TRUE, Boolean.FALSE);
225
226 mgr.release(endpoint1, null, TimeValue.ofMilliseconds(10));
227
228 Assertions.assertEquals(route, mgr.getRoute());
229 Assertions.assertNull(mgr.getState());
230
231 Thread.sleep(50);
232
233 final LeaseRequest connRequest2 = mgr.lease("some-id", route, null);
234 final ConnectionEndpoint conn2 = connRequest2.get(Timeout.ZERO_MILLISECONDS);
235 Assertions.assertNotNull(conn2);
236 Assertions.assertFalse(conn2.isConnected());
237
238 Mockito.verify(conn).close(CloseMode.GRACEFUL);
239 Mockito.verify(connFactory, Mockito.times(2)).createConnection(Mockito.any());
240 }
241
242 @Test
243 void testReleaseInvalidArg() {
244 Assertions.assertThrows(NullPointerException.class, () ->
245 mgr.release(null, null, TimeValue.NEG_ONE_MILLISECOND));
246 }
247
248 @Test
249 void testReleaseAnotherConnection() {
250 final ConnectionEndpoint wrongCon = Mockito.mock(ConnectionEndpoint.class);
251 Assertions.assertThrows(IllegalStateException.class, () ->
252 mgr.release(wrongCon, null, TimeValue.NEG_ONE_MILLISECOND));
253 }
254
255 @Test
256 void testShutdown() throws Exception {
257 final HttpHost target = new HttpHost("somehost", 80);
258 final HttpRoute route = new HttpRoute(target);
259
260 Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
261
262 final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
263 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
264 Assertions.assertNotNull(endpoint1);
265
266 Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
267
268 Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE);
269 Mockito.when(conn.isConsistent()).thenReturn(Boolean.TRUE);
270
271 mgr.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
272
273 mgr.close();
274
275 Mockito.verify(conn, Mockito.times(1)).close(CloseMode.GRACEFUL);
276
277 final LeaseRequest connRequest2 = mgr.lease("some-id", route, null);
278 Assertions.assertThrows(IllegalStateException.class, () -> connRequest2.get(Timeout.ZERO_MILLISECONDS));
279
280
281 mgr.closeExpired();
282 mgr.closeIdle(TimeValue.ZERO_MILLISECONDS);
283 mgr.close();
284
285 Mockito.verify(conn, Mockito.times(1)).close(CloseMode.GRACEFUL);
286 }
287
288 @Test
289 void testCloseExpired() throws Exception {
290 final HttpHost target = new HttpHost("somehost", 80);
291 final HttpRoute route = new HttpRoute(target);
292
293 Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
294
295 final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
296 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
297 Assertions.assertNotNull(endpoint1);
298
299 Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
300
301 Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE);
302 Mockito.when(conn.isConsistent()).thenReturn(Boolean.TRUE, Boolean.FALSE);
303
304 mgr.release(endpoint1, null, TimeValue.ofMilliseconds(10));
305
306 Assertions.assertEquals(route, mgr.getRoute());
307 Assertions.assertNull(mgr.getState());
308
309 Thread.sleep(50);
310
311 mgr.closeExpired();
312
313 Mockito.verify(conn).close(CloseMode.GRACEFUL);
314 }
315
316 @Test
317 void testCloseIdle() throws Exception {
318 final HttpHost target = new HttpHost("somehost", 80);
319 final HttpRoute route = new HttpRoute(target);
320
321 Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
322
323 final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
324 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
325 Assertions.assertNotNull(endpoint1);
326
327 Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
328
329 Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE);
330 Mockito.when(conn.isConsistent()).thenReturn(Boolean.TRUE, Boolean.FALSE);
331
332 mgr.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
333
334 Assertions.assertEquals(route, mgr.getRoute());
335 Assertions.assertNull(mgr.getState());
336
337 Thread.sleep(100);
338
339 mgr.closeIdle(TimeValue.ofMilliseconds(50));
340
341 Mockito.verify(conn).close(CloseMode.GRACEFUL);
342 }
343
344 @Test
345 void testAlreadyLeased() throws Exception {
346 final HttpHost target = new HttpHost("somehost", 80);
347 final HttpRoute route = new HttpRoute(target);
348
349 Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
350
351 final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
352 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
353 Assertions.assertNotNull(endpoint1);
354 mgr.release(endpoint1, null, TimeValue.ofMilliseconds(100));
355
356 mgr.getConnection(route, null);
357 Assertions.assertThrows(IllegalStateException.class, () ->
358 mgr.getConnection(route, null));
359 }
360
361 @Test
362 void testTargetConnect() throws Exception {
363 final HttpHost target = new HttpHost("https", "somehost", 443);
364 final InetAddress remote = InetAddress.getByAddress(new byte[] {10, 0, 0, 1});
365 final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
366 final HttpRoute route = new HttpRoute(target, local, true);
367
368 Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
369
370 final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
371 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
372 Assertions.assertNotNull(endpoint1);
373
374 final HttpClientContext context = HttpClientContext.create();
375 final SocketConfig sconfig = SocketConfig.custom().build();
376
377 mgr.setSocketConfig(sconfig);
378
379 final ConnectionConfig connectionConfig = ConnectionConfig.custom()
380 .setConnectTimeout(234, TimeUnit.MILLISECONDS)
381 .build();
382 mgr.setConnectionConfig(connectionConfig);
383 final TlsConfig tlsConfig = TlsConfig.custom()
384 .setHandshakeTimeout(345, TimeUnit.MILLISECONDS)
385 .build();
386 mgr.setTlsConfig(tlsConfig);
387
388 Mockito.when(dnsResolver.resolve("somehost", 8443)).thenReturn(Collections.singletonList(new InetSocketAddress(remote, 8443)));
389 Mockito.when(schemePortResolver.resolve(target.getSchemeName(), target)).thenReturn(8443);
390 Mockito.when(detachedSocketFactory.create(Mockito.any(), Mockito.any())).thenReturn(socket);
391
392 Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy);
393 Mockito.when(tlsSocketStrategy.upgrade(
394 Mockito.same(socket),
395 Mockito.eq("somehost"),
396 Mockito.eq(8443),
397 Mockito.any(),
398 Mockito.any())).thenReturn(upgradedSocket);
399
400 mgr.connect(endpoint1, null, context);
401
402 Mockito.verify(dnsResolver, Mockito.times(1)).resolve("somehost", 8443);
403 Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target.getSchemeName(), target);
404 Mockito.verify(detachedSocketFactory, Mockito.times(1)).create("https", null);
405 Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8443), 234);
406 Mockito.verify(tlsSocketStrategy).upgrade(socket, "somehost", 443, tlsConfig, context);
407
408 mgr.connect(endpoint1, TimeValue.ofMilliseconds(123), context);
409
410 Mockito.verify(dnsResolver, Mockito.times(2)).resolve("somehost", 8443);
411 Mockito.verify(schemePortResolver, Mockito.times(2)).resolve(target.getSchemeName(), target);
412 Mockito.verify(detachedSocketFactory, Mockito.times(2)).create("https", null);
413 Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8443), 123);
414 Mockito.verify(tlsSocketStrategy, Mockito.times(2)).upgrade(socket, "somehost", 443, tlsConfig, context);
415 }
416
417 @Test
418 void testProxyConnectAndUpgrade() throws Exception {
419 final HttpHost target = new HttpHost("https", "somehost", 443);
420 final HttpHost proxy = new HttpHost("someproxy", 8080);
421 final InetAddress remote = InetAddress.getByAddress(new byte[] {10, 0, 0, 1});
422 final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
423 final HttpRoute route = new HttpRoute(target, local, proxy, true);
424
425 Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
426
427 final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
428 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
429 Assertions.assertNotNull(endpoint1);
430
431 final HttpClientContext context = HttpClientContext.create();
432 final SocketConfig sconfig = SocketConfig.custom().build();
433
434 mgr.setSocketConfig(sconfig);
435
436 final ConnectionConfig connectionConfig = ConnectionConfig.custom()
437 .setConnectTimeout(234, TimeUnit.MILLISECONDS)
438 .build();
439 mgr.setConnectionConfig(connectionConfig);
440 final TlsConfig tlsConfig = TlsConfig.custom()
441 .setHandshakeTimeout(345, TimeUnit.MILLISECONDS)
442 .build();
443 mgr.setTlsConfig(tlsConfig);
444
445 Mockito.when(dnsResolver.resolve("someproxy", 8080)).thenReturn(Collections.singletonList(new InetSocketAddress(remote, 8080)));
446 Mockito.when(schemePortResolver.resolve(proxy.getSchemeName(), proxy)).thenReturn(8080);
447 Mockito.when(schemePortResolver.resolve(target.getSchemeName(), target)).thenReturn(8443);
448 Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy);
449 Mockito.when(detachedSocketFactory.create(Mockito.any(), Mockito.any())).thenReturn(socket);
450
451 mgr.connect(endpoint1, null, context);
452
453 Mockito.verify(dnsResolver, Mockito.times(1)).resolve("someproxy", 8080);
454 Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(proxy.getSchemeName(), proxy);
455 Mockito.verify(detachedSocketFactory, Mockito.times(1)).create("http", null);
456 Mockito.verify(socket, Mockito.times(1)).connect(new InetSocketAddress(remote, 8080), 234);
457
458 Mockito.when(conn.getSocket()).thenReturn(socket);
459
460 mgr.upgrade(endpoint1, context);
461
462 Mockito.verify(tlsSocketStrategy, Mockito.times(1)).upgrade(
463 socket, "somehost", 443, tlsConfig, context);
464 }
465
466
467 @Test
468 void shouldCloseStaleConnectionAndCreateNewOne() throws Exception {
469 final HttpHost target = new HttpHost("somehost", 80);
470 final HttpRoute route = new HttpRoute(target);
471
472 Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
473
474 final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
475 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
476 Assertions.assertNotNull(endpoint1);
477
478 Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
479
480 Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE);
481 Mockito.when(conn.isConsistent()).thenReturn(Boolean.TRUE);
482
483 mgr.release(endpoint1, null, TimeValue.ofMilliseconds(100));
484
485 Assertions.assertEquals(route, mgr.getRoute());
486 Assertions.assertNull(mgr.getState());
487
488 final LeaseRequest connRequest2 = mgr.lease("some-id", route, null);
489 Mockito.when(conn.isStale()).thenReturn(Boolean.TRUE);
490 final ConnectionEndpoint conn2 = connRequest2.get(Timeout.ZERO_MILLISECONDS);
491 Assertions.assertNotNull(conn2);
492 Assertions.assertTrue(conn2.isConnected());
493
494 Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
495 }
496
497 @Test
498 void shouldCloseGRACEFULStaleConnection() throws Exception {
499 final HttpHost target = new HttpHost("somehost", 80);
500 final HttpRoute route = new HttpRoute(target);
501
502 Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
503
504 final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
505 final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
506 Assertions.assertNotNull(endpoint1);
507
508 Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
509
510 Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE);
511 Mockito.when(conn.isConsistent()).thenReturn(Boolean.TRUE);
512
513
514 mgr.release(endpoint1, null, null);
515
516
517 Mockito.verify(conn, Mockito.times(1)).close(CloseMode.GRACEFUL);
518
519
520 Mockito.when(conn.isStale()).thenReturn(Boolean.TRUE);
521
522
523 final LeaseRequest connRequest2 = mgr.lease("some-id", route, null);
524 final ConnectionEndpoint endpoint2 = connRequest2.get(Timeout.ZERO_MILLISECONDS);
525 Assertions.assertNotNull(endpoint2);
526
527
528 Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
529
530
531 Assertions.assertTrue(endpoint2.isConnected());
532 }
533
534 }