RouteSelectorTest.java revision 7c7f22d80748dc444d5da3c5be11d7d81ef14a2b
1/*
2 * Copyright (C) 2012 Square, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.squareup.okhttp.internal.http;
17
18import com.squareup.okhttp.Address;
19import com.squareup.okhttp.Connection;
20import com.squareup.okhttp.ConnectionPool;
21import com.squareup.okhttp.HostResolver;
22import com.squareup.okhttp.OkAuthenticator;
23import com.squareup.okhttp.Protocol;
24import com.squareup.okhttp.RouteDatabase;
25import com.squareup.okhttp.internal.SslContextBuilder;
26import java.io.IOException;
27import java.net.InetAddress;
28import java.net.InetSocketAddress;
29import java.net.Proxy;
30import java.net.ProxySelector;
31import java.net.SocketAddress;
32import java.net.URI;
33import java.net.UnknownHostException;
34import java.util.ArrayList;
35import java.util.Arrays;
36import java.util.List;
37import java.util.NoSuchElementException;
38import javax.net.SocketFactory;
39import javax.net.ssl.HostnameVerifier;
40import javax.net.ssl.SSLContext;
41import javax.net.ssl.SSLHandshakeException;
42import javax.net.ssl.SSLSocketFactory;
43import org.junit.Test;
44
45import static java.net.Proxy.NO_PROXY;
46import static org.junit.Assert.assertEquals;
47import static org.junit.Assert.assertFalse;
48import static org.junit.Assert.assertTrue;
49import static org.junit.Assert.fail;
50
51public final class RouteSelectorTest {
52  private static final int proxyAPort = 1001;
53  private static final String proxyAHost = "proxyA";
54  private static final Proxy proxyA =
55      new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyAHost, proxyAPort));
56  private static final int proxyBPort = 1002;
57  private static final String proxyBHost = "proxyB";
58  private static final Proxy proxyB =
59      new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyBHost, proxyBPort));
60  private static final URI uri;
61  private static final String uriHost = "hostA";
62  private static final int uriPort = 80;
63
64  private static final SocketFactory socketFactory;
65  private static final SSLContext sslContext = SslContextBuilder.localhost();
66  private static final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
67  private static final HostnameVerifier hostnameVerifier;
68  private static final ConnectionPool pool;
69
70  static {
71    try {
72      uri = new URI("http://" + uriHost + ":" + uriPort + "/path");
73      socketFactory = SocketFactory.getDefault();
74      pool = ConnectionPool.getDefault();
75      hostnameVerifier = HttpsURLConnectionImpl.getDefaultHostnameVerifier();
76    } catch (Exception e) {
77      throw new AssertionError(e);
78    }
79  }
80
81  private final OkAuthenticator authenticator = HttpAuthenticator.SYSTEM_DEFAULT;
82  private final List<Protocol> protocols = Arrays.asList(Protocol.HTTP_11);
83  private final FakeDns dns = new FakeDns();
84  private final FakeProxySelector proxySelector = new FakeProxySelector();
85
86  @Test public void singleRoute() throws Exception {
87    Address address = new Address(uriHost, uriPort, socketFactory, null, null, authenticator, null,
88        protocols);
89    RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
90        new RouteDatabase());
91
92    assertTrue(routeSelector.hasNext());
93    dns.inetAddresses = makeFakeAddresses(255, 1);
94    assertConnection(routeSelector.next("GET"), address, NO_PROXY, dns.inetAddresses[0], uriPort,
95        false);
96    dns.assertRequests(uriHost);
97
98    assertFalse(routeSelector.hasNext());
99    try {
100      routeSelector.next("GET");
101      fail();
102    } catch (NoSuchElementException expected) {
103    }
104  }
105
106  @Test public void singleRouteReturnsFailedRoute() throws Exception {
107    Address address = new Address(uriHost, uriPort, socketFactory, null, null, authenticator, null,
108        protocols);
109    RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
110        new RouteDatabase());
111
112    assertTrue(routeSelector.hasNext());
113    dns.inetAddresses = makeFakeAddresses(255, 1);
114    Connection connection = routeSelector.next("GET");
115    RouteDatabase routeDatabase = new RouteDatabase();
116    routeDatabase.failed(connection.getRoute());
117    routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, routeDatabase);
118    assertConnection(routeSelector.next("GET"), address, NO_PROXY, dns.inetAddresses[0], uriPort,
119        false);
120    assertFalse(routeSelector.hasNext());
121    try {
122      routeSelector.next("GET");
123      fail();
124    } catch (NoSuchElementException expected) {
125    }
126  }
127
128  @Test public void explicitProxyTriesThatProxiesAddressesOnly() throws Exception {
129    Address address = new Address(uriHost, uriPort, socketFactory, null, null, authenticator,
130        proxyA, protocols);
131    RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
132        new RouteDatabase());
133
134    assertTrue(routeSelector.hasNext());
135    dns.inetAddresses = makeFakeAddresses(255, 2);
136    assertConnection(routeSelector.next("GET"), address, proxyA, dns.inetAddresses[0], proxyAPort,
137        false);
138    assertConnection(routeSelector.next("GET"), address, proxyA, dns.inetAddresses[1], proxyAPort,
139        false);
140
141    assertFalse(routeSelector.hasNext());
142    dns.assertRequests(proxyAHost);
143    proxySelector.assertRequests(); // No proxy selector requests!
144  }
145
146  @Test public void explicitDirectProxy() throws Exception {
147    Address address = new Address(uriHost, uriPort, socketFactory, null, null, authenticator,
148        NO_PROXY, protocols);
149    RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
150        new RouteDatabase());
151
152    assertTrue(routeSelector.hasNext());
153    dns.inetAddresses = makeFakeAddresses(255, 2);
154    assertConnection(routeSelector.next("GET"), address, NO_PROXY, dns.inetAddresses[0], uriPort,
155        false);
156    assertConnection(routeSelector.next("GET"), address, NO_PROXY, dns.inetAddresses[1], uriPort,
157        false);
158
159    assertFalse(routeSelector.hasNext());
160    dns.assertRequests(uri.getHost());
161    proxySelector.assertRequests(); // No proxy selector requests!
162  }
163
164  @Test public void proxySelectorReturnsNull() throws Exception {
165    Address address = new Address(uriHost, uriPort, socketFactory, null, null, authenticator, null,
166        protocols);
167
168    proxySelector.proxies = null;
169    RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
170        new RouteDatabase());
171    proxySelector.assertRequests(uri);
172
173    assertTrue(routeSelector.hasNext());
174    dns.inetAddresses = makeFakeAddresses(255, 1);
175    assertConnection(routeSelector.next("GET"), address, NO_PROXY, dns.inetAddresses[0], uriPort,
176        false);
177    dns.assertRequests(uriHost);
178
179    assertFalse(routeSelector.hasNext());
180  }
181
182  @Test public void proxySelectorReturnsNoProxies() throws Exception {
183    Address address = new Address(uriHost, uriPort, socketFactory, null, null, authenticator, null,
184        protocols);
185    RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
186        new RouteDatabase());
187
188    assertTrue(routeSelector.hasNext());
189    dns.inetAddresses = makeFakeAddresses(255, 2);
190    assertConnection(routeSelector.next("GET"), address, NO_PROXY, dns.inetAddresses[0], uriPort,
191        false);
192    assertConnection(routeSelector.next("GET"), address, NO_PROXY, dns.inetAddresses[1], uriPort,
193        false);
194
195    assertFalse(routeSelector.hasNext());
196    dns.assertRequests(uri.getHost());
197    proxySelector.assertRequests(uri);
198  }
199
200  @Test public void proxySelectorReturnsMultipleProxies() throws Exception {
201    Address address = new Address(uriHost, uriPort, socketFactory, null, null, authenticator, null,
202        protocols);
203
204    proxySelector.proxies.add(proxyA);
205    proxySelector.proxies.add(proxyB);
206    RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
207        new RouteDatabase());
208    proxySelector.assertRequests(uri);
209
210    // First try the IP addresses of the first proxy, in sequence.
211    assertTrue(routeSelector.hasNext());
212    dns.inetAddresses = makeFakeAddresses(255, 2);
213    assertConnection(routeSelector.next("GET"), address, proxyA, dns.inetAddresses[0], proxyAPort,
214        false);
215    assertConnection(routeSelector.next("GET"), address, proxyA, dns.inetAddresses[1], proxyAPort,
216        false);
217    dns.assertRequests(proxyAHost);
218
219    // Next try the IP address of the second proxy.
220    assertTrue(routeSelector.hasNext());
221    dns.inetAddresses = makeFakeAddresses(254, 1);
222    assertConnection(routeSelector.next("GET"), address, proxyB, dns.inetAddresses[0], proxyBPort,
223        false);
224    dns.assertRequests(proxyBHost);
225
226    // Finally try the only IP address of the origin server.
227    assertTrue(routeSelector.hasNext());
228    dns.inetAddresses = makeFakeAddresses(253, 1);
229    assertConnection(routeSelector.next("GET"), address, NO_PROXY, dns.inetAddresses[0], uriPort,
230        false);
231    dns.assertRequests(uriHost);
232
233    assertFalse(routeSelector.hasNext());
234  }
235
236  @Test public void proxySelectorDirectConnectionsAreSkipped() throws Exception {
237    Address address = new Address(uriHost, uriPort, socketFactory, null, null, authenticator, null,
238        protocols);
239
240    proxySelector.proxies.add(NO_PROXY);
241    RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
242        new RouteDatabase());
243    proxySelector.assertRequests(uri);
244
245    // Only the origin server will be attempted.
246    assertTrue(routeSelector.hasNext());
247    dns.inetAddresses = makeFakeAddresses(255, 1);
248    assertConnection(routeSelector.next("GET"), address, NO_PROXY, dns.inetAddresses[0], uriPort,
249        false);
250    dns.assertRequests(uriHost);
251
252    assertFalse(routeSelector.hasNext());
253  }
254
255  @Test public void proxyDnsFailureContinuesToNextProxy() throws Exception {
256    Address address = new Address(uriHost, uriPort, socketFactory, null, null, authenticator, null,
257        protocols);
258
259    proxySelector.proxies.add(proxyA);
260    proxySelector.proxies.add(proxyB);
261    proxySelector.proxies.add(proxyA);
262    RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
263        new RouteDatabase());
264    proxySelector.assertRequests(uri);
265
266    assertTrue(routeSelector.hasNext());
267    dns.inetAddresses = makeFakeAddresses(255, 1);
268    assertConnection(routeSelector.next("GET"), address, proxyA, dns.inetAddresses[0], proxyAPort,
269        false);
270    dns.assertRequests(proxyAHost);
271
272    assertTrue(routeSelector.hasNext());
273    dns.inetAddresses = null;
274    try {
275      routeSelector.next("GET");
276      fail();
277    } catch (UnknownHostException expected) {
278    }
279    dns.assertRequests(proxyBHost);
280
281    assertTrue(routeSelector.hasNext());
282    dns.inetAddresses = makeFakeAddresses(255, 1);
283    assertConnection(routeSelector.next("GET"), address, proxyA, dns.inetAddresses[0], proxyAPort,
284        false);
285    dns.assertRequests(proxyAHost);
286
287    assertTrue(routeSelector.hasNext());
288    dns.inetAddresses = makeFakeAddresses(254, 1);
289    assertConnection(routeSelector.next("GET"), address, NO_PROXY, dns.inetAddresses[0], uriPort,
290        false);
291    dns.assertRequests(uriHost);
292
293    assertFalse(routeSelector.hasNext());
294  }
295
296  // https://github.com/square/okhttp/issues/442
297  @Test public void nonSslErrorAddsAllTlsModesToFailedRoute() throws Exception {
298    Address address = new Address(uriHost, uriPort, socketFactory, sslSocketFactory,
299        hostnameVerifier, authenticator, Proxy.NO_PROXY, protocols);
300    RouteDatabase routeDatabase = new RouteDatabase();
301    RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
302        routeDatabase);
303
304    dns.inetAddresses = makeFakeAddresses(255, 1);
305    Connection connection = routeSelector.next("GET");
306    routeSelector.connectFailed(connection, new IOException("Non SSL exception"));
307    assertTrue(routeDatabase.failedRoutesCount() == 2);
308    assertFalse(routeSelector.hasNext());
309  }
310
311  @Test public void sslErrorAddsOnlyFailedTlsModeToFailedRoute() throws Exception {
312    Address address = new Address(uriHost, uriPort, socketFactory, sslSocketFactory,
313        hostnameVerifier, authenticator, Proxy.NO_PROXY, protocols);
314    RouteDatabase routeDatabase = new RouteDatabase();
315    RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
316        routeDatabase);
317
318    dns.inetAddresses = makeFakeAddresses(255, 1);
319    Connection connection = routeSelector.next("GET");
320    routeSelector.connectFailed(connection, new SSLHandshakeException("SSL exception"));
321    assertTrue(routeDatabase.failedRoutesCount() == 1);
322    assertTrue(routeSelector.hasNext());
323  }
324
325  @Test public void multipleProxiesMultipleInetAddressesMultipleTlsModes() throws Exception {
326    Address address = new Address(uriHost, uriPort, socketFactory, sslSocketFactory,
327        hostnameVerifier, authenticator, null, protocols);
328    proxySelector.proxies.add(proxyA);
329    proxySelector.proxies.add(proxyB);
330    RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
331        new RouteDatabase());
332
333    // Proxy A
334    dns.inetAddresses = makeFakeAddresses(255, 2);
335    assertConnection(routeSelector.next("GET"), address, proxyA, dns.inetAddresses[0], proxyAPort,
336        true);
337    dns.assertRequests(proxyAHost);
338    assertConnection(routeSelector.next("GET"), address, proxyA, dns.inetAddresses[0], proxyAPort,
339        false);
340    assertConnection(routeSelector.next("GET"), address, proxyA, dns.inetAddresses[1], proxyAPort,
341        true);
342    assertConnection(routeSelector.next("GET"), address, proxyA, dns.inetAddresses[1], proxyAPort,
343        false);
344
345    // Proxy B
346    dns.inetAddresses = makeFakeAddresses(254, 2);
347    assertConnection(routeSelector.next("GET"), address, proxyB, dns.inetAddresses[0], proxyBPort,
348        true);
349    dns.assertRequests(proxyBHost);
350    assertConnection(routeSelector.next("GET"), address, proxyB, dns.inetAddresses[0], proxyBPort,
351        false);
352    assertConnection(routeSelector.next("GET"), address, proxyB, dns.inetAddresses[1], proxyBPort,
353        true);
354    assertConnection(routeSelector.next("GET"), address, proxyB, dns.inetAddresses[1], proxyBPort,
355        false);
356
357    // Origin
358    dns.inetAddresses = makeFakeAddresses(253, 2);
359    assertConnection(routeSelector.next("GET"), address, NO_PROXY, dns.inetAddresses[0], uriPort,
360        true);
361    dns.assertRequests(uriHost);
362    assertConnection(routeSelector.next("GET"), address, NO_PROXY, dns.inetAddresses[0], uriPort,
363        false);
364    assertConnection(routeSelector.next("GET"), address, NO_PROXY, dns.inetAddresses[1], uriPort,
365        true);
366    assertConnection(routeSelector.next("GET"), address, NO_PROXY, dns.inetAddresses[1], uriPort,
367        false);
368
369    assertFalse(routeSelector.hasNext());
370  }
371
372  @Test public void failedRoutesAreLast() throws Exception {
373    Address address = new Address(uriHost, uriPort, socketFactory, sslSocketFactory,
374        hostnameVerifier, authenticator, Proxy.NO_PROXY, protocols);
375
376    RouteDatabase routeDatabase = new RouteDatabase();
377    RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
378        routeDatabase);
379    dns.inetAddresses = makeFakeAddresses(255, 1);
380
381    // Extract the regular sequence of routes from selector.
382    List<Connection> regularRoutes = new ArrayList<Connection>();
383    while (routeSelector.hasNext()) {
384      regularRoutes.add(routeSelector.next("GET"));
385    }
386
387    // Check that we do indeed have more than one route.
388    assertTrue(regularRoutes.size() > 1);
389    // Add first regular route as failed.
390    routeDatabase.failed(regularRoutes.get(0).getRoute());
391    // Reset selector
392    routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, routeDatabase);
393
394    List<Connection> routesWithFailedRoute = new ArrayList<Connection>();
395    while (routeSelector.hasNext()) {
396      routesWithFailedRoute.add(routeSelector.next("GET"));
397    }
398
399    assertEquals(regularRoutes.get(0).getRoute(),
400        routesWithFailedRoute.get(routesWithFailedRoute.size() - 1).getRoute());
401    assertEquals(regularRoutes.size(), routesWithFailedRoute.size());
402  }
403
404  private void assertConnection(Connection connection, Address address, Proxy proxy,
405      InetAddress socketAddress, int socketPort, boolean modernTls) {
406    assertEquals(address, connection.getRoute().getAddress());
407    assertEquals(proxy, connection.getRoute().getProxy());
408    assertEquals(socketAddress, connection.getRoute().getSocketAddress().getAddress());
409    assertEquals(socketPort, connection.getRoute().getSocketAddress().getPort());
410    assertEquals(modernTls, connection.getRoute().isModernTls());
411  }
412
413  private static InetAddress[] makeFakeAddresses(int prefix, int count) {
414    try {
415      InetAddress[] result = new InetAddress[count];
416      for (int i = 0; i < count; i++) {
417        result[i] =
418            InetAddress.getByAddress(new byte[] { (byte) prefix, (byte) 0, (byte) 0, (byte) i });
419      }
420      return result;
421    } catch (UnknownHostException e) {
422      throw new AssertionError();
423    }
424  }
425
426  private static class FakeDns implements HostResolver {
427    List<String> requestedHosts = new ArrayList<String>();
428    InetAddress[] inetAddresses;
429
430    @Override public InetAddress[] getAllByName(String host) throws UnknownHostException {
431      requestedHosts.add(host);
432      if (inetAddresses == null) throw new UnknownHostException();
433      return inetAddresses;
434    }
435
436    public void assertRequests(String... expectedHosts) {
437      assertEquals(Arrays.asList(expectedHosts), requestedHosts);
438      requestedHosts.clear();
439    }
440  }
441
442  private static class FakeProxySelector extends ProxySelector {
443    List<URI> requestedUris = new ArrayList<URI>();
444    List<Proxy> proxies = new ArrayList<Proxy>();
445    List<String> failures = new ArrayList<String>();
446
447    @Override public List<Proxy> select(URI uri) {
448      requestedUris.add(uri);
449      return proxies;
450    }
451
452    public void assertRequests(URI... expectedUris) {
453      assertEquals(Arrays.asList(expectedUris), requestedUris);
454      requestedUris.clear();
455    }
456
457    @Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
458      InetSocketAddress socketAddress = (InetSocketAddress) sa;
459      failures.add(
460          String.format("%s %s:%d %s", uri, socketAddress.getHostName(), socketAddress.getPort(),
461              ioe.getMessage()));
462    }
463  }
464}
465