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