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