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.ConnectException;
31  import java.net.InetAddress;
32  import java.net.InetSocketAddress;
33  import java.net.Socket;
34  import java.net.SocketTimeoutException;
35  import java.util.concurrent.TimeUnit;
36  
37  import org.apache.hc.client5.http.ConnectTimeoutException;
38  import org.apache.hc.client5.http.DnsResolver;
39  import org.apache.hc.client5.http.HttpHostConnectException;
40  import org.apache.hc.client5.http.SchemePortResolver;
41  import org.apache.hc.client5.http.UnsupportedSchemeException;
42  import org.apache.hc.client5.http.config.TlsConfig;
43  import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
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.SocketConfig;
49  import org.apache.hc.core5.http.protocol.BasicHttpContext;
50  import org.apache.hc.core5.http.protocol.HttpContext;
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.Mockito;
57  
58  @SuppressWarnings({"boxing","static-access"}) // test code
59  public class TestHttpClientConnectionOperator {
60  
61      private ManagedHttpClientConnection conn;
62      private Socket socket;
63      private ConnectionSocketFactory plainSocketFactory;
64      private LayeredConnectionSocketFactory sslSocketFactory;
65      private Lookup<ConnectionSocketFactory> socketFactoryRegistry;
66      private SchemePortResolver schemePortResolver;
67      private DnsResolver dnsResolver;
68      private DefaultHttpClientConnectionOperator connectionOperator;
69  
70      @BeforeEach
71      public void setup() throws Exception {
72          conn = Mockito.mock(ManagedHttpClientConnection.class);
73          socket = Mockito.mock(Socket.class);
74          plainSocketFactory = Mockito.mock(ConnectionSocketFactory.class);
75          sslSocketFactory = Mockito.mock(LayeredConnectionSocketFactory.class);
76          socketFactoryRegistry = Mockito.mock(Lookup.class);
77          schemePortResolver = Mockito.mock(SchemePortResolver.class);
78          dnsResolver = Mockito.mock(DnsResolver.class);
79          connectionOperator = new DefaultHttpClientConnectionOperator(
80                  socketFactoryRegistry, schemePortResolver, dnsResolver);
81      }
82  
83      @Test
84      public void testConnect() throws Exception {
85          final HttpContext context = new BasicHttpContext();
86          final HttpHost host = new HttpHost("somehost");
87          final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 0});
88          final InetAddress ip1 = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
89          final InetAddress ip2 = InetAddress.getByAddress(new byte[] {127, 0, 0, 2});
90  
91          Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[] { ip1, ip2 });
92          Mockito.when(socketFactoryRegistry.lookup("http")).thenReturn(plainSocketFactory);
93          Mockito.when(schemePortResolver.resolve(host)).thenReturn(80);
94          Mockito.when(plainSocketFactory.createSocket(Mockito.any(), Mockito.any())).thenReturn(socket);
95          Mockito.when(plainSocketFactory.connectSocket(
96                  Mockito.any(),
97                  Mockito.any(),
98                  Mockito.any(),
99                  Mockito.any(),
100                 Mockito.any(),
101                 Mockito.any(),
102                 Mockito.any())).thenReturn(socket);
103 
104         final SocketConfig socketConfig = SocketConfig.custom()
105             .setSoKeepAlive(true)
106             .setSoReuseAddress(true)
107             .setSoTimeout(5000, TimeUnit.MILLISECONDS)
108             .setTcpNoDelay(true)
109             .setSoLinger(50, TimeUnit.MILLISECONDS)
110             .build();
111         final TlsConfig tlsConfig = TlsConfig.custom()
112                 .build();
113         final InetSocketAddress localAddress = new InetSocketAddress(local, 0);
114         connectionOperator.connect(conn, host, localAddress,
115                 Timeout.ofMilliseconds(123), socketConfig, tlsConfig, context);
116 
117         Mockito.verify(socket).setKeepAlive(true);
118         Mockito.verify(socket).setReuseAddress(true);
119         Mockito.verify(socket).setSoTimeout(5000);
120         Mockito.verify(socket).setSoLinger(true, 50);
121         Mockito.verify(socket).setTcpNoDelay(true);
122 
123         Mockito.verify(plainSocketFactory).connectSocket(
124                 socket,
125                 host,
126                 new InetSocketAddress(ip1, 80),
127                 localAddress,
128                 Timeout.ofMilliseconds(123),
129                 tlsConfig,
130                 context);
131         Mockito.verify(conn, Mockito.times(2)).bind(socket);
132     }
133 
134     @Test
135     public void testConnectTimeout() throws Exception {
136         final HttpContext context = new BasicHttpContext();
137         final HttpHost host = new HttpHost("somehost");
138         final InetAddress ip1 = InetAddress.getByAddress(new byte[] {10, 0, 0, 1});
139         final InetAddress ip2 = InetAddress.getByAddress(new byte[] {10, 0, 0, 2});
140 
141         Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[] { ip1, ip2 });
142         Mockito.when(socketFactoryRegistry.lookup("http")).thenReturn(plainSocketFactory);
143         Mockito.when(schemePortResolver.resolve(host)).thenReturn(80);
144         Mockito.when(plainSocketFactory.createSocket(Mockito.any(), Mockito.any())).thenReturn(socket);
145         Mockito.when(plainSocketFactory.connectSocket(
146                 Mockito.any(),
147                 Mockito.any(),
148                 Mockito.any(),
149                 Mockito.any(),
150                 Mockito.any(),
151                 Mockito.any(),
152                 Mockito.any())).thenThrow(new SocketTimeoutException());
153 
154         Assertions.assertThrows(ConnectTimeoutException.class, () ->
155                 connectionOperator.connect(
156                         conn, host, null, TimeValue.ofMilliseconds(1000), SocketConfig.DEFAULT, context));
157     }
158 
159     @Test
160     public void testConnectFailure() throws Exception {
161         final HttpContext context = new BasicHttpContext();
162         final HttpHost host = new HttpHost("somehost");
163         final InetAddress ip1 = InetAddress.getByAddress(new byte[] {10, 0, 0, 1});
164         final InetAddress ip2 = InetAddress.getByAddress(new byte[] {10, 0, 0, 2});
165 
166         Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[] { ip1, ip2 });
167         Mockito.when(socketFactoryRegistry.lookup("http")).thenReturn(plainSocketFactory);
168         Mockito.when(schemePortResolver.resolve(host)).thenReturn(80);
169         Mockito.when(plainSocketFactory.createSocket(Mockito.any(), Mockito.any())).thenReturn(socket);
170         Mockito.when(plainSocketFactory.connectSocket(
171                 Mockito.any(),
172                 Mockito.any(),
173                 Mockito.any(),
174                 Mockito.any(),
175                 Mockito.any(),
176                 Mockito.any(),
177                 Mockito.any())).thenThrow(new ConnectException());
178 
179         Assertions.assertThrows(HttpHostConnectException.class, () ->
180                 connectionOperator.connect(
181                         conn, host, null, TimeValue.ofMilliseconds(1000), SocketConfig.DEFAULT, context));
182     }
183 
184     @Test
185     public void testConnectFailover() throws Exception {
186         final HttpContext context = new BasicHttpContext();
187         final HttpHost host = new HttpHost("somehost");
188         final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 0});
189         final InetAddress ip1 = InetAddress.getByAddress(new byte[] {10, 0, 0, 1});
190         final InetAddress ip2 = InetAddress.getByAddress(new byte[] {10, 0, 0, 2});
191 
192         Mockito.when(dnsResolver.resolve("somehost")).thenReturn(new InetAddress[] { ip1, ip2 });
193         Mockito.when(socketFactoryRegistry.lookup("http")).thenReturn(plainSocketFactory);
194         Mockito.when(schemePortResolver.resolve(host)).thenReturn(80);
195         Mockito.when(plainSocketFactory.createSocket(Mockito.any(), Mockito.any())).thenReturn(socket);
196         Mockito.when(plainSocketFactory.connectSocket(
197                 Mockito.any(),
198                 Mockito.any(),
199                 Mockito.eq(new InetSocketAddress(ip1, 80)),
200                 Mockito.any(),
201                 Mockito.any(),
202                 Mockito.any(),
203                 Mockito.any())).thenThrow(new ConnectException());
204         Mockito.when(plainSocketFactory.connectSocket(
205                 Mockito.any(),
206                 Mockito.any(),
207                 Mockito.eq(new InetSocketAddress(ip2, 80)),
208                 Mockito.any(),
209                 Mockito.any(),
210                 Mockito.any(),
211                 Mockito.any())).thenReturn(socket);
212 
213         final InetSocketAddress localAddress = new InetSocketAddress(local, 0);
214         final TlsConfig tlsConfig = TlsConfig.custom()
215                 .build();
216         connectionOperator.connect(conn, host, localAddress,
217                 Timeout.ofMilliseconds(123), SocketConfig.DEFAULT, tlsConfig, context);
218 
219         Mockito.verify(plainSocketFactory).connectSocket(
220                 socket,
221                 host,
222                 new InetSocketAddress(ip2, 80),
223                 localAddress,
224                 Timeout.ofMilliseconds(123),
225                 tlsConfig,
226                 context);
227         Mockito.verify(conn, Mockito.times(3)).bind(socket);
228     }
229 
230     @Test
231     public void testConnectExplicitAddress() throws Exception {
232         final HttpContext context = new BasicHttpContext();
233         final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 0});
234         final InetAddress ip = InetAddress.getByAddress(new byte[] {127, 0, 0, 23});
235         final HttpHost host = new HttpHost(ip);
236 
237         Mockito.when(socketFactoryRegistry.lookup("http")).thenReturn(plainSocketFactory);
238         Mockito.when(schemePortResolver.resolve(host)).thenReturn(80);
239         Mockito.when(plainSocketFactory.createSocket(Mockito.any(), Mockito.any())).thenReturn(socket);
240         Mockito.when(plainSocketFactory.connectSocket(
241                 Mockito.any(),
242                 Mockito.any(),
243                 Mockito.any(),
244                 Mockito.any(),
245                 Mockito.any(),
246                 Mockito.any(),
247                 Mockito.any())).thenReturn(socket);
248 
249         final InetSocketAddress localAddress = new InetSocketAddress(local, 0);
250         final TlsConfig tlsConfig = TlsConfig.custom()
251                 .build();
252         connectionOperator.connect(conn, host, localAddress,
253                 Timeout.ofMilliseconds(123), SocketConfig.DEFAULT, tlsConfig, context);
254 
255         Mockito.verify(plainSocketFactory).connectSocket(
256                 socket,
257                 host,
258                 new InetSocketAddress(ip, 80),
259                 localAddress,
260                 Timeout.ofMilliseconds(123),
261                 tlsConfig,
262                 context);
263         Mockito.verify(dnsResolver, Mockito.never()).resolve(Mockito.anyString());
264         Mockito.verify(conn, Mockito.times(2)).bind(socket);
265     }
266 
267     @Test
268     public void testUpgrade() throws Exception {
269         final HttpContext context = new BasicHttpContext();
270         final HttpHost host = new HttpHost("https", "somehost", -1);
271 
272         Mockito.when(conn.isOpen()).thenReturn(true);
273         Mockito.when(conn.getSocket()).thenReturn(socket);
274         Mockito.when(socketFactoryRegistry.lookup("https")).thenReturn(sslSocketFactory);
275         Mockito.when(schemePortResolver.resolve(host)).thenReturn(443);
276         Mockito.when(sslSocketFactory.createSocket(Mockito.any())).thenReturn(socket);
277         Mockito.when(sslSocketFactory.createLayeredSocket(
278                 Mockito.any(),
279                 Mockito.eq("somehost"),
280                 Mockito.eq(443),
281                 Mockito.eq(Timeout.ofMilliseconds(345)),
282                 Mockito.any())).thenReturn(socket);
283 
284         connectionOperator.upgrade(conn, host, Timeout.ofMilliseconds(345), context);
285 
286         Mockito.verify(conn).bind(socket);
287     }
288 
289     @Test
290     public void testUpgradeUpsupportedScheme() throws Exception {
291         final HttpContext context = new BasicHttpContext();
292         final HttpHost host = new HttpHost("httpsssss", "somehost", -1);
293         Mockito.when(socketFactoryRegistry.lookup("http")).thenReturn(plainSocketFactory);
294 
295         Assertions.assertThrows(UnsupportedSchemeException.class, () ->
296                 connectionOperator.upgrade(conn, host, context));
297     }
298 
299     @Test
300     public void testUpgradeNonLayeringScheme() throws Exception {
301         final HttpContext context = new BasicHttpContext();
302         final HttpHost host = new HttpHost("http", "somehost", -1);
303         Mockito.when(socketFactoryRegistry.lookup("http")).thenReturn(plainSocketFactory);
304 
305         Assertions.assertThrows(UnsupportedSchemeException.class, () ->
306                 connectionOperator.upgrade(conn, host, context));
307     }
308 
309 }