1
0
mirror of https://github.com/square/okhttp.git synced 2026-01-27 04:22:07 +03:00

Merge pull request #142 from square/marcelo/failed_routes

RouteSelector tries previously failed routes last #106
This commit is contained in:
Marcelo Cortes
2013-04-10 15:20:23 -07:00
10 changed files with 306 additions and 108 deletions

View File

@@ -28,7 +28,6 @@ import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.URL;
@@ -76,10 +75,7 @@ public final class Connection implements Closeable {
'h', 't', 't', 'p', '/', '1', '.', '1'
};
private final Address address;
private final Proxy proxy;
private final InetSocketAddress inetSocketAddress;
private final boolean modernTls;
private final Route route;
private Socket socket;
private InputStream in;
@@ -89,15 +85,8 @@ public final class Connection implements Closeable {
private int httpMinorVersion = 1; // Assume HTTP/1.1
private long idleStartTimeNs;
public Connection(Address address, Proxy proxy, InetSocketAddress inetSocketAddress,
boolean modernTls) {
if (address == null) throw new NullPointerException("address == null");
if (proxy == null) throw new NullPointerException("proxy == null");
if (inetSocketAddress == null) throw new NullPointerException("inetSocketAddress == null");
this.address = address;
this.proxy = proxy;
this.inetSocketAddress = inetSocketAddress;
this.modernTls = modernTls;
public Connection(Route route) {
this.route = route;
}
public void connect(int connectTimeout, int readTimeout, TunnelRequest tunnelRequest)
@@ -106,13 +95,13 @@ public final class Connection implements Closeable {
throw new IllegalStateException("already connected");
}
connected = true;
socket = (proxy.type() != Proxy.Type.HTTP) ? new Socket(proxy) : new Socket();
socket.connect(inetSocketAddress, connectTimeout);
socket = (route.proxy.type() != Proxy.Type.HTTP) ? new Socket(route.proxy) : new Socket();
socket.connect(route.inetSocketAddress, connectTimeout);
socket.setSoTimeout(readTimeout);
in = socket.getInputStream();
out = socket.getOutputStream();
if (address.sslSocketFactory != null) {
if (route.address.sslSocketFactory != null) {
upgradeToTls(tunnelRequest);
}
@@ -136,16 +125,16 @@ public final class Connection implements Closeable {
}
// Create the wrapper over connected socket.
socket = address.sslSocketFactory
.createSocket(socket, address.uriHost, address.uriPort, true /* autoClose */);
socket = route.address.sslSocketFactory
.createSocket(socket, route.address.uriHost, route.address.uriPort, true /* autoClose */);
SSLSocket sslSocket = (SSLSocket) socket;
if (modernTls) {
platform.enableTlsExtensions(sslSocket, address.uriHost);
if (route.modernTls) {
platform.enableTlsExtensions(sslSocket, route.address.uriHost);
} else {
platform.supportTlsIntolerantServer(sslSocket);
}
if (modernTls) {
if (route.modernTls) {
platform.setNpnProtocols(sslSocket, NPN_PROTOCOLS);
}
@@ -153,18 +142,19 @@ public final class Connection implements Closeable {
sslSocket.startHandshake();
// Verify that the socket's certificates are acceptable for the target host.
if (!address.hostnameVerifier.verify(address.uriHost, sslSocket.getSession())) {
throw new IOException("Hostname '" + address.uriHost + "' was not verified");
if (!route.address.hostnameVerifier.verify(route.address.uriHost, sslSocket.getSession())) {
throw new IOException("Hostname '" + route.address.uriHost + "' was not verified");
}
out = sslSocket.getOutputStream();
in = sslSocket.getInputStream();
byte[] selectedProtocol;
if (modernTls && (selectedProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) {
if (route.modernTls && (selectedProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) {
if (Arrays.equals(selectedProtocol, SPDY3)) {
sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream.
spdyConnection = new SpdyConnection.Builder(address.getUriHost(), true, in, out).build();
spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, in, out)
.build();
} else if (!Arrays.equals(selectedProtocol, HTTP_11)) {
throw new IOException(
"Unexpected NPN transport " + new String(selectedProtocol, "ISO-8859-1"));
@@ -181,29 +171,9 @@ public final class Connection implements Closeable {
socket.close();
}
/**
* Returns the proxy that this connection is using.
*
* <strong>Warning:</strong> This may be different than the proxy returned
* by {@link #getAddress}! That is the proxy that the user asked to be
* connected to; this returns the proxy that they were actually connected
* to. The two may disagree when a proxy selector selects a different proxy
* for a connection.
*/
public Proxy getProxy() {
return proxy;
}
public Address getAddress() {
return address;
}
public InetSocketAddress getSocketAddress() {
return inetSocketAddress;
}
public boolean isModernTls() {
return modernTls;
/** Returns the route used by this connection. */
public Route getRoute() {
return route;
}
/**
@@ -284,7 +254,7 @@ public final class Connection implements Closeable {
* we must avoid buffering bytes intended for the higher-level protocol.
*/
public boolean requiresTunnel() {
return address.sslSocketFactory != null && proxy != null && proxy.type() == Proxy.Type.HTTP;
return route.address.sslSocketFactory != null && route.proxy.type() == Proxy.Type.HTTP;
}
/**
@@ -304,9 +274,8 @@ public final class Connection implements Closeable {
case HTTP_PROXY_AUTH:
requestHeaders = new RawHeaders(requestHeaders);
URL url = new URL("https", tunnelRequest.host, tunnelRequest.port, "/");
boolean credentialsFound =
HttpAuthenticator.processAuthHeader(HTTP_PROXY_AUTH, responseHeaders, requestHeaders,
proxy, url);
boolean credentialsFound = HttpAuthenticator.processAuthHeader(HTTP_PROXY_AUTH,
responseHeaders, requestHeaders, route.proxy, url);
if (credentialsFound) {
continue;
} else {

View File

@@ -164,7 +164,7 @@ public class ConnectionPool {
for (ListIterator<Connection> i = connections.listIterator(connections.size());
i.hasPrevious(); ) {
Connection connection = i.previous();
if (!connection.getAddress().equals(address)
if (!connection.getRoute().getAddress().equals(address)
|| !connection.isAlive()
|| System.nanoTime() - connection.getIdleStartTimeNs() >= keepAliveDurationNs) {
continue;

View File

@@ -25,6 +25,9 @@ import java.net.Proxy;
import java.net.ProxySelector;
import java.net.ResponseCache;
import java.net.URL;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
@@ -32,6 +35,7 @@ import javax.net.ssl.SSLSocketFactory;
/** Configures and creates HTTP connections. */
public final class OkHttpClient {
private Proxy proxy;
private Set<Route> failedRoutes = Collections.synchronizedSet(new LinkedHashSet<Route>());
private ProxySelector proxySelector;
private CookieHandler cookieHandler;
private ResponseCache responseCache;
@@ -180,21 +184,22 @@ public final class OkHttpClient {
String protocol = url.getProtocol();
OkHttpClient copy = copyWithDefaults();
if (protocol.equals("http")) {
return new HttpURLConnectionImpl(url, copy, copy.okResponseCache());
return new HttpURLConnectionImpl(url, copy, copy.okResponseCache(), copy.failedRoutes);
} else if (protocol.equals("https")) {
return new HttpsURLConnectionImpl(url, copy, copy.okResponseCache());
return new HttpsURLConnectionImpl(url, copy, copy.okResponseCache(), copy.failedRoutes);
} else {
throw new IllegalArgumentException("Unexpected protocol: " + protocol);
}
}
/**
* Returns a copy of this OkHttpClient that uses the system-wide default for
* Returns a shallow copy of this OkHttpClient that uses the system-wide default for
* each field that hasn't been explicitly configured.
*/
private OkHttpClient copyWithDefaults() {
OkHttpClient result = new OkHttpClient();
result.proxy = proxy;
result.failedRoutes = failedRoutes;
result.proxySelector = proxySelector != null ? proxySelector : ProxySelector.getDefault();
result.cookieHandler = cookieHandler != null ? cookieHandler : CookieHandler.getDefault();
result.responseCache = responseCache != null ? responseCache : ResponseCache.getDefault();

View File

@@ -0,0 +1,91 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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 com.squareup.okhttp;
import java.net.InetSocketAddress;
import java.net.Proxy;
/** Represents the route used by a connection to reach an endpoint. */
public class Route {
final Address address;
final Proxy proxy;
final InetSocketAddress inetSocketAddress;
final boolean modernTls;
public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress,
boolean modernTls) {
if (address == null) throw new NullPointerException("address == null");
if (proxy == null) throw new NullPointerException("proxy == null");
if (inetSocketAddress == null) throw new NullPointerException("inetSocketAddress == null");
this.address = address;
this.proxy = proxy;
this.inetSocketAddress = inetSocketAddress;
this.modernTls = modernTls;
}
/** Returns the {@link Address} of this route. */
public Address getAddress() {
return address;
}
/**
* Returns the {@link Proxy} of this route.
*
* <strong>Warning:</strong> This may be different than the proxy returned
* by {@link #getAddress}! That is the proxy that the user asked to be
* connected to; this returns the proxy that they were actually connected
* to. The two may disagree when a proxy selector selects a different proxy
* for a connection.
*/
public Proxy getProxy() {
return proxy;
}
/** Returns the {@link InetSocketAddress} of this route. */
public InetSocketAddress getSocketAddress() {
return inetSocketAddress;
}
/** Returns true if this route uses modern tls. */
public boolean isModernTls() {
return modernTls;
}
/** Returns a copy of this route with flipped tls mode. */
public Route flipTlsMode() {
return new Route(address, proxy, inetSocketAddress, !modernTls);
}
@Override public boolean equals(Object obj) {
if (obj instanceof Route) {
Route other = (Route) obj;
return (address.equals(other.address)
&& proxy.equals(other.proxy)
&& inetSocketAddress.equals(other.inetSocketAddress)
&& modernTls == other.modernTls);
}
return false;
}
@Override public int hashCode() {
int result = 17;
result = 31 * result + address.hashCode();
result = 31 * result + proxy.hashCode();
result = 31 * result + inetSocketAddress.hashCode();
result = result + (modernTls ? (31 * result) : 0);
return result;
}
}

View File

@@ -197,6 +197,7 @@ public class HttpEngine {
sendSocketRequest();
} else if (connection != null) {
policy.connectionPool.recycle(connection);
policy.getFailedRoutes().remove(connection.getRoute());
connection = null;
}
}
@@ -278,16 +279,17 @@ public class HttpEngine {
}
Address address = new Address(uriHost, getEffectivePort(uri), sslSocketFactory,
hostnameVerifier, policy.requestedProxy);
routeSelector =
new RouteSelector(address, uri, policy.proxySelector, policy.connectionPool, Dns.DEFAULT);
routeSelector = new RouteSelector(address, uri, policy.proxySelector, policy.connectionPool,
Dns.DEFAULT, policy.getFailedRoutes());
}
connection = routeSelector.next();
if (!connection.isConnected()) {
connection.connect(policy.getConnectTimeout(), policy.getReadTimeout(), getTunnelConfig());
policy.connectionPool.maybeShare(connection);
policy.getFailedRoutes().remove(connection.getRoute());
}
connected(connection);
if (connection.getProxy() != policy.requestedProxy) {
if (connection.getRoute().getProxy() != policy.requestedProxy) {
// Update the request line if the proxy changed; it may need a host name.
requestHeaders.getHeaders().setRequestLine(getRequestLine());
}
@@ -573,7 +575,7 @@ public class HttpEngine {
protected boolean includeAuthorityInRequestLine() {
return connection == null
? policy.usingProxy() // A proxy was requested.
: connection.getProxy().type() == Proxy.Type.HTTP; // A proxy was selected.
: connection.getRoute().getProxy().type() == Proxy.Type.HTTP; // A proxy was selected.
}
public static String getDefaultUserAgent() {

View File

@@ -20,6 +20,7 @@ package com.squareup.okhttp.internal.http;
import com.squareup.okhttp.Connection;
import com.squareup.okhttp.ConnectionPool;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Route;
import com.squareup.okhttp.internal.Util;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -38,6 +39,7 @@ import java.security.Permission;
import java.security.cert.CertificateException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocketFactory;
@@ -81,6 +83,8 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
/* SSL configuration; necessary for HTTP requests that get redirected to HTTPS. */
SSLSocketFactory sslSocketFactory;
HostnameVerifier hostnameVerifier;
final Set<Route> failedRoutes;
private final RawHeaders rawRequestHeaders = new RawHeaders();
@@ -89,9 +93,11 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
protected IOException httpEngineFailure;
protected HttpEngine httpEngine;
public HttpURLConnectionImpl(URL url, OkHttpClient client, OkResponseCache responseCache) {
public HttpURLConnectionImpl(URL url, OkHttpClient client, OkResponseCache responseCache,
Set<Route> failedRoutes) {
super(url);
this.followProtocolRedirects = client.getFollowProtocolRedirects();
this.failedRoutes = failedRoutes;
this.requestedProxy = client.getProxy();
this.proxySelector = client.getProxySelector();
this.cookieHandler = client.getCookieHandler();
@@ -101,6 +107,10 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
this.responseCache = responseCache;
}
Set<Route> getFailedRoutes() {
return failedRoutes;
}
@Override public final void connect() throws IOException {
initHttpEngine();
boolean success;
@@ -401,7 +411,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
*/
private Retry processResponseHeaders() throws IOException {
Proxy selectedProxy = httpEngine.connection != null
? httpEngine.connection.getProxy()
? httpEngine.connection.getRoute().getProxy()
: requestedProxy;
final int responseCode = getResponseCode();
switch (responseCode) {

View File

@@ -18,6 +18,7 @@ package com.squareup.okhttp.internal.http;
import com.squareup.okhttp.Connection;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Route;
import com.squareup.okhttp.TunnelRequest;
import java.io.IOException;
import java.io.InputStream;
@@ -32,6 +33,7 @@ import java.security.Principal;
import java.security.cert.Certificate;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLPeerUnverifiedException;
@@ -45,9 +47,10 @@ public final class HttpsURLConnectionImpl extends HttpsURLConnection {
/** HttpUrlConnectionDelegate allows reuse of HttpURLConnectionImpl. */
private final HttpUrlConnectionDelegate delegate;
public HttpsURLConnectionImpl(URL url, OkHttpClient client, OkResponseCache responseCache) {
public HttpsURLConnectionImpl(URL url, OkHttpClient client, OkResponseCache responseCache,
Set<Route> failedRoutes) {
super(url);
delegate = new HttpUrlConnectionDelegate(url, client, responseCache);
delegate = new HttpUrlConnectionDelegate(url, client, responseCache, failedRoutes);
}
@Override public String getCipherSuite() {
@@ -399,8 +402,9 @@ public final class HttpsURLConnectionImpl extends HttpsURLConnection {
}
private final class HttpUrlConnectionDelegate extends HttpURLConnectionImpl {
private HttpUrlConnectionDelegate(URL url, OkHttpClient client, OkResponseCache responseCache) {
super(url, client, responseCache);
private HttpUrlConnectionDelegate(URL url, OkHttpClient client, OkResponseCache responseCache,
Set<Route> failedRoutes) {
super(url, client, responseCache, failedRoutes);
}
@Override protected HttpURLConnection getHttpConnectionToCache() {
@@ -425,8 +429,7 @@ public final class HttpsURLConnectionImpl extends HttpsURLConnection {
* @param policy the HttpURLConnectionImpl with connection configuration
*/
public HttpsEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders,
Connection connection, RetryableOutputStream requestBody)
throws IOException {
Connection connection, RetryableOutputStream requestBody) throws IOException {
super(policy, method, requestHeaders, connection, requestBody);
this.sslSocket = connection != null ? (SSLSocket) connection.getSocket() : null;
}

View File

@@ -18,6 +18,7 @@ package com.squareup.okhttp.internal.http;
import com.squareup.okhttp.Address;
import com.squareup.okhttp.Connection;
import com.squareup.okhttp.ConnectionPool;
import com.squareup.okhttp.Route;
import com.squareup.okhttp.internal.Dns;
import java.io.IOException;
import java.net.InetAddress;
@@ -28,8 +29,11 @@ import java.net.SocketAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.net.ssl.SSLHandshakeException;
import static com.squareup.okhttp.internal.Util.getEffectivePort;
@@ -51,6 +55,7 @@ public final class RouteSelector {
private final ProxySelector proxySelector;
private final ConnectionPool pool;
private final Dns dns;
private final Set<Route> failedRoutes;
/* The most recently attempted route. */
private Proxy lastProxy;
@@ -64,19 +69,23 @@ public final class RouteSelector {
/* State for negotiating the next InetSocketAddress to use. */
private InetAddress[] socketAddresses;
private int nextSocketAddressIndex;
private String socketHost;
private int socketPort;
/* State for negotiating the next TLS configuration */
private int nextTlsMode = TLS_MODE_NULL;
/* State for negotiating failed routes */
private final List<Route> postponedRoutes;
public RouteSelector(Address address, URI uri, ProxySelector proxySelector, ConnectionPool pool,
Dns dns) {
Dns dns, Set<Route> failedRoutes) {
this.address = address;
this.uri = uri;
this.proxySelector = proxySelector;
this.pool = pool;
this.dns = dns;
this.failedRoutes = failedRoutes;
this.postponedRoutes = new LinkedList<Route>();
resetNextProxy(uri, address.getProxy());
}
@@ -86,7 +95,7 @@ public final class RouteSelector {
* least one route.
*/
public boolean hasNext() {
return hasNextTlsMode() || hasNextInetSocketAddress() || hasNextProxy();
return hasNextTlsMode() || hasNextInetSocketAddress() || hasNextProxy() || hasNextPostponed();
}
/**
@@ -105,7 +114,10 @@ public final class RouteSelector {
if (!hasNextTlsMode()) {
if (!hasNextInetSocketAddress()) {
if (!hasNextProxy()) {
throw new NoSuchElementException();
if (!hasNextPostponed()) {
throw new NoSuchElementException();
}
return new Connection(nextPostponed());
}
lastProxy = nextProxy();
resetNextInetSocketAddress(lastProxy);
@@ -113,9 +125,17 @@ public final class RouteSelector {
lastInetSocketAddress = nextInetSocketAddress();
resetNextTlsMode();
}
boolean modernTls = nextTlsMode() == TLS_MODE_MODERN;
return new Connection(address, lastProxy, lastInetSocketAddress, modernTls);
boolean modernTls = nextTlsMode() == TLS_MODE_MODERN;
Route route = new Route(address, lastProxy, lastInetSocketAddress, modernTls);
if (failedRoutes.contains(route)) {
postponedRoutes.add(route);
// We will only recurse in order to skip previously failed routes. They will be
// tried last.
return next();
}
return new Connection(route);
}
/**
@@ -123,9 +143,17 @@ public final class RouteSelector {
* failure on a connection returned by this route selector.
*/
public void connectFailed(Connection connection, IOException failure) {
if (connection.getProxy().type() != Proxy.Type.DIRECT && proxySelector != null) {
Route failedRoute = connection.getRoute();
if (failedRoute.getProxy().type() != Proxy.Type.DIRECT && proxySelector != null) {
// Tell the proxy selector when we fail to connect on a fresh connection.
proxySelector.connectFailed(uri, connection.getProxy().address(), failure);
proxySelector.connectFailed(uri, failedRoute.getProxy().address(), failure);
}
failedRoutes.add(failedRoute);
if (!(failure instanceof SSLHandshakeException)) {
// If the problem was not related to SSL then it will also fail with
// a different Tls mode therefore we can be proactive about it.
failedRoutes.add(failedRoute.flipTlsMode());
}
}
@@ -175,6 +203,7 @@ public final class RouteSelector {
private void resetNextInetSocketAddress(Proxy proxy) throws UnknownHostException {
socketAddresses = null; // Clear the addresses. Necessary if getAllByName() below throws!
String socketHost;
if (proxy.type() == Proxy.Type.DIRECT) {
socketHost = uri.getHost();
socketPort = getEffectivePort(uri);
@@ -233,4 +262,14 @@ public final class RouteSelector {
throw new AssertionError();
}
}
/** Returns true if there is another postponed route to try. */
private boolean hasNextPostponed() {
return !postponedRoutes.isEmpty();
}
/** Returns the next postponed route to try. */
private Route nextPostponed() {
return postponedRoutes.remove(0);
}
}

View File

@@ -81,19 +81,21 @@ public final class ConnectionPoolTest {
spdySocketAddress = new InetSocketAddress(InetAddress.getByName(spdyServer.getHostName()),
spdyServer.getPort());
httpA = new Connection(httpAddress, Proxy.NO_PROXY, httpSocketAddress, true);
Route httpRoute = new Route(httpAddress, Proxy.NO_PROXY, httpSocketAddress, true);
Route spdyRoute = new Route(spdyAddress, Proxy.NO_PROXY, spdySocketAddress, true);
httpA = new Connection(httpRoute);
httpA.connect(100, 100, null);
httpB = new Connection(httpAddress, Proxy.NO_PROXY, httpSocketAddress, true);
httpB = new Connection(httpRoute);
httpB.connect(100, 100, null);
httpC = new Connection(httpAddress, Proxy.NO_PROXY, httpSocketAddress, true);
httpC = new Connection(httpRoute);
httpC.connect(100, 100, null);
httpD = new Connection(httpAddress, Proxy.NO_PROXY, httpSocketAddress, true);
httpD = new Connection(httpRoute);
httpD.connect(100, 100, null);
httpE = new Connection(httpAddress, Proxy.NO_PROXY, httpSocketAddress, true);
httpE = new Connection(httpRoute);
httpE.connect(100, 100, null);
spdyA = new Connection(spdyAddress, Proxy.NO_PROXY, spdySocketAddress, true);
spdyA = new Connection(spdyRoute);
spdyA.connect(100, 100, null);
spdyB = new Connection(spdyAddress, Proxy.NO_PROXY, spdySocketAddress, true);
spdyB = new Connection(spdyRoute);
spdyB.connect(100, 100, null);
}
@@ -115,7 +117,7 @@ public final class ConnectionPoolTest {
Connection connection = pool.get(httpAddress);
assertNull(connection);
connection = new Connection(httpAddress, Proxy.NO_PROXY, httpSocketAddress, true);
connection = new Connection(new Route(httpAddress, Proxy.NO_PROXY, httpSocketAddress, true));
connection.connect(100, 100, null);
assertEquals(0, pool.getConnectionCount());
pool.recycle(connection);

View File

@@ -18,6 +18,7 @@ package com.squareup.okhttp.internal.http;
import com.squareup.okhttp.Address;
import com.squareup.okhttp.Connection;
import com.squareup.okhttp.ConnectionPool;
import com.squareup.okhttp.Route;
import com.squareup.okhttp.internal.Dns;
import com.squareup.okhttp.internal.SslContextBuilder;
import java.io.IOException;
@@ -30,10 +31,14 @@ import java.net.URI;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocketFactory;
import org.junit.Test;
@@ -60,6 +65,7 @@ public final class RouteSelectorTest {
private static final SSLSocketFactory socketFactory;
private static final HostnameVerifier hostnameVerifier;
private static final ConnectionPool pool;
static {
try {
uri = new URI("http://" + uriHost + ":" + uriPort + "/path");
@@ -77,7 +83,8 @@ public final class RouteSelectorTest {
@Test public void singleRoute() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, null);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 1);
@@ -92,9 +99,31 @@ public final class RouteSelectorTest {
}
}
@Test public void singleRouteReturnsFailedRoute() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, null);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 1);
Connection connection = routeSelector.next();
Set<Route> failedRoutes = new LinkedHashSet<Route>();
failedRoutes.add(connection.getRoute());
routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
assertConnection(routeSelector.next(), address, NO_PROXY, dns.inetAddresses[0], uriPort, false);
assertFalse(routeSelector.hasNext());
try {
routeSelector.next();
fail();
} catch (NoSuchElementException expected) {
}
}
@Test public void explicitProxyTriesThatProxiesAddressesOnly() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, proxyA);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 2);
@@ -110,7 +139,8 @@ public final class RouteSelectorTest {
@Test public void explicitDirectProxy() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, NO_PROXY);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 2);
@@ -126,7 +156,8 @@ public final class RouteSelectorTest {
Address address = new Address(uriHost, uriPort, null, null, null);
proxySelector.proxies = null;
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
proxySelector.assertRequests(uri);
assertTrue(routeSelector.hasNext());
@@ -139,7 +170,8 @@ public final class RouteSelectorTest {
@Test public void proxySelectorReturnsNoProxies() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, null);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 2);
@@ -156,7 +188,8 @@ public final class RouteSelectorTest {
proxySelector.proxies.add(proxyA);
proxySelector.proxies.add(proxyB);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
proxySelector.assertRequests(uri);
// First try the IP addresses of the first proxy, in sequence.
@@ -188,7 +221,8 @@ public final class RouteSelectorTest {
Address address = new Address(uriHost, uriPort, null, null, null);
proxySelector.proxies.add(NO_PROXY);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
proxySelector.assertRequests(uri);
// Only the origin server will be attempted.
@@ -206,7 +240,8 @@ public final class RouteSelectorTest {
proxySelector.proxies.add(proxyA);
proxySelector.proxies.add(proxyB);
proxySelector.proxies.add(proxyA);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
proxySelector.assertRequests(uri);
assertTrue(routeSelector.hasNext());
@@ -238,28 +273,38 @@ public final class RouteSelectorTest {
assertFalse(routeSelector.hasNext());
}
@Test public void multipleTlsModes() throws Exception {
@Test public void nonSslErrorAddsAllTlsModesToFailedRoute() throws Exception {
Address address =
new Address(uriHost, uriPort, socketFactory, hostnameVerifier, Proxy.NO_PROXY);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
Set<Route> failedRoutes = new LinkedHashSet<Route>();
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
failedRoutes);
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 1);
assertConnection(routeSelector.next(), address, NO_PROXY, dns.inetAddresses[0], uriPort, true);
dns.assertRequests(uriHost);
Connection connection = routeSelector.next();
routeSelector.connectFailed(connection, new IOException("Non SSL exception"));
assertTrue(failedRoutes.size() == 2);
}
assertTrue(routeSelector.hasNext());
assertConnection(routeSelector.next(), address, NO_PROXY, dns.inetAddresses[0], uriPort, false);
dns.assertRequests(); // No more DNS requests since the previous!
@Test public void sslErrorAddsOnlyFailedTlsModeToFailedRoute() throws Exception {
Address address =
new Address(uriHost, uriPort, socketFactory, hostnameVerifier, Proxy.NO_PROXY);
Set<Route> failedRoutes = new LinkedHashSet<Route>();
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
failedRoutes);
assertFalse(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 1);
Connection connection = routeSelector.next();
routeSelector.connectFailed(connection, new SSLHandshakeException("SSL exception"));
assertTrue(failedRoutes.size() == 1);
}
@Test public void multipleProxiesMultipleInetAddressesMultipleTlsModes() throws Exception {
Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, null);
proxySelector.proxies.add(proxyA);
proxySelector.proxies.add(proxyB);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
// Proxy A
dns.inetAddresses = makeFakeAddresses(255, 2);
@@ -292,13 +337,45 @@ public final class RouteSelectorTest {
assertFalse(routeSelector.hasNext());
}
@Test public void failedRoutesAreLast() throws Exception {
Address address =
new Address(uriHost, uriPort, socketFactory, hostnameVerifier, Proxy.NO_PROXY);
Set<Route> failedRoutes = new LinkedHashSet<Route>(1);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
failedRoutes);
dns.inetAddresses = makeFakeAddresses(255, 1);
// Extract the regular sequence of routes from selector.
List<Connection> regularRoutes = new ArrayList<Connection>();
while (routeSelector.hasNext()) {
regularRoutes.add(routeSelector.next());
}
// Check that we do indeed have more than one route.
assertTrue(regularRoutes.size() > 1);
// Add first regular route as failed.
failedRoutes.add(regularRoutes.get(0).getRoute());
// Reset selector
routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, failedRoutes);
List<Connection> routesWithFailedRoute = new ArrayList<Connection>();
while (routeSelector.hasNext()) {
routesWithFailedRoute.add(routeSelector.next());
}
assertEquals(regularRoutes.get(0).getRoute(),
routesWithFailedRoute.get(routesWithFailedRoute.size() - 1).getRoute());
assertEquals(regularRoutes.size(), routesWithFailedRoute.size());
}
private void assertConnection(Connection connection, Address address, Proxy proxy,
InetAddress socketAddress, int socketPort, boolean modernTls) {
assertEquals(address, connection.getAddress());
assertEquals(proxy, connection.getProxy());
assertEquals(socketAddress, connection.getSocketAddress().getAddress());
assertEquals(socketPort, connection.getSocketAddress().getPort());
assertEquals(modernTls, connection.isModernTls());
assertEquals(address, connection.getRoute().getAddress());
assertEquals(proxy, connection.getRoute().getProxy());
assertEquals(socketAddress, connection.getRoute().getSocketAddress().getAddress());
assertEquals(socketPort, connection.getRoute().getSocketAddress().getPort());
assertEquals(modernTls, connection.getRoute().isModernTls());
}
private static InetAddress[] makeFakeAddresses(int prefix, int count) {