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