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