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.Future;
34  import java.util.concurrent.TimeUnit;
35  import java.util.concurrent.TimeoutException;
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.LeaseRequest;
44  import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
45  import org.apache.hc.client5.http.protocol.HttpClientContext;
46  import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
47  import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory;
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.SocketConfig;
51  import org.apache.hc.core5.pool.PoolEntry;
52  import org.apache.hc.core5.pool.StrictConnPool;
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  /**
63   * {@link PoolingHttpClientConnectionManager} tests.
64   */
65  @SuppressWarnings({"boxing","static-access","resource"}) // test code
66  public class TestPoolingHttpClientConnectionManager {
67  
68      @Mock
69      private ManagedHttpClientConnection conn;
70      @Mock
71      private Lookup<ConnectionSocketFactory> socketFactoryRegistry;
72      @Mock
73      private ConnectionSocketFactory plainSocketFactory;
74      @Mock
75      private ConnectionSocketFactory sslSocketFactory;
76      @Mock
77      private Socket socket;
78      @Mock
79      private SchemePortResolver schemePortResolver;
80      @Mock
81      private DnsResolver dnsResolver;
82      @Mock
83      private Future<PoolEntry<HttpRoute, ManagedHttpClientConnection>> future;
84      @Mock
85      private StrictConnPool<HttpRoute, ManagedHttpClientConnection> pool;
86      private PoolingHttpClientConnectionManager mgr;
87  
88      @BeforeEach
89      public void setup() throws Exception {
90          MockitoAnnotations.openMocks(this);
91          mgr = new PoolingHttpClientConnectionManager(
92                  new DefaultHttpClientConnectionOperator(socketFactoryRegistry, schemePortResolver, dnsResolver), pool, null);
93      }
94  
95      @Test
96      public void testLeaseRelease() throws Exception {
97          final HttpHost target = new HttpHost("localhost", 80);
98          final HttpRoute route = new HttpRoute(target);
99  
100         final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry = new PoolEntry<>(route, TimeValue.NEG_ONE_MILLISECOND);
101         entry.assignConnection(conn);
102 
103         Mockito.when(conn.isOpen()).thenReturn(true);
104         Mockito.when(conn.isConsistent()).thenReturn(true);
105         Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry);
106         Mockito.when(pool.lease(
107                 Mockito.eq(route),
108                 Mockito.eq(null),
109                 Mockito.any(),
110                 Mockito.eq(null)))
111                 .thenReturn(future);
112 
113         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
114         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ofSeconds(1));
115         Assertions.assertNotNull(endpoint1);
116         Assertions.assertNotSame(conn, endpoint1);
117 
118         mgr.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
119 
120         Mockito.verify(pool).release(entry, true);
121     }
122 
123     @Test
124     public void testReleaseRouteIncomplete() throws Exception {
125         final HttpHost target = new HttpHost("localhost", 80);
126         final HttpRoute route = new HttpRoute(target);
127 
128         final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry = new PoolEntry<>(route, TimeValue.NEG_ONE_MILLISECOND);
129 
130         Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry);
131         Mockito.when(pool.lease(
132                 Mockito.eq(route),
133                 Mockito.eq(null),
134                 Mockito.any(),
135                 Mockito.eq(null)))
136                 .thenReturn(future);
137 
138         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
139         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ofSeconds(1));
140         Assertions.assertNotNull(endpoint1);
141         Assertions.assertNotSame(conn, endpoint1);
142 
143         mgr.release(endpoint1, null, TimeValue.NEG_ONE_MILLISECOND);
144 
145         Mockito.verify(pool).release(entry, false);
146     }
147 
148     @Test
149     public void testLeaseFutureTimeout() throws Exception {
150         final HttpHost target = new HttpHost("localhost", 80);
151         final HttpRoute route = new HttpRoute(target);
152 
153         Mockito.when(future.get(1, TimeUnit.SECONDS)).thenThrow(new TimeoutException());
154         Mockito.when(pool.lease(
155                 Mockito.eq(route),
156                 Mockito.eq(null),
157                 Mockito.any(),
158                 Mockito.eq(null)))
159                 .thenReturn(future);
160 
161         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
162         Assertions.assertThrows(TimeoutException.class, () ->
163                 connRequest1.get(Timeout.ofSeconds(1)));
164     }
165 
166     @Test
167     public void testReleaseReusable() throws Exception {
168         final HttpHost target = new HttpHost("localhost", 80);
169         final HttpRoute route = new HttpRoute(target);
170 
171         final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry = new PoolEntry<>(route, TimeValue.NEG_ONE_MILLISECOND);
172         entry.assignConnection(conn);
173 
174         Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry);
175         Mockito.when(pool.lease(
176                 Mockito.eq(route),
177                 Mockito.eq(null),
178                 Mockito.any(),
179                 Mockito.eq(null)))
180                 .thenReturn(future);
181         Mockito.when(conn.isOpen()).thenReturn(true);
182         Mockito.when(conn.isConsistent()).thenReturn(true);
183 
184         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
185         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ofSeconds(1));
186         Assertions.assertNotNull(endpoint1);
187         Assertions.assertTrue(endpoint1.isConnected());
188 
189         mgr.release(endpoint1, "some state", TimeValue.NEG_ONE_MILLISECOND);
190 
191         Mockito.verify(pool).release(entry, true);
192         Assertions.assertEquals("some state", entry.getState());
193     }
194 
195     @Test
196     public void testReleaseNonReusable() throws Exception {
197         final HttpHost target = new HttpHost("localhost", 80);
198         final HttpRoute route = new HttpRoute(target);
199 
200         final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry = new PoolEntry<>(route, TimeValue.NEG_ONE_MILLISECOND);
201         entry.assignConnection(conn);
202 
203         Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry);
204         Mockito.when(pool.lease(
205                 Mockito.eq(route),
206                 Mockito.eq(null),
207                 Mockito.any(),
208                 Mockito.eq(null)))
209                 .thenReturn(future);
210         Mockito.when(conn.isOpen()).thenReturn(Boolean.FALSE);
211 
212         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
213         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ofSeconds(1));
214         Assertions.assertNotNull(endpoint1);
215         Assertions.assertFalse(endpoint1.isConnected());
216 
217         mgr.release(endpoint1, "some state", TimeValue.NEG_ONE_MILLISECOND);
218 
219         Mockito.verify(pool).release(entry, false);
220         Assertions.assertNull(entry.getState());
221     }
222 
223     @Test
224     public void testTargetConnect() throws Exception {
225         final HttpHost target = new HttpHost("https", "somehost", 443);
226         final InetAddress remote = InetAddress.getByAddress(new byte[] {10, 0, 0, 1});
227         final InetAddress local = InetAddress.getByAddress(new byte[]{127, 0, 0, 1});
228         final HttpRoute route = new HttpRoute(target, local, true);
229 
230         final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry = new PoolEntry<>(route, TimeValue.NEG_ONE_MILLISECOND);
231         entry.assignConnection(conn);
232 
233         Mockito.when(conn.isOpen()).thenReturn(false);
234         Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry);
235         Mockito.when(pool.lease(
236                 Mockito.eq(route),
237                 Mockito.eq(null),
238                 Mockito.any(),
239                 Mockito.eq(null)))
240                 .thenReturn(future);
241 
242         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
243         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ofSeconds(1));
244         Assertions.assertNotNull(endpoint1);
245 
246         final HttpClientContext context = HttpClientContext.create();
247         final SocketConfig sconfig = SocketConfig.custom().build();
248 
249         mgr.setDefaultSocketConfig(sconfig);
250 
251         final ConnectionConfig connectionConfig = ConnectionConfig.custom()
252                 .setConnectTimeout(234, TimeUnit.MILLISECONDS)
253                 .build();
254         mgr.setDefaultConnectionConfig(connectionConfig);
255         final TlsConfig tlsConfig = TlsConfig.custom()
256                 .setHandshakeTimeout(345, TimeUnit.MILLISECONDS)
257                 .build();
258         mgr.setDefaultTlsConfig(tlsConfig);
259 
260         Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[]{remote});
261         Mockito.when(schemePortResolver.resolve(target)).thenReturn(8443);
262         Mockito.when(socketFactoryRegistry.lookup("https")).thenReturn(plainSocketFactory);
263         Mockito.when(plainSocketFactory.createSocket(Mockito.any(), Mockito.any())).thenReturn(socket);
264         Mockito.when(plainSocketFactory.connectSocket(
265                 Mockito.eq(socket),
266                 Mockito.any(),
267                 Mockito.any(),
268                 Mockito.any(),
269                 Mockito.any(),
270                 Mockito.any(),
271                 Mockito.any())).thenReturn(socket);
272 
273         mgr.connect(endpoint1, null, context);
274 
275         Mockito.verify(dnsResolver, Mockito.times(1)).resolve("somehost");
276         Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target);
277         Mockito.verify(plainSocketFactory, Mockito.times(1)).createSocket(null, context);
278         Mockito.verify(plainSocketFactory, Mockito.times(1)).connectSocket(
279                 socket,
280                 target,
281                 new InetSocketAddress(remote, 8443),
282                 new InetSocketAddress(local, 0),
283                 Timeout.ofMilliseconds(234),
284                 tlsConfig,
285                 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);
291         Mockito.verify(plainSocketFactory, Mockito.times(2)).createSocket(null, context);
292         Mockito.verify(plainSocketFactory, Mockito.times(1)).connectSocket(
293                 socket,
294                 target,
295                 new InetSocketAddress(remote, 8443),
296                 new InetSocketAddress(local, 0),
297                 Timeout.ofMilliseconds(123),
298                 tlsConfig,
299                 context);
300     }
301 
302     @Test
303     public void testProxyConnectAndUpgrade() throws Exception {
304         final HttpHost target = new HttpHost("https", "somehost", 443);
305         final HttpHost proxy = new HttpHost("someproxy", 8080);
306         final InetAddress remote = InetAddress.getByAddress(new byte[] {10, 0, 0, 1});
307         final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
308         final HttpRoute route = new HttpRoute(target, local, proxy, true);
309 
310         final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry = new PoolEntry<>(route, TimeValue.NEG_ONE_MILLISECOND);
311         entry.assignConnection(conn);
312 
313         Mockito.when(conn.isOpen()).thenReturn(false);
314         Mockito.when(future.get(1, TimeUnit.SECONDS)).thenReturn(entry);
315         Mockito.when(pool.lease(
316                 Mockito.eq(route),
317                 Mockito.eq(null),
318                 Mockito.any(),
319                 Mockito.eq(null)))
320                 .thenReturn(future);
321 
322         final LeaseRequest connRequest1 = mgr.lease("some-id", route, null);
323         final ConnectionEndpoint endpoint1 = connRequest1.get(Timeout.ofSeconds(1));
324         Assertions.assertNotNull(endpoint1);
325 
326         final ConnectionSocketFactory plainsf = Mockito.mock(ConnectionSocketFactory.class);
327         final LayeredConnectionSocketFactory sslsf = Mockito.mock(LayeredConnectionSocketFactory.class);
328         final Socket mockSock = Mockito.mock(Socket.class);
329         final HttpClientContext context = HttpClientContext.create();
330         final SocketConfig sconfig = SocketConfig.custom().build();
331 
332         mgr.setDefaultSocketConfig(sconfig);
333 
334         final ConnectionConfig connectionConfig = ConnectionConfig.custom()
335                 .setConnectTimeout(234, TimeUnit.MILLISECONDS)
336                 .build();
337         mgr.setDefaultConnectionConfig(connectionConfig);
338         final TlsConfig tlsConfig = TlsConfig.custom()
339                 .setHandshakeTimeout(345, TimeUnit.MILLISECONDS)
340                 .build();
341         mgr.setDefaultTlsConfig(tlsConfig);
342 
343         Mockito.when(dnsResolver.resolve("someproxy")).thenReturn(new InetAddress[] {remote});
344         Mockito.when(schemePortResolver.resolve(proxy)).thenReturn(8080);
345         Mockito.when(schemePortResolver.resolve(target)).thenReturn(8443);
346         Mockito.when(socketFactoryRegistry.lookup("http")).thenReturn(plainsf);
347         Mockito.when(socketFactoryRegistry.lookup("https")).thenReturn(sslsf);
348         Mockito.when(plainsf.createSocket(Mockito.any(), Mockito.any())).thenReturn(mockSock);
349         Mockito.when(plainsf.connectSocket(
350                 Mockito.eq(mockSock),
351                 Mockito.any(),
352                 Mockito.any(),
353                 Mockito.any(),
354                 Mockito.any(),
355                 Mockito.any(),
356                 Mockito.any())).thenReturn(mockSock);
357 
358         mgr.connect(endpoint1, null, context);
359 
360         Mockito.verify(dnsResolver, Mockito.times(1)).resolve("someproxy");
361         Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(proxy);
362         Mockito.verify(plainsf, Mockito.times(1)).createSocket(null, context);
363         Mockito.verify(plainsf, Mockito.times(1)).connectSocket(
364                 mockSock,
365                 proxy,
366                 new InetSocketAddress(remote, 8080),
367                 new InetSocketAddress(local, 0),
368                 Timeout.ofMilliseconds(234),
369                 tlsConfig,
370                 context);
371 
372         Mockito.when(conn.isOpen()).thenReturn(true);
373         Mockito.when(conn.getSocket()).thenReturn(mockSock);
374 
375         mgr.upgrade(endpoint1, context);
376 
377         Mockito.verify(schemePortResolver, Mockito.times(1)).resolve(target);
378         Mockito.verify(sslsf, Mockito.times(1)).createLayeredSocket(
379                 mockSock, "somehost", 8443, tlsConfig, context);
380     }
381 
382 }