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