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         mgr.release(endpoint1, null, TimeValue.ofMilliseconds(100));
130 
131         Assertions.assertEquals(route, mgr.getRoute());
132         Assertions.assertNull(mgr.getState());
133 
134         final LeaseRequest connRequest2 = mgr.lease("some-id", route, null);
135         final ConnectionEndpoint conn2 = connRequest2.get(Timeout.ZERO_MILLISECONDS);
136         Assertions.assertNotNull(conn2);
137         Assertions.assertTrue(conn2.isConnected());
138 
139         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
140     }
141 
142     @Test
143     public void testLeaseReleaseReusableWithState() throws Exception {
144         final HttpHost target = new HttpHost("somehost", 80);
145         final HttpRoute route = new HttpRoute(target);
146 
147         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
148 
149         final LeaseRequest connRequest1 = mgr.lease("some-id", route, "some state");
150         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
151         Assertions.assertNotNull(endpoint1);
152 
153         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
154 
155         Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE);
156 
157         mgr.release(endpoint1, "some other state", TimeValue.ofMilliseconds(10000));
158 
159         Assertions.assertEquals(route, mgr.getRoute());
160         Assertions.assertEquals("some other state", mgr.getState());
161 
162         final LeaseRequest connRequest2 = mgr.lease("some-id", route, "some other state");
163         final ConnectionEndpoint conn2 = connRequest2.get(Timeout.ZERO_MILLISECONDS);
164         Assertions.assertNotNull(conn2);
165         Assertions.assertTrue(conn2.isConnected());
166 
167         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
168     }
169 
170     @Test
171     public void testLeaseDifferentRoute() throws Exception {
172         final HttpHost target1 = new HttpHost("somehost", 80);
173         final HttpRoute route1 = new HttpRoute(target1);
174 
175         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
176 
177         final LeaseRequest connRequest1 = mgr.lease("some-id", route1, null);
178         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
179         Assertions.assertNotNull(endpoint1);
180 
181         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
182 
183         Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE);
184 
185         mgr.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
186 
187         Assertions.assertEquals(route1, mgr.getRoute());
188         Assertions.assertNull(mgr.getState());
189 
190         final HttpHost target2 = new HttpHost("otherhost", 80);
191         final HttpRoute route2 = new HttpRoute(target2);
192         final LeaseRequest connRequest2 = mgr.lease("some-id", route2, null);
193         final ConnectionEndpoint conn2 = connRequest2.get(Timeout.ZERO_MILLISECONDS);
194         Assertions.assertNotNull(conn2);
195         Assertions.assertFalse(conn2.isConnected());
196 
197         Mockito.verify(conn).close(CloseMode.GRACEFUL);
198         Mockito.verify(connFactory, Mockito.times(2)).createConnection(Mockito.any());
199     }
200 
201     @Test
202     public void testLeaseExpired() throws Exception {
203         final HttpHost target = new HttpHost("somehost", 80);
204         final HttpRoute route = new HttpRoute(target);
205 
206         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
207 
208         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
209         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
210         Assertions.assertNotNull(endpoint1);
211 
212         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
213 
214         Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE);
215 
216         mgr.release(endpoint1, null, TimeValue.ofMilliseconds(10));
217 
218         Assertions.assertEquals(route, mgr.getRoute());
219         Assertions.assertNull(mgr.getState());
220 
221         Thread.sleep(50);
222 
223         final LeaseRequest connRequest2 = mgr.lease("some-id", route, null);
224         final ConnectionEndpoint conn2 = connRequest2.get(Timeout.ZERO_MILLISECONDS);
225         Assertions.assertNotNull(conn2);
226         Assertions.assertFalse(conn2.isConnected());
227 
228         Mockito.verify(conn).close(CloseMode.GRACEFUL);
229         Mockito.verify(connFactory, Mockito.times(2)).createConnection(Mockito.any());
230     }
231 
232     @Test
233     public void testReleaseInvalidArg() throws Exception {
234         Assertions.assertThrows(NullPointerException.class, () ->
235                 mgr.release(null, null, TimeValue.NEG_ONE_MILLISECOND));
236     }
237 
238     @Test
239     public void testReleaseAnotherConnection() throws Exception {
240         final ConnectionEndpoint wrongCon = Mockito.mock(ConnectionEndpoint.class);
241         Assertions.assertThrows(IllegalStateException.class, () ->
242                 mgr.release(wrongCon, null, TimeValue.NEG_ONE_MILLISECOND));
243     }
244 
245     @Test
246     public void testShutdown() throws Exception {
247         final HttpHost target = new HttpHost("somehost", 80);
248         final HttpRoute route = new HttpRoute(target);
249 
250         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
251 
252         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
253         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
254         Assertions.assertNotNull(endpoint1);
255 
256         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
257 
258         Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE);
259 
260         mgr.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
261 
262         mgr.close();
263 
264         Mockito.verify(conn, Mockito.times(1)).close(CloseMode.GRACEFUL);
265 
266         final LeaseRequest connRequest2 = mgr.lease("some-id", route, null);
267         Assertions.assertThrows(IllegalStateException.class, () -> connRequest2.get(Timeout.ZERO_MILLISECONDS));
268 
269         // Should have no effect
270         mgr.closeExpired();
271         mgr.closeIdle(TimeValue.ZERO_MILLISECONDS);
272         mgr.close();
273 
274         Mockito.verify(conn, Mockito.times(1)).close(CloseMode.GRACEFUL);
275     }
276 
277     @Test
278     public void testCloseExpired() throws Exception {
279         final HttpHost target = new HttpHost("somehost", 80);
280         final HttpRoute route = new HttpRoute(target);
281 
282         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
283 
284         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
285         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
286         Assertions.assertNotNull(endpoint1);
287 
288         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
289 
290         Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE);
291 
292         mgr.release(endpoint1, null, TimeValue.ofMilliseconds(10));
293 
294         Assertions.assertEquals(route, mgr.getRoute());
295         Assertions.assertNull(mgr.getState());
296 
297         Thread.sleep(50);
298 
299         mgr.closeExpired();
300 
301         Mockito.verify(conn).close(CloseMode.GRACEFUL);
302     }
303 
304     @Test
305     public void testCloseIdle() throws Exception {
306         final HttpHost target = new HttpHost("somehost", 80);
307         final HttpRoute route = new HttpRoute(target);
308 
309         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
310 
311         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
312         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
313         Assertions.assertNotNull(endpoint1);
314 
315         Mockito.verify(connFactory, Mockito.times(1)).createConnection(Mockito.any());
316 
317         Mockito.when(conn.isOpen()).thenReturn(Boolean.TRUE, Boolean.FALSE);
318 
319         mgr.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
320 
321         Assertions.assertEquals(route, mgr.getRoute());
322         Assertions.assertNull(mgr.getState());
323 
324         Thread.sleep(100);
325 
326         mgr.closeIdle(TimeValue.ofMilliseconds(50));
327 
328         Mockito.verify(conn).close(CloseMode.GRACEFUL);
329     }
330 
331     @Test
332     public void testAlreadyLeased() throws Exception {
333         final HttpHost target = new HttpHost("somehost", 80);
334         final HttpRoute route = new HttpRoute(target);
335 
336         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
337 
338         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
339         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
340         Assertions.assertNotNull(endpoint1);
341         mgr.release(endpoint1, null, TimeValue.ofMilliseconds(100));
342 
343         mgr.getConnection(route, null);
344         Assertions.assertThrows(IllegalStateException.class, () ->
345                 mgr.getConnection(route, null));
346     }
347 
348     @Test
349     public void testTargetConnect() throws Exception {
350         final HttpHost target = new HttpHost("https", "somehost", 443);
351         final InetAddress remote = InetAddress.getByAddress(new byte[] {10, 0, 0, 1});
352         final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
353         final HttpRoute route = new HttpRoute(target, local, true);
354 
355         Mockito.when(connFactory.createConnection(Mockito.any())).thenReturn(conn);
356 
357         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
358         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ZERO_MILLISECONDS);
359         Assertions.assertNotNull(endpoint1);
360 
361         final HttpClientContext context = HttpClientContext.create();
362         final SocketConfig sconfig = SocketConfig.custom().build();
363 
364         mgr.setSocketConfig(sconfig);
365 
366         final ConnectionConfig connectionConfig = ConnectionConfig.custom()
367                 .setConnectTimeout(234, TimeUnit.MILLISECONDS)
368                 .build();
369         mgr.setConnectionConfig(connectionConfig);
370         final TlsConfig tlsConfig = TlsConfig.custom()
371                 .setHandshakeTimeout(345, TimeUnit.MILLISECONDS)
372                 .build();
373         mgr.setTlsConfig(tlsConfig);
374 
375         Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[] {remote});
376         Mockito.when(schemePortResolver.resolve(target)).thenReturn(8443);
377         Mockito.when(socketFactoryRegistry.lookup("https")).thenReturn(plainSocketFactory);
378         Mockito.when(plainSocketFactory.createSocket(Mockito.any(), Mockito.any())).thenReturn(socket);
379         Mockito.when(plainSocketFactory.connectSocket(
380                 Mockito.eq(socket),
381                 Mockito.any(),
382                 Mockito.any(),
383                 Mockito.any(),
384                 Mockito.any(),
385                 Mockito.any(),
386                 Mockito.any())).thenReturn(socket);
387 
388         mgr.connect(endpoint1, null, context);
389 
390         Mockito.verify(dnsResolver, Mockito.times(1)).resolve("somehost");
391         Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target);
392         Mockito.verify(plainSocketFactory, Mockito.times(1)).createSocket(null, context);
393         Mockito.verify(plainSocketFactory, Mockito.times(1)).connectSocket(
394                 socket,
395                 target,
396                 new InetSocketAddress(remote, 8443),
397                 new InetSocketAddress(local, 0),
398                 Timeout.ofMilliseconds(234),
399                 tlsConfig,
400                 context);
401 
402         mgr.connect(endpoint1, TimeValue.ofMilliseconds(123), context);
403 
404         Mockito.verify(dnsResolver, Mockito.times(2)).resolve("somehost");
405         Mockito.verify(schemePortResolver, Mockito.times(2)).resolve(target);
406         Mockito.verify(plainSocketFactory, Mockito.times(2)).createSocket(null, context);
407         Mockito.verify(plainSocketFactory, Mockito.times(1)).connectSocket(
408                 socket,
409                 target,
410                 new InetSocketAddress(remote, 8443),
411                 new InetSocketAddress(local, 0),
412                 Timeout.ofMilliseconds(123),
413                 tlsConfig,
414                 context);
415     }
416 
417     @Test
418     public 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")).thenReturn(new InetAddress[] {remote});
446         Mockito.when(schemePortResolver.resolve(proxy)).thenReturn(8080);
447         Mockito.when(schemePortResolver.resolve(target)).thenReturn(8443);
448         Mockito.when(socketFactoryRegistry.lookup("http")).thenReturn(plainSocketFactory);
449         Mockito.when(socketFactoryRegistry.lookup("https")).thenReturn(sslSocketFactory);
450         Mockito.when(plainSocketFactory.createSocket(Mockito.any(), Mockito.any())).thenReturn(socket);
451         Mockito.when(plainSocketFactory.connectSocket(
452                 Mockito.eq(socket),
453                 Mockito.any(),
454                 Mockito.any(),
455                 Mockito.any(),
456                 Mockito.any(),
457                 Mockito.any(),
458                 Mockito.any())).thenReturn(socket);
459 
460         mgr.connect(endpoint1, null, context);
461 
462         Mockito.verify(dnsResolver, Mockito.times(1)).resolve("someproxy");
463         Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(proxy);
464         Mockito.verify(plainSocketFactory, Mockito.times(1)).createSocket(null, context);
465         Mockito.verify(plainSocketFactory, Mockito.times(1)).connectSocket(
466                 socket,
467                 proxy,
468                 new InetSocketAddress(remote, 8080),
469                 new InetSocketAddress(local, 0),
470                 Timeout.ofMilliseconds(234),
471                 tlsConfig,
472                 context);
473 
474         Mockito.when(conn.getSocket()).thenReturn(socket);
475 
476         mgr.upgrade(endpoint1, context);
477 
478         Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target);
479         Mockito.verify(sslSocketFactory, Mockito.times(1)).createLayeredSocket(
480                 socket, "somehost", 8443, tlsConfig, context);
481     }
482 
483 }