/* * Copyright (C) 2012 Square, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package okhttp3.internal.connection; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.ProxySelector; import java.net.SocketAddress; import java.net.URI; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.NoSuchElementException; import javax.net.SocketFactory; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSocketFactory; import okhttp3.Address; import okhttp3.Authenticator; import okhttp3.ConnectionSpec; import okhttp3.FakeDns; import okhttp3.Protocol; import okhttp3.Route; import okhttp3.internal.Util; import okhttp3.internal.http.RecordingProxySelector; import okhttp3.internal.tls.SslClient; import org.junit.Before; import org.junit.Test; import static java.net.Proxy.NO_PROXY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public final class RouteSelectorTest { public final List<ConnectionSpec> connectionSpecs = Util.immutableList( ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS, ConnectionSpec.CLEARTEXT); private static final int proxyAPort = 1001; private static final String proxyAHost = "proxya"; private static final Proxy proxyA = new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(proxyAHost, proxyAPort)); private static final int proxyBPort = 1002; private static final String proxyBHost = "proxyb"; private static final Proxy proxyB = new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(proxyBHost, proxyBPort)); private String uriHost = "hosta"; private int uriPort = 1003; private SocketFactory socketFactory; private final SslClient sslClient = SslClient.localhost(); private final SSLSocketFactory sslSocketFactory = sslClient.socketFactory; private HostnameVerifier hostnameVerifier; private final Authenticator authenticator = Authenticator.NONE; private final List<Protocol> protocols = Arrays.asList(Protocol.HTTP_1_1); private final FakeDns dns = new FakeDns(); private final RecordingProxySelector proxySelector = new RecordingProxySelector(); private RouteDatabase routeDatabase = new RouteDatabase(); @Before public void setUp() throws Exception { socketFactory = SocketFactory.getDefault(); hostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); } @Test public void singleRoute() throws Exception { Address address = httpAddress(); RouteSelector routeSelector = new RouteSelector(address, routeDatabase); assertTrue(routeSelector.hasNext()); dns.set(uriHost, dns.allocate(1)); assertRoute(routeSelector.next(), address, NO_PROXY, dns.lookup(uriHost, 0), uriPort); dns.assertRequests(uriHost); assertFalse(routeSelector.hasNext()); try { routeSelector.next(); fail(); } catch (NoSuchElementException expected) { } } @Test public void singleRouteReturnsFailedRoute() throws Exception { Address address = httpAddress(); RouteSelector routeSelector = new RouteSelector(address, routeDatabase); assertTrue(routeSelector.hasNext()); dns.set(uriHost, dns.allocate(1)); Route route = routeSelector.next(); routeDatabase.failed(route); routeSelector = new RouteSelector(address, routeDatabase); assertRoute(routeSelector.next(), address, NO_PROXY, dns.lookup(uriHost, 0), uriPort); assertFalse(routeSelector.hasNext()); try { routeSelector.next(); fail(); } catch (NoSuchElementException expected) { } } @Test public void explicitProxyTriesThatProxysAddressesOnly() throws Exception { Address address = new Address(uriHost, uriPort, dns, socketFactory, null, null, null, authenticator, proxyA, protocols, connectionSpecs, proxySelector); RouteSelector routeSelector = new RouteSelector(address, routeDatabase); assertTrue(routeSelector.hasNext()); dns.set(proxyAHost, dns.allocate(2)); assertRoute(routeSelector.next(), address, proxyA, dns.lookup(proxyAHost, 0), proxyAPort); assertRoute(routeSelector.next(), address, proxyA, dns.lookup(proxyAHost, 1), proxyAPort); assertFalse(routeSelector.hasNext()); dns.assertRequests(proxyAHost); proxySelector.assertRequests(); // No proxy selector requests! } @Test public void explicitDirectProxy() throws Exception { Address address = new Address(uriHost, uriPort, dns, socketFactory, null, null, null, authenticator, NO_PROXY, protocols, connectionSpecs, proxySelector); RouteSelector routeSelector = new RouteSelector(address, routeDatabase); assertTrue(routeSelector.hasNext()); dns.set(uriHost, dns.allocate(2)); assertRoute(routeSelector.next(), address, NO_PROXY, dns.lookup(uriHost, 0), uriPort); assertRoute(routeSelector.next(), address, NO_PROXY, dns.lookup(uriHost, 1), uriPort); assertFalse(routeSelector.hasNext()); dns.assertRequests(uriHost); proxySelector.assertRequests(); // No proxy selector requests! } @Test public void proxySelectorReturnsNull() throws Exception { ProxySelector nullProxySelector = new ProxySelector() { @Override public List<Proxy> select(URI uri) { assertEquals(uriHost, uri.getHost()); return null; } @Override public void connectFailed( URI uri, SocketAddress socketAddress, IOException e) { throw new AssertionError(); } }; Address address = new Address(uriHost, uriPort, dns, socketFactory, null, null, null, authenticator, null, protocols, connectionSpecs, nullProxySelector); RouteSelector routeSelector = new RouteSelector(address, routeDatabase); assertTrue(routeSelector.hasNext()); dns.set(uriHost, dns.allocate(1)); assertRoute(routeSelector.next(), address, NO_PROXY, dns.lookup(uriHost, 0), uriPort); dns.assertRequests(uriHost); assertFalse(routeSelector.hasNext()); } @Test public void proxySelectorReturnsNoProxies() throws Exception { Address address = httpAddress(); RouteSelector routeSelector = new RouteSelector(address, routeDatabase); assertTrue(routeSelector.hasNext()); dns.set(uriHost, dns.allocate(2)); assertRoute(routeSelector.next(), address, NO_PROXY, dns.lookup(uriHost, 0), uriPort); assertRoute(routeSelector.next(), address, NO_PROXY, dns.lookup(uriHost, 1), uriPort); assertFalse(routeSelector.hasNext()); dns.assertRequests(uriHost); proxySelector.assertRequests(address.url().uri()); } @Test public void proxySelectorReturnsMultipleProxies() throws Exception { Address address = httpAddress(); proxySelector.proxies.add(proxyA); proxySelector.proxies.add(proxyB); RouteSelector routeSelector = new RouteSelector(address, routeDatabase); proxySelector.assertRequests(address.url().uri()); // First try the IP addresses of the first proxy, in sequence. assertTrue(routeSelector.hasNext()); dns.set(proxyAHost, dns.allocate(2)); assertRoute(routeSelector.next(), address, proxyA, dns.lookup(proxyAHost, 0), proxyAPort); assertRoute(routeSelector.next(), address, proxyA, dns.lookup(proxyAHost, 1), proxyAPort); dns.assertRequests(proxyAHost); // Next try the IP address of the second proxy. assertTrue(routeSelector.hasNext()); dns.set(proxyBHost, dns.allocate(1)); assertRoute(routeSelector.next(), address, proxyB, dns.lookup(proxyBHost, 0), proxyBPort); dns.assertRequests(proxyBHost); // No more proxies to try. assertFalse(routeSelector.hasNext()); } @Test public void proxySelectorDirectConnectionsAreSkipped() throws Exception { Address address = httpAddress(); proxySelector.proxies.add(NO_PROXY); RouteSelector routeSelector = new RouteSelector(address, routeDatabase); proxySelector.assertRequests(address.url().uri()); // Only the origin server will be attempted. assertTrue(routeSelector.hasNext()); dns.set(uriHost, dns.allocate(1)); assertRoute(routeSelector.next(), address, NO_PROXY, dns.lookup(uriHost, 0), uriPort); dns.assertRequests(uriHost); assertFalse(routeSelector.hasNext()); } @Test public void proxyDnsFailureContinuesToNextProxy() throws Exception { Address address = httpAddress(); proxySelector.proxies.add(proxyA); proxySelector.proxies.add(proxyB); proxySelector.proxies.add(proxyA); RouteSelector routeSelector = new RouteSelector(address, routeDatabase); proxySelector.assertRequests(address.url().uri()); assertTrue(routeSelector.hasNext()); dns.set(proxyAHost, dns.allocate(1)); assertRoute(routeSelector.next(), address, proxyA, dns.lookup(proxyAHost, 0), proxyAPort); dns.assertRequests(proxyAHost); assertTrue(routeSelector.hasNext()); dns.clear(proxyBHost); try { routeSelector.next(); fail(); } catch (UnknownHostException expected) { } dns.assertRequests(proxyBHost); assertTrue(routeSelector.hasNext()); dns.set(proxyAHost, dns.allocate(1)); assertRoute(routeSelector.next(), address, proxyA, dns.lookup(proxyAHost, 0), proxyAPort); dns.assertRequests(proxyAHost); assertFalse(routeSelector.hasNext()); } @Test public void multipleProxiesMultipleInetAddressesMultipleConfigurations() throws Exception { Address address = httpsAddress(); proxySelector.proxies.add(proxyA); proxySelector.proxies.add(proxyB); RouteSelector routeSelector = new RouteSelector(address, routeDatabase); // Proxy A dns.set(proxyAHost, dns.allocate(2)); assertRoute(routeSelector.next(), address, proxyA, dns.lookup(proxyAHost, 0), proxyAPort); dns.assertRequests(proxyAHost); assertRoute(routeSelector.next(), address, proxyA, dns.lookup(proxyAHost, 1), proxyAPort); // Proxy B dns.set(proxyBHost, dns.allocate(2)); assertRoute(routeSelector.next(), address, proxyB, dns.lookup(proxyBHost, 0), proxyBPort); dns.assertRequests(proxyBHost); assertRoute(routeSelector.next(), address, proxyB, dns.lookup(proxyBHost, 1), proxyBPort); // No more proxies to attempt. assertFalse(routeSelector.hasNext()); } @Test public void failedRoutesAreLast() throws Exception { Address address = httpsAddress(); RouteSelector routeSelector = new RouteSelector(address, routeDatabase); final int numberOfAddresses = 2; dns.set(uriHost, dns.allocate(numberOfAddresses)); // Extract the regular sequence of routes from selector. List<Route> regularRoutes = new ArrayList<>(); while (routeSelector.hasNext()) { regularRoutes.add(routeSelector.next()); } // Check that we do indeed have more than one route. assertEquals(numberOfAddresses, regularRoutes.size()); // Add first regular route as failed. routeDatabase.failed(regularRoutes.get(0)); // Reset selector routeSelector = new RouteSelector(address, routeDatabase); List<Route> routesWithFailedRoute = new ArrayList<>(); while (routeSelector.hasNext()) { routesWithFailedRoute.add(routeSelector.next()); } assertEquals(regularRoutes.get(0), routesWithFailedRoute.get(routesWithFailedRoute.size() - 1)); assertEquals(regularRoutes.size(), routesWithFailedRoute.size()); } @Test public void getHostString() throws Exception { // Name proxy specification. InetSocketAddress socketAddress = InetSocketAddress.createUnresolved("host", 1234); assertEquals("host", RouteSelector.getHostString(socketAddress)); socketAddress = InetSocketAddress.createUnresolved("127.0.0.1", 1234); assertEquals("127.0.0.1", RouteSelector.getHostString(socketAddress)); // InetAddress proxy specification. socketAddress = new InetSocketAddress(InetAddress.getByName("localhost"), 1234); assertEquals("127.0.0.1", RouteSelector.getHostString(socketAddress)); socketAddress = new InetSocketAddress( InetAddress.getByAddress(new byte[] {127, 0, 0, 1}), 1234); assertEquals("127.0.0.1", RouteSelector.getHostString(socketAddress)); socketAddress = new InetSocketAddress( InetAddress.getByAddress("foobar", new byte[] {127, 0, 0, 1}), 1234); assertEquals("127.0.0.1", RouteSelector.getHostString(socketAddress)); } @Test public void routeToString() throws Exception { Route route = new Route(httpAddress(), Proxy.NO_PROXY, InetSocketAddress.createUnresolved("host", 1234)); assertEquals("Route{host:1234}", route.toString()); } private void assertRoute(Route route, Address address, Proxy proxy, InetAddress socketAddress, int socketPort) { assertEquals(address, route.address()); assertEquals(proxy, route.proxy()); assertEquals(socketAddress, route.socketAddress().getAddress()); assertEquals(socketPort, route.socketAddress().getPort()); } /** Returns an address that's without an SSL socket factory or hostname verifier. */ private Address httpAddress() { return new Address(uriHost, uriPort, dns, socketFactory, null, null, null, authenticator, null, protocols, connectionSpecs, proxySelector); } private Address httpsAddress() { return new Address(uriHost, uriPort, dns, socketFactory, sslSocketFactory, hostnameVerifier, null, authenticator, null, protocols, connectionSpecs, proxySelector); } }