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