1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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.Arrays;
36 import java.util.Collections;
37 import java.util.List;
38 import java.util.concurrent.TimeUnit;
39
40 import javax.net.ssl.SSLSocket;
41
42 import org.apache.hc.client5.http.ConnectTimeoutException;
43 import org.apache.hc.client5.http.DnsResolver;
44 import org.apache.hc.client5.http.HttpHostConnectException;
45 import org.apache.hc.client5.http.SchemePortResolver;
46 import org.apache.hc.client5.http.UnsupportedSchemeException;
47 import org.apache.hc.client5.http.config.TlsConfig;
48 import org.apache.hc.client5.http.io.DetachedSocketFactory;
49 import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
50 import org.apache.hc.client5.http.protocol.HttpClientContext;
51 import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
52 import org.apache.hc.core5.http.HttpHost;
53 import org.apache.hc.core5.http.config.Lookup;
54 import org.apache.hc.core5.http.io.SocketConfig;
55 import org.apache.hc.core5.http2.HttpVersionPolicy;
56 import org.apache.hc.core5.util.TimeValue;
57 import org.apache.hc.core5.util.Timeout;
58 import org.junit.jupiter.api.Assertions;
59 import org.junit.jupiter.api.BeforeEach;
60 import org.junit.jupiter.api.Test;
61 import org.mockito.Mockito;
62
63 class TestHttpClientConnectionOperator {
64
65 private ManagedHttpClientConnection conn;
66 private Socket socket;
67 private DetachedSocketFactory detachedSocketFactory;
68 private TlsSocketStrategy tlsSocketStrategy;
69 private Lookup<TlsSocketStrategy> tlsSocketStrategyLookup;
70 private SchemePortResolver schemePortResolver;
71 private DnsResolver dnsResolver;
72 private DefaultHttpClientConnectionOperator connectionOperator;
73
74 @BeforeEach
75 void setup() {
76 conn = Mockito.mock(ManagedHttpClientConnection.class);
77 socket = Mockito.mock(Socket.class);
78 detachedSocketFactory = Mockito.mock(DetachedSocketFactory.class);
79 tlsSocketStrategy = Mockito.mock(TlsSocketStrategy.class);
80 tlsSocketStrategyLookup = Mockito.mock(Lookup.class);
81 schemePortResolver = Mockito.mock(SchemePortResolver.class);
82 dnsResolver = Mockito.mock(DnsResolver.class);
83 connectionOperator = new DefaultHttpClientConnectionOperator(
84 detachedSocketFactory, schemePortResolver, dnsResolver, tlsSocketStrategyLookup);
85 }
86
87 @Test
88 void testConnect() throws Exception {
89 final HttpClientContext context = HttpClientContext.create();
90 final HttpHost host = new HttpHost("somehost");
91 final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 0});
92 final InetAddress ip1 = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
93 final InetAddress ip2 = InetAddress.getByAddress(new byte[] {127, 0, 0, 2});
94 final int port = 80;
95 final List<InetSocketAddress> resolvedAddresses = Arrays.asList(
96 new InetSocketAddress(ip1, port),
97 new InetSocketAddress(ip2, port)
98 );
99 Mockito.when(dnsResolver.resolve("somehost", port)).thenReturn(resolvedAddresses);
100
101 Mockito.when(schemePortResolver.resolve(host.getSchemeName(), host)).thenReturn(port);
102 Mockito.when(detachedSocketFactory.create(Mockito.any(), 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 InetSocketAddress localAddress = new InetSocketAddress(local, 0);
112 connectionOperator.connect(conn, host, null, localAddress, Timeout.ofMilliseconds(123), socketConfig, null, context);
113
114 Mockito.verify(socket).setKeepAlive(true);
115 Mockito.verify(socket).setReuseAddress(true);
116 Mockito.verify(socket).setSoTimeout(5000);
117 Mockito.verify(socket).setSoLinger(true, 50);
118 Mockito.verify(socket).setTcpNoDelay(true);
119 Mockito.verify(socket).bind(localAddress);
120
121 Mockito.verify(socket).connect(new InetSocketAddress(ip1, port), 123);
122 Mockito.verify(conn, Mockito.times(2)).bind(socket);
123 }
124
125 @Test
126 void testConnectWithTLSUpgrade() throws Exception {
127 final HttpClientContext context = HttpClientContext.create();
128 final HttpHost host = new HttpHost("https", "somehost");
129 final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 0});
130 final InetAddress ip1 = InetAddress.getByAddress(new byte[] {127, 0, 0, 1});
131 final InetAddress ip2 = InetAddress.getByAddress(new byte[] {127, 0, 0, 2});
132 final int port = 443;
133
134 final TlsConfig tlsConfig = TlsConfig.custom()
135 .setHandshakeTimeout(Timeout.ofMilliseconds(345))
136 .setVersionPolicy(HttpVersionPolicy.FORCE_HTTP_1)
137 .build();
138
139 final List<InetSocketAddress> resolvedAddresses = Arrays.asList(
140 new InetSocketAddress(ip1, port),
141 new InetSocketAddress(ip2, port)
142 );
143 Mockito.when(dnsResolver.resolve("somehost", port)).thenReturn(resolvedAddresses);
144
145 Mockito.when(schemePortResolver.resolve(host.getSchemeName(), host)).thenReturn(port);
146 Mockito.when(detachedSocketFactory.create(Mockito.any(), Mockito.any())).thenReturn(socket);
147
148 Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy);
149 final SSLSocket upgradedSocket = Mockito.mock(SSLSocket.class);
150 Mockito.when(tlsSocketStrategy.upgrade(
151 Mockito.same(socket),
152 Mockito.eq("somehost"),
153 Mockito.anyInt(),
154 Mockito.any(),
155 Mockito.any())).thenReturn(upgradedSocket);
156
157 final InetSocketAddress localAddress = new InetSocketAddress(local, 0);
158 connectionOperator.connect(conn, host, null, localAddress,
159 Timeout.ofMilliseconds(123), SocketConfig.DEFAULT, tlsConfig, context);
160
161 Mockito.verify(socket).connect(new InetSocketAddress(ip1, port), 123);
162 Mockito.verify(conn, Mockito.times(2)).bind(socket);
163 Mockito.verify(tlsSocketStrategy).upgrade(socket, "somehost", -1, tlsConfig, context);
164 Mockito.verify(conn, Mockito.times(1)).bind(upgradedSocket, socket);
165 }
166
167
168 @Test
169 void testConnectTimeout() throws Exception {
170 final HttpClientContext context = HttpClientContext.create();
171 final HttpHost host = new HttpHost("somehost");
172 final int port = 80;
173 final InetAddress ip1 = InetAddress.getByAddress(new byte[] {10, 0, 0, 1});
174 final InetAddress ip2 = InetAddress.getByAddress(new byte[] {10, 0, 0, 2});
175 final List<InetSocketAddress> resolvedAddresses = Arrays.asList(
176 new InetSocketAddress(ip1, port),
177 new InetSocketAddress(ip2, port)
178 );
179 Mockito.when(dnsResolver.resolve("somehost", port)).thenReturn(resolvedAddresses);
180 Mockito.when(schemePortResolver.resolve(host.getSchemeName(), host)).thenReturn(port);
181 Mockito.when(detachedSocketFactory.create(Mockito.any(), Mockito.any())).thenReturn(socket);
182 Mockito.doThrow(new SocketTimeoutException()).when(socket).connect(Mockito.any(), Mockito.anyInt());
183 Assertions.assertThrows(ConnectTimeoutException.class, () ->
184 connectionOperator.connect(
185 conn, host, null, new InetSocketAddress(InetAddress.getLoopbackAddress(), 0),
186 Timeout.ofMilliseconds(1000), SocketConfig.DEFAULT, null, context));
187 }
188
189 @Test
190 void testConnectFailure() throws Exception {
191 final HttpClientContext context = HttpClientContext.create();
192 final HttpHost host = new HttpHost("somehost");
193 final InetAddress ip1 = InetAddress.getByAddress(new byte[] {10, 0, 0, 1});
194 final InetAddress ip2 = InetAddress.getByAddress(new byte[] {10, 0, 0, 2});
195 final int port = 80;
196 final List<InetSocketAddress> resolvedAddresses = Arrays.asList(
197 new InetSocketAddress(ip1, port),
198 new InetSocketAddress(ip2, port)
199 );
200 Mockito.when(dnsResolver.resolve("somehost", port)).thenReturn(resolvedAddresses);
201
202 Mockito.when(schemePortResolver.resolve(host.getSchemeName(), host)).thenReturn(port);
203 Mockito.when(detachedSocketFactory.create(Mockito.any(), Mockito.any())).thenReturn(socket);
204 Mockito.doThrow(new ConnectException()).when(socket).connect(Mockito.any(), Mockito.anyInt());
205
206 Assertions.assertThrows(HttpHostConnectException.class, () ->
207 connectionOperator.connect(
208 conn, host, null, TimeValue.ofMilliseconds(1000), SocketConfig.DEFAULT, context));
209 }
210
211 @Test
212 void testConnectFailover() throws Exception {
213 final HttpClientContext context = HttpClientContext.create();
214 final HttpHost host = new HttpHost("somehost");
215 final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 0});
216 final InetSocketAddress ipAddress1 = new InetSocketAddress(InetAddress.getByAddress(new byte[] {10, 0, 0, 1}), 80);
217 final InetSocketAddress ipAddress2 = new InetSocketAddress(InetAddress.getByAddress(new byte[] {10, 0, 0, 2}), 80);
218
219 Mockito.when(dnsResolver.resolve("somehost", 80)).thenReturn(Arrays.asList(ipAddress1, ipAddress2));
220 Mockito.when(schemePortResolver.resolve(host.getSchemeName(), host)).thenReturn(80);
221 Mockito.when(detachedSocketFactory.create(Mockito.any(), Mockito.any())).thenReturn(socket);
222 Mockito.doThrow(new ConnectException()).when(socket).connect(
223 Mockito.eq(ipAddress1),
224 Mockito.anyInt());
225
226 final InetSocketAddress localAddress = new InetSocketAddress(local, 0);
227 final TlsConfig tlsConfig = TlsConfig.custom()
228 .build();
229 connectionOperator.connect(conn, host, null, localAddress,
230 Timeout.ofMilliseconds(123), SocketConfig.DEFAULT, tlsConfig, context);
231
232 Mockito.verify(socket, Mockito.times(2)).bind(localAddress);
233 Mockito.verify(socket).connect(ipAddress2, 123);
234 Mockito.verify(conn, Mockito.times(3)).bind(socket);
235
236 }
237
238 @Test
239 void testConnectExplicitAddress() throws Exception {
240 final HttpClientContext context = HttpClientContext.create();
241 final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 0});
242 final InetAddress ip = InetAddress.getByAddress(new byte[] {127, 0, 0, 23});
243 final HttpHost host = new HttpHost(ip);
244
245 Mockito.when(schemePortResolver.resolve(host.getSchemeName(), host)).thenReturn(80);
246 Mockito.when(detachedSocketFactory.create(Mockito.any(), Mockito.any())).thenReturn(socket);
247
248 final InetSocketAddress localAddress = new InetSocketAddress(local, 0);
249 final TlsConfig tlsConfig = TlsConfig.custom()
250 .build();
251 connectionOperator.connect(conn, host, null, localAddress,
252 Timeout.ofMilliseconds(123), SocketConfig.DEFAULT, tlsConfig, context);
253
254 Mockito.verify(socket).bind(localAddress);
255 Mockito.verify(socket).connect(new InetSocketAddress(ip, 80), 123);
256 Mockito.verify(dnsResolver, Mockito.never()).resolve(Mockito.anyString(), Mockito.anyInt());
257 Mockito.verify(conn, Mockito.times(2)).bind(socket);
258 }
259
260 @Test
261 void testUpgrade() throws Exception {
262 final HttpClientContext context = HttpClientContext.create();
263 final HttpHost host = new HttpHost("https", "somehost", -1);
264
265 Mockito.when(conn.isOpen()).thenReturn(true);
266 Mockito.when(conn.getSocket()).thenReturn(socket);
267 Mockito.when(tlsSocketStrategyLookup.lookup("https")).thenReturn(tlsSocketStrategy);
268
269 final SSLSocket upgradedSocket = Mockito.mock(SSLSocket.class);
270 Mockito.when(tlsSocketStrategy.upgrade(
271 Mockito.any(),
272 Mockito.eq("somehost"),
273 Mockito.anyInt(),
274 Mockito.eq(Timeout.ofMilliseconds(345)),
275 Mockito.any())).thenReturn(upgradedSocket);
276
277 connectionOperator.upgrade(conn, host, null, Timeout.ofMilliseconds(345), context);
278
279 Mockito.verify(conn).bind(Mockito.eq(upgradedSocket), Mockito.any());
280 }
281
282 @Test
283 void testUpgradeUpsupportedScheme() {
284 final HttpClientContext context = HttpClientContext.create();
285 final HttpHost host = new HttpHost("httpsssss", "somehost", -1);
286
287 Mockito.when(conn.isOpen()).thenReturn(true);
288 Mockito.when(conn.getSocket()).thenReturn(socket);
289
290 Assertions.assertThrows(UnsupportedSchemeException.class, () ->
291 connectionOperator.upgrade(conn, host, context));
292 }
293
294 @Test
295 void testUpgradeNonLayeringScheme() {
296 final HttpClientContext context = HttpClientContext.create();
297 final HttpHost host = new HttpHost("http", "somehost", -1);
298
299 Mockito.when(conn.isOpen()).thenReturn(true);
300 Mockito.when(conn.getSocket()).thenReturn(socket);
301
302 Assertions.assertThrows(UnsupportedSchemeException.class, () ->
303 connectionOperator.upgrade(conn, host, context));
304 }
305
306 @Test
307 void testConnectWithDisableDnsResolution() throws Exception {
308 final HttpClientContext context = HttpClientContext.create();
309 final HttpHost host = new HttpHost("someonion.onion");
310 final InetAddress local = InetAddress.getByAddress(new byte[]{127, 0, 0, 0});
311 final int port = 80;
312
313 final List<InetSocketAddress> resolvedAddresses = Collections.singletonList(
314 InetSocketAddress.createUnresolved(host.getHostName(), port)
315 );
316 Mockito.when(dnsResolver.resolve(host.getHostName(), port)).thenReturn(resolvedAddresses);
317
318 Mockito.when(schemePortResolver.resolve(host.getSchemeName(), host)).thenReturn(port);
319 Mockito.when(detachedSocketFactory.create(Mockito.any(), Mockito.any())).thenReturn(socket);
320
321 final SocketConfig socketConfig = SocketConfig.custom()
322 .setSoKeepAlive(true)
323 .setSoReuseAddress(true)
324 .setSoTimeout(5000, TimeUnit.MILLISECONDS)
325 .setTcpNoDelay(true)
326 .setSoLinger(50, TimeUnit.MILLISECONDS)
327 .build();
328 final InetSocketAddress localAddress = new InetSocketAddress(local, 0);
329 final InetSocketAddress remoteAddress = InetSocketAddress.createUnresolved(host.getHostName(), port);
330
331 connectionOperator.connect(conn, host, null, localAddress, Timeout.ofMilliseconds(123), socketConfig, null, context);
332
333
334 Mockito.verify(socket).setKeepAlive(true);
335 Mockito.verify(socket).setReuseAddress(true);
336 Mockito.verify(socket).setSoTimeout(5000);
337 Mockito.verify(socket).setSoLinger(true, 50);
338 Mockito.verify(socket).setTcpNoDelay(true);
339 Mockito.verify(socket).bind(localAddress);
340
341 Mockito.verify(socket).connect(remoteAddress, 123);
342 Mockito.verify(conn, Mockito.times(2)).bind(socket);
343 Mockito.verify(dnsResolver, Mockito.never()).resolve(Mockito.anyString());
344 }
345
346 @Test
347 void testConnectWithDnsResolutionAndFallback() throws Exception {
348 final HttpClientContext context = HttpClientContext.create();
349 final HttpHost host = new HttpHost("fallbackhost.com");
350 final InetAddress local = InetAddress.getByAddress(new byte[] {127, 0, 0, 0});
351 final int port = 8080;
352 final InetAddress ip1 = InetAddress.getByAddress(new byte[] {10, 0, 0, 1});
353 final InetAddress ip2 = InetAddress.getByAddress(new byte[] {10, 0, 0, 2});
354
355
356 final List<InetSocketAddress> resolvedAddresses = Arrays.asList(
357 new InetSocketAddress(ip1, port),
358 new InetSocketAddress(ip2, port)
359 );
360 Mockito.when(dnsResolver.resolve("fallbackhost.com", port)).thenReturn(resolvedAddresses);
361 Mockito.when(schemePortResolver.resolve(host.getSchemeName(), host)).thenReturn(port);
362 Mockito.when(detachedSocketFactory.create(Mockito.any(), Mockito.any())).thenReturn(socket);
363
364
365 Mockito.doThrow(new ConnectException()).when(socket).connect(Mockito.eq(new InetSocketAddress(ip1, port)), Mockito.anyInt());
366
367 final InetSocketAddress localAddress = new InetSocketAddress(local, 0);
368 final SocketConfig socketConfig = SocketConfig.custom()
369 .setSoKeepAlive(true)
370 .setSoReuseAddress(true)
371 .setSoTimeout(5000, TimeUnit.MILLISECONDS)
372 .setTcpNoDelay(true)
373 .setSoLinger(50, TimeUnit.MILLISECONDS)
374 .build();
375
376
377 connectionOperator.connect(conn, host, null, localAddress, Timeout.ofMilliseconds(123), socketConfig, null, context);
378
379
380 Mockito.verify(socket, Mockito.times(2)).bind(localAddress);
381 Mockito.verify(socket).connect(new InetSocketAddress(ip2, port), 123);
382 Mockito.verify(conn, Mockito.times(3)).bind(socket);
383 }
384 }