1
0
mirror of https://github.com/square/okhttp.git synced 2026-01-12 10:23:16 +03:00

Merge pull request #1089 from square/jwilson_1013_connection_configuration

Introduce ConnectionConfiguration.
This commit is contained in:
Jesse Wilson
2014-10-13 16:50:12 -04:00
11 changed files with 308 additions and 188 deletions

View File

@@ -15,8 +15,10 @@
*/
package com.squareup.okhttp;
import com.squareup.okhttp.internal.Internal;
import com.squareup.okhttp.internal.RecordingHostnameVerifier;
import com.squareup.okhttp.internal.RecordingOkAuthenticator;
import com.squareup.okhttp.internal.SingleInetAddressNetwork;
import com.squareup.okhttp.internal.SslContextBuilder;
import com.squareup.okhttp.mockwebserver.Dispatcher;
import com.squareup.okhttp.mockwebserver.MockResponse;
@@ -29,6 +31,7 @@ import java.io.InputStream;
import java.net.CookieManager;
import java.net.HttpCookie;
import java.net.HttpURLConnection;
import java.net.SocketException;
import java.net.URL;
import java.security.cert.Certificate;
import java.util.Arrays;
@@ -47,6 +50,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLProtocolException;
import okio.Buffer;
import okio.BufferedSink;
import okio.BufferedSource;
@@ -625,6 +629,7 @@ public final class CallTest {
client.setSslSocketFactory(sslContext.getSocketFactory());
client.setHostnameVerifier(new RecordingHostnameVerifier());
Internal.instance.setNetwork(client, new SingleInetAddressNetwork());
executeSynchronously(new Request.Builder().url(server.getUrl("/")).build())
.assertBody("abc");
@@ -647,6 +652,43 @@ public final class CallTest {
callback.await(request.url()).assertBody("abc");
}
@Test public void noRecoveryFromTlsHandshakeFailureWhenTlsFallbackIsDisabled() throws Exception {
client.setConnectionConfigurations(Arrays.asList(
ConnectionConfiguration.MODERN_TLS, ConnectionConfiguration.CLEARTEXT));
server.useHttps(sslContext.getSocketFactory(), false);
server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE));
server.play();
client.setSslSocketFactory(sslContext.getSocketFactory());
client.setHostnameVerifier(new RecordingHostnameVerifier());
Internal.instance.setNetwork(client, new SingleInetAddressNetwork());
Request request = new Request.Builder().url(server.getUrl("/")).build();
try {
client.newCall(request).execute();
fail();
} catch (SSLProtocolException expected) {
}
}
@Test public void cleartextCallsFailWhenCleartextIsDisabled() throws Exception {
// Configure the client with only TLS configurations. No cleartext!
client.setConnectionConfigurations(Arrays.asList(
ConnectionConfiguration.MODERN_TLS, ConnectionConfiguration.COMPATIBLE_TLS));
server.enqueue(new MockResponse());
server.play();
Request request = new Request.Builder().url(server.getUrl("/")).build();
try {
client.newCall(request).execute();
fail();
} catch (SocketException expected) {
assertTrue(expected.getMessage().contains("exhausted connection configurations"));
}
}
@Test public void setFollowSslRedirectsFalse() throws Exception {
server.useHttps(sslContext.getSocketFactory(), false);
server.enqueue(new MockResponse()
@@ -694,7 +736,7 @@ public final class CallTest {
client.setSslSocketFactory(sslContext.getSocketFactory());
client.setHostnameVerifier(new RecordingHostnameVerifier());
client.setCertificatePinner( new CertificatePinner.Builder()
client.setCertificatePinner(new CertificatePinner.Builder()
.add(server.getHostName(), "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=") // publicobject.com's cert.
.build());

View File

@@ -25,6 +25,7 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.Arrays;
import java.util.List;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import org.junit.After;
@@ -72,10 +73,13 @@ public final class ConnectionPoolTest {
httpServer = new MockWebServer();
spdyServer.useHttps(sslContext.getSocketFactory(), false);
List<ConnectionConfiguration> connectionConfigurations = Util.immutableList(
ConnectionConfiguration.MODERN_TLS, ConnectionConfiguration.CLEARTEXT);
httpServer.play();
httpAddress = new Address(httpServer.getHostName(), httpServer.getPort(), socketFactory, null,
null, null, AuthenticatorAdapter.INSTANCE, null,
Util.immutableList(Protocol.SPDY_3, Protocol.HTTP_1_1));
Util.immutableList(Protocol.SPDY_3, Protocol.HTTP_1_1), connectionConfigurations);
httpSocketAddress = new InetSocketAddress(InetAddress.getByName(httpServer.getHostName()),
httpServer.getPort());
@@ -83,14 +87,14 @@ public final class ConnectionPoolTest {
spdyAddress = new Address(spdyServer.getHostName(), spdyServer.getPort(), socketFactory,
sslContext.getSocketFactory(), new RecordingHostnameVerifier(), CertificatePinner.DEFAULT,
AuthenticatorAdapter.INSTANCE, null,
Util.immutableList(Protocol.SPDY_3, Protocol.HTTP_1_1));
Util.immutableList(Protocol.SPDY_3, Protocol.HTTP_1_1), connectionConfigurations);
spdySocketAddress = new InetSocketAddress(InetAddress.getByName(spdyServer.getHostName()),
spdyServer.getPort());
Route httpRoute = new Route(httpAddress, Proxy.NO_PROXY, httpSocketAddress,
TlsConfiguration.PREFERRED);
ConnectionConfiguration.CLEARTEXT);
Route spdyRoute = new Route(spdyAddress, Proxy.NO_PROXY, spdySocketAddress,
TlsConfiguration.PREFERRED);
ConnectionConfiguration.MODERN_TLS);
pool = new ConnectionPool(poolSize, KEEP_ALIVE_DURATION_MS);
httpA = new Connection(pool, httpRoute);
httpA.connect(200, 200, 200, null);
@@ -136,7 +140,7 @@ public final class ConnectionPoolTest {
assertNull(connection);
connection = new Connection(pool, new Route(httpAddress, Proxy.NO_PROXY, httpSocketAddress,
TlsConfiguration.PREFERRED));
ConnectionConfiguration.CLEARTEXT));
connection.connect(200, 200, 200, null);
connection.setOwner(owner);
assertEquals(0, pool.getConnectionCount());

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2014 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 com.squareup.okhttp.internal;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* A network that resolves only one IP address per host. Use this when testing
* route selection fallbacks to prevent the host machine's various IP addresses
* from interfering.
*/
public class SingleInetAddressNetwork implements Network {
@Override public InetAddress[] resolveInetAddresses(String host) throws UnknownHostException {
InetAddress[] allInetAddresses = Network.DEFAULT.resolveInetAddresses(host);
return new InetAddress[] { allInetAddresses[0] };
}
}

View File

@@ -18,15 +18,16 @@ package com.squareup.okhttp.internal.http;
import com.squareup.okhttp.Address;
import com.squareup.okhttp.Authenticator;
import com.squareup.okhttp.Connection;
import com.squareup.okhttp.ConnectionConfiguration;
import com.squareup.okhttp.ConnectionPool;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Protocol;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.TlsConfiguration;
import com.squareup.okhttp.internal.Internal;
import com.squareup.okhttp.internal.Network;
import com.squareup.okhttp.internal.RouteDatabase;
import com.squareup.okhttp.internal.SslContextBuilder;
import com.squareup.okhttp.internal.Util;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -52,6 +53,11 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public final class RouteSelectorTest {
public final List<ConnectionConfiguration> connectionConfigurations = Util.immutableList(
ConnectionConfiguration.MODERN_TLS,
ConnectionConfiguration.COMPATIBLE_TLS,
ConnectionConfiguration.CLEARTEXT);
private static final int proxyAPort = 1001;
private static final String proxyAHost = "proxyA";
private static final Proxy proxyA =
@@ -88,6 +94,7 @@ public final class RouteSelectorTest {
.setSslSocketFactory(sslSocketFactory)
.setHostnameVerifier(hostnameVerifier)
.setProtocols(protocols)
.setConnectionConfigurations(connectionConfigurations)
.setConnectionPool(ConnectionPool.getDefault());
Internal.instance.setNetwork(client, dns);
@@ -108,7 +115,7 @@ public final class RouteSelectorTest {
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 1);
assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0],
uriPort, TlsConfiguration.FALLBACK);
uriPort, ConnectionConfiguration.CLEARTEXT);
dns.assertRequests(uriHost);
assertFalse(routeSelector.hasNext());
@@ -129,7 +136,7 @@ public final class RouteSelectorTest {
routeDatabase.failed(connection.getRoute());
routeSelector = RouteSelector.get(httpRequest, client);
assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0],
uriPort, TlsConfiguration.FALLBACK);
uriPort, ConnectionConfiguration.CLEARTEXT);
assertFalse(routeSelector.hasNext());
try {
routeSelector.nextUnconnected();
@@ -140,16 +147,16 @@ public final class RouteSelectorTest {
@Test public void explicitProxyTriesThatProxysAddressesOnly() throws Exception {
Address address = new Address(uriHost, uriPort, socketFactory, null, null, null, authenticator,
proxyA, protocols);
proxyA, protocols, connectionConfigurations);
client.setProxy(proxyA);
RouteSelector routeSelector = RouteSelector.get(httpRequest, client);
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 2);
assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[0],
proxyAPort, TlsConfiguration.FALLBACK);
proxyAPort, ConnectionConfiguration.CLEARTEXT);
assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[1],
proxyAPort, TlsConfiguration.FALLBACK);
proxyAPort, ConnectionConfiguration.CLEARTEXT);
assertFalse(routeSelector.hasNext());
dns.assertRequests(proxyAHost);
@@ -158,16 +165,16 @@ public final class RouteSelectorTest {
@Test public void explicitDirectProxy() throws Exception {
Address address = new Address(uriHost, uriPort, socketFactory, null, null, null, authenticator,
NO_PROXY, protocols);
NO_PROXY, protocols, connectionConfigurations);
client.setProxy(NO_PROXY);
RouteSelector routeSelector = RouteSelector.get(httpRequest, client);
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 2);
assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0],
uriPort, TlsConfiguration.FALLBACK);
uriPort, ConnectionConfiguration.CLEARTEXT);
assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[1],
uriPort, TlsConfiguration.FALLBACK);
uriPort, ConnectionConfiguration.CLEARTEXT);
assertFalse(routeSelector.hasNext());
dns.assertRequests(uriHost);
@@ -184,7 +191,7 @@ public final class RouteSelectorTest {
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 1);
assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0],
uriPort, TlsConfiguration.FALLBACK);
uriPort, ConnectionConfiguration.CLEARTEXT);
dns.assertRequests(uriHost);
assertFalse(routeSelector.hasNext());
@@ -197,9 +204,9 @@ public final class RouteSelectorTest {
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 2);
assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0],
uriPort, TlsConfiguration.FALLBACK);
uriPort, ConnectionConfiguration.CLEARTEXT);
assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[1],
uriPort, TlsConfiguration.FALLBACK);
uriPort, ConnectionConfiguration.CLEARTEXT);
assertFalse(routeSelector.hasNext());
dns.assertRequests(uriHost);
@@ -218,23 +225,23 @@ public final class RouteSelectorTest {
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 2);
assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[0], proxyAPort,
TlsConfiguration.FALLBACK);
ConnectionConfiguration.CLEARTEXT);
assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[1], proxyAPort,
TlsConfiguration.FALLBACK);
ConnectionConfiguration.CLEARTEXT);
dns.assertRequests(proxyAHost);
// Next try the IP address of the second proxy.
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(254, 1);
assertConnection(routeSelector.nextUnconnected(), address, proxyB, dns.inetAddresses[0], proxyBPort,
TlsConfiguration.FALLBACK);
ConnectionConfiguration.CLEARTEXT);
dns.assertRequests(proxyBHost);
// Finally try the only IP address of the origin server.
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(253, 1);
assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0], uriPort,
TlsConfiguration.FALLBACK);
ConnectionConfiguration.CLEARTEXT);
dns.assertRequests(uriHost);
assertFalse(routeSelector.hasNext());
@@ -251,7 +258,7 @@ public final class RouteSelectorTest {
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 1);
assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0], uriPort,
TlsConfiguration.FALLBACK);
ConnectionConfiguration.CLEARTEXT);
dns.assertRequests(uriHost);
assertFalse(routeSelector.hasNext());
@@ -269,7 +276,7 @@ public final class RouteSelectorTest {
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 1);
assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[0],
proxyAPort, TlsConfiguration.FALLBACK);
proxyAPort, ConnectionConfiguration.CLEARTEXT);
dns.assertRequests(proxyAHost);
assertTrue(routeSelector.hasNext());
@@ -284,13 +291,13 @@ public final class RouteSelectorTest {
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 1);
assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[0],
proxyAPort, TlsConfiguration.FALLBACK);
proxyAPort, ConnectionConfiguration.CLEARTEXT);
dns.assertRequests(proxyAHost);
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(254, 1);
assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0],
uriPort, TlsConfiguration.FALLBACK);
uriPort, ConnectionConfiguration.CLEARTEXT);
dns.assertRequests(uriHost);
assertFalse(routeSelector.hasNext());
@@ -304,11 +311,11 @@ public final class RouteSelectorTest {
dns.inetAddresses = makeFakeAddresses(255, 1);
Connection connection = routeSelector.nextUnconnected();
routeSelector.connectFailed(connection, new IOException("Non SSL exception"));
assertEquals(RouteSelector.TLS_CONFIGURATIONS.size(), routeDatabase.failedRoutesCount());
assertEquals(2, routeDatabase.failedRoutesCount());
assertFalse(routeSelector.hasNext());
}
@Test public void sslErrorAddsOnlyFailedTlsModeToFailedRoute() throws Exception {
@Test public void sslErrorAddsOnlyFailedConfigurationToFailedRoute() throws Exception {
client.setProxy(Proxy.NO_PROXY);
RouteSelector routeSelector = RouteSelector.get(httpsRequest, client);
@@ -319,9 +326,9 @@ public final class RouteSelectorTest {
assertTrue(routeSelector.hasNext());
}
@Test public void multipleProxiesMultipleInetAddressesMultipleTlsModes() throws Exception {
@Test public void multipleProxiesMultipleInetAddressesMultipleConfigurations() throws Exception {
Address address = new Address(uriHost, uriPort, socketFactory, sslSocketFactory,
hostnameVerifier, null, authenticator, null, protocols);
hostnameVerifier, null, authenticator, null, protocols, connectionConfigurations);
proxySelector.proxies.add(proxyA);
proxySelector.proxies.add(proxyB);
RouteSelector routeSelector = RouteSelector.get(httpsRequest, client);
@@ -329,38 +336,38 @@ public final class RouteSelectorTest {
// Proxy A
dns.inetAddresses = makeFakeAddresses(255, 2);
assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[0],
proxyAPort, TlsConfiguration.PREFERRED);
proxyAPort, ConnectionConfiguration.MODERN_TLS);
dns.assertRequests(proxyAHost);
assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[0],
proxyAPort, TlsConfiguration.FALLBACK);
proxyAPort, ConnectionConfiguration.COMPATIBLE_TLS);
assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[1],
proxyAPort, TlsConfiguration.PREFERRED);
proxyAPort, ConnectionConfiguration.MODERN_TLS);
assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[1],
proxyAPort, TlsConfiguration.FALLBACK);
proxyAPort, ConnectionConfiguration.COMPATIBLE_TLS);
// Proxy B
dns.inetAddresses = makeFakeAddresses(254, 2);
assertConnection(routeSelector.nextUnconnected(), address, proxyB, dns.inetAddresses[0],
proxyBPort, TlsConfiguration.PREFERRED);
proxyBPort, ConnectionConfiguration.MODERN_TLS);
dns.assertRequests(proxyBHost);
assertConnection(routeSelector.nextUnconnected(), address, proxyB, dns.inetAddresses[0],
proxyBPort, TlsConfiguration.FALLBACK);
proxyBPort, ConnectionConfiguration.COMPATIBLE_TLS);
assertConnection(routeSelector.nextUnconnected(), address, proxyB, dns.inetAddresses[1],
proxyBPort, TlsConfiguration.PREFERRED);
proxyBPort, ConnectionConfiguration.MODERN_TLS);
assertConnection(routeSelector.nextUnconnected(), address, proxyB, dns.inetAddresses[1],
proxyBPort, TlsConfiguration.FALLBACK);
proxyBPort, ConnectionConfiguration.COMPATIBLE_TLS);
// Origin
dns.inetAddresses = makeFakeAddresses(253, 2);
assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0],
uriPort, TlsConfiguration.PREFERRED);
uriPort, ConnectionConfiguration.MODERN_TLS);
dns.assertRequests(uriHost);
assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0],
uriPort, TlsConfiguration.FALLBACK);
uriPort, ConnectionConfiguration.COMPATIBLE_TLS);
assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[1],
uriPort, TlsConfiguration.PREFERRED);
uriPort, ConnectionConfiguration.MODERN_TLS);
assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[1],
uriPort, TlsConfiguration.FALLBACK);
uriPort, ConnectionConfiguration.COMPATIBLE_TLS);
assertFalse(routeSelector.hasNext());
}
@@ -395,18 +402,18 @@ public final class RouteSelectorTest {
}
private void assertConnection(Connection connection, Address address, Proxy proxy,
InetAddress socketAddress, int socketPort, TlsConfiguration tlsConfiguration) {
InetAddress socketAddress, int socketPort, ConnectionConfiguration connectionConfiguration) {
assertEquals(address, connection.getRoute().getAddress());
assertEquals(proxy, connection.getRoute().getProxy());
assertEquals(socketAddress, connection.getRoute().getSocketAddress().getAddress());
assertEquals(socketPort, connection.getRoute().getSocketAddress().getPort());
assertEquals(tlsConfiguration, connection.getRoute().getTlsConfiguration());
assertEquals(connectionConfiguration, connection.getRoute().getConnectionConfiguration());
}
/** Returns an address that's without an SSL socket factory or hostname verifier. */
private Address httpAddress() {
return new Address(uriHost, uriPort, socketFactory, null, null, null, authenticator, null,
protocols);
protocols, connectionConfigurations);
}
private static InetAddress[] makeFakeAddresses(int prefix, int count) {

View File

@@ -19,18 +19,18 @@ package com.squareup.okhttp.internal.http;
import com.squareup.okhttp.AbstractResponseCache;
import com.squareup.okhttp.Cache;
import com.squareup.okhttp.Challenge;
import com.squareup.okhttp.ConnectionConfiguration;
import com.squareup.okhttp.ConnectionPool;
import com.squareup.okhttp.Credentials;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.OkUrlFactory;
import com.squareup.okhttp.Protocol;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.TlsConfiguration;
import com.squareup.okhttp.internal.Internal;
import com.squareup.okhttp.internal.Network;
import com.squareup.okhttp.internal.RecordingAuthenticator;
import com.squareup.okhttp.internal.RecordingHostnameVerifier;
import com.squareup.okhttp.internal.RecordingOkAuthenticator;
import com.squareup.okhttp.internal.SingleInetAddressNetwork;
import com.squareup.okhttp.internal.SslContextBuilder;
import com.squareup.okhttp.internal.Util;
import com.squareup.okhttp.internal.huc.CacheAdapter;
@@ -858,12 +858,12 @@ public final class URLConnectionTest {
.setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
.setBody("bogus proxy connect response content");
// Configure a single IP address for the host, so we don't retry once for each. Enqueue a
// response for all TLS configurations that will be attempted.
Internal.instance.setNetwork(client.client(), networkWithFirstAddressOnly());
for (TlsConfiguration tlsConfiguration : RouteSelector.TLS_CONFIGURATIONS) {
server.enqueue(response);
}
// Configure a single IP address for the host and a single configuration, so we only need one
// failure to fail permanently.
Internal.instance.setNetwork(client.client(), new SingleInetAddressNetwork());
client.client().setConnectionConfigurations(
Util.immutableList(ConnectionConfiguration.MODERN_TLS));
server.enqueue(response);
server.play();
client.client().setProxy(server.toProxyAddress());
@@ -3297,14 +3297,4 @@ public final class URLConnectionTest {
server.setProtocolNegotiationEnabled(true);
server.setProtocols(client.client().getProtocols());
}
/** Returns a network that resolves only one IP address per host. */
private Network networkWithFirstAddressOnly() {
return new Network() {
@Override public InetAddress[] resolveInetAddresses(String host) throws UnknownHostException {
InetAddress[] allInetAddresses = Network.DEFAULT.resolveInetAddresses(host);
return new InetAddress[] { allInetAddresses[0] };
}
};
}
}

View File

@@ -44,11 +44,12 @@ public final class Address {
final CertificatePinner certificatePinner;
final Authenticator authenticator;
final List<Protocol> protocols;
final List<ConnectionConfiguration> connectionConfigurations;
public Address(String uriHost, int uriPort, SocketFactory socketFactory,
SSLSocketFactory sslSocketFactory, HostnameVerifier hostnameVerifier,
CertificatePinner certificatePinner, Authenticator authenticator, Proxy proxy,
List<Protocol> protocols) {
List<Protocol> protocols, List<ConnectionConfiguration> connectionConfigurations) {
if (uriHost == null) throw new NullPointerException("uriHost == null");
if (uriPort <= 0) throw new IllegalArgumentException("uriPort <= 0: " + uriPort);
if (authenticator == null) throw new IllegalArgumentException("authenticator == null");
@@ -62,6 +63,7 @@ public final class Address {
this.certificatePinner = certificatePinner;
this.authenticator = authenticator;
this.protocols = Util.immutableList(protocols);
this.connectionConfigurations = Util.immutableList(connectionConfigurations);
}
/** Returns the hostname of the origin server. */
@@ -113,6 +115,10 @@ public final class Address {
return protocols;
}
public List<ConnectionConfiguration> getConnectionConfigurations() {
return connectionConfigurations;
}
/**
* Returns this address's explicitly-specified HTTP proxy, or null to
* delegate to the HTTP client's proxy selector.

View File

@@ -229,7 +229,7 @@ public final class Connection {
SSLSocket sslSocket = (SSLSocket) socket;
// Configure the socket's ciphers, TLS versions, and extensions.
route.tlsConfiguration.apply(sslSocket, route);
route.connectionConfiguration.apply(sslSocket, route);
// Force handshake. This can throw!
sslSocket.startHandshake();
@@ -246,7 +246,7 @@ public final class Connection {
handshake = Handshake.get(sslSocket.getSession());
String maybeProtocol;
if (route.tlsConfiguration.supportsTlsExtensions()
if (route.connectionConfiguration.supportsTlsExtensions()
&& (maybeProtocol = platform.getSelectedProtocol(sslSocket)) != null) {
protocol = Protocol.get(maybeProtocol); // Throws IOE on unknown.
}

View File

@@ -22,8 +22,11 @@ import java.util.List;
import javax.net.ssl.SSLSocket;
/**
* The TLS version and ciphers used to negotiate a secure connection. */
public final class TlsConfiguration {
* Configuration for the socket connection that HTTP traffic travels through.
* For {@code https:} URLs, this includes the TLS version and ciphers to use
* when negotiating a secure connection.
*/
public final class ConnectionConfiguration {
/**
* This is a subset of the cipher suites supported in Chrome 37, current as of 2014-10-5. All of
* these suites are available on Android L; earlier releases support a subset of these suites.
@@ -56,29 +59,46 @@ public final class TlsConfiguration {
private static final String SSL_3_0 = "SSLv3"; // 1996.
/** A modern TLS configuration with extensions like SNI and ALPN available. */
public static final TlsConfiguration PREFERRED = new TlsConfiguration(
CIPHER_SUITES, new String[] { TLS_1_2, TLS_1_1, TLS_1_0, SSL_3_0 }, true);
public static final ConnectionConfiguration MODERN_TLS = new ConnectionConfiguration(
true, CIPHER_SUITES, new String[] { TLS_1_2, TLS_1_1, TLS_1_0, SSL_3_0 }, true);
/** A backwards-compatible fallback configuration for interop with obsolete servers. */
public static final TlsConfiguration FALLBACK = new TlsConfiguration(
CIPHER_SUITES, new String[] { SSL_3_0 }, true);
public static final ConnectionConfiguration COMPATIBLE_TLS = new ConnectionConfiguration(
true, CIPHER_SUITES, new String[] { SSL_3_0 }, true);
/** Unencrypted, unauthenticated connections for {@code http:} URLs. */
public static final ConnectionConfiguration CLEARTEXT = new ConnectionConfiguration(
false, new String[0], new String[0], false);
private final boolean tls;
private final String[] cipherSuites;
private final String[] tlsVersions;
private final boolean supportsTlsExtensions;
/**
* Caches the subset of this TLS configuration that's supported by the platform. It's possible
* that the platform hosts multiple implementations of {@link SSLSocket}, in which case this cache
* will be incorrect.
* Caches the subset of this configuration that's supported by the host
* platform. It's possible that the platform hosts multiple implementations of
* {@link SSLSocket}, in which case this cache will be incorrect.
*/
private TlsConfiguration supportedConfiguration;
private ConnectionConfiguration supportedConfiguration;
private TlsConfiguration(String[] cipherSuites, String[] tlsVersions,
private ConnectionConfiguration(boolean tls, String[] cipherSuites, String[] tlsVersions,
boolean supportsTlsExtensions) {
this.tls = tls;
this.cipherSuites = cipherSuites;
this.tlsVersions = tlsVersions;
this.supportsTlsExtensions = supportsTlsExtensions;
if (tls && (cipherSuites.length == 0 || tlsVersions.length == 0)) {
throw new IllegalArgumentException("Unexpected configuration: " + this);
}
if (!tls && (cipherSuites.length != 0 || tlsVersions.length != 0 || supportsTlsExtensions)) {
throw new IllegalArgumentException("Unexpected configuration: " + this);
}
}
public boolean isTls() {
return tls;
}
public List<String> cipherSuites() {
@@ -95,7 +115,7 @@ public final class TlsConfiguration {
/** Applies this configuration to {@code sslSocket} for {@code route}. */
public void apply(SSLSocket sslSocket, Route route) {
TlsConfiguration configurationToApply = supportedConfiguration;
ConnectionConfiguration configurationToApply = supportedConfiguration;
if (configurationToApply == null) {
configurationToApply = supportedConfiguration(sslSocket);
supportedConfiguration = configurationToApply;
@@ -114,14 +134,22 @@ public final class TlsConfiguration {
* Returns a copy of this that omits cipher suites and TLS versions not
* supported by {@code sslSocket}.
*/
private TlsConfiguration supportedConfiguration(SSLSocket sslSocket) {
private ConnectionConfiguration supportedConfiguration(SSLSocket sslSocket) {
List<String> supportedCipherSuites = Util.intersect(Arrays.asList(cipherSuites),
Arrays.asList(sslSocket.getSupportedCipherSuites()));
List<String> supportedTlsVersions = Util.intersect(Arrays.asList(tlsVersions),
Arrays.asList(sslSocket.getSupportedProtocols()));
return new TlsConfiguration(
return new ConnectionConfiguration(tls,
supportedCipherSuites.toArray(new String[supportedCipherSuites.size()]),
supportedTlsVersions.toArray(new String[supportedTlsVersions.size()]),
supportsTlsExtensions);
}
@Override public String toString() {
return "ConnectionConfiguration(tls=" + tls
+ ", cipherSuites=" + Arrays.toString(cipherSuites)
+ ", tlsVersions=" + Arrays.toString(tlsVersions)
+ ", supportsTlsExtensions=" + supportsTlsExtensions
+ ")";
}
}

View File

@@ -49,6 +49,13 @@ import javax.net.ssl.SSLSocketFactory;
* safely modified with further configuration changes.
*/
public class OkHttpClient implements Cloneable {
private static final List<Protocol> DEFAULT_PROTOCOLS = Util.immutableList(
Protocol.HTTP_2, Protocol.SPDY_3, Protocol.HTTP_1_1);
private static final List<ConnectionConfiguration> DEFAULT_CONNECTION_CONFIGURATIONS =
Util.immutableList(ConnectionConfiguration.MODERN_TLS, ConnectionConfiguration.COMPATIBLE_TLS,
ConnectionConfiguration.CLEARTEXT);
static {
Internal.instance = new Internal() {
@Override public Transport newTransport(
@@ -122,6 +129,7 @@ public class OkHttpClient implements Cloneable {
private Dispatcher dispatcher;
private Proxy proxy;
private List<Protocol> protocols;
private List<ConnectionConfiguration> connectionConfigurations;
private ProxySelector proxySelector;
private CookieHandler cookieHandler;
@@ -152,6 +160,7 @@ public class OkHttpClient implements Cloneable {
this.dispatcher = okHttpClient.dispatcher;
this.proxy = okHttpClient.proxy;
this.protocols = okHttpClient.protocols;
this.connectionConfigurations = okHttpClient.connectionConfigurations;
this.proxySelector = okHttpClient.proxySelector;
this.cookieHandler = okHttpClient.cookieHandler;
this.cache = okHttpClient.cache;
@@ -479,6 +488,16 @@ public class OkHttpClient implements Cloneable {
return protocols;
}
public final OkHttpClient setConnectionConfigurations(
List<ConnectionConfiguration> connectionConfigurations) {
this.connectionConfigurations = Util.immutableList(connectionConfigurations);
return this;
}
public final List<ConnectionConfiguration> getConnectionConfigurations() {
return connectionConfigurations;
}
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@@ -526,7 +545,10 @@ public class OkHttpClient implements Cloneable {
result.connectionPool = ConnectionPool.getDefault();
}
if (result.protocols == null) {
result.protocols = Util.immutableList(Protocol.HTTP_2, Protocol.SPDY_3, Protocol.HTTP_1_1);
result.protocols = DEFAULT_PROTOCOLS;
}
if (result.connectionConfigurations == null) {
result.connectionConfigurations = DEFAULT_CONNECTION_CONFIGURATIONS;
}
if (result.network == null) {
result.network = Network.DEFAULT;

View File

@@ -37,25 +37,26 @@ public final class Route {
final Address address;
final Proxy proxy;
final InetSocketAddress inetSocketAddress;
final TlsConfiguration tlsConfiguration;
/** @deprecated replaced with a constructor that takes a {@link TlsConfiguration}. */
public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress,
String tlsVersion) {
this(address, proxy, inetSocketAddress,
tlsVersion.equals("SSLv3") ? TlsConfiguration.FALLBACK : TlsConfiguration.PREFERRED);
}
final ConnectionConfiguration connectionConfiguration;
public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress,
TlsConfiguration tlsConfiguration) {
if (address == null) throw new NullPointerException("address == null");
if (proxy == null) throw new NullPointerException("proxy == null");
if (inetSocketAddress == null) throw new NullPointerException("inetSocketAddress == null");
if (tlsConfiguration == null) throw new NullPointerException("tlsConfiguration == null");
ConnectionConfiguration connectionConfiguration) {
if (address == null) {
throw new NullPointerException("address == null");
}
if (proxy == null) {
throw new NullPointerException("proxy == null");
}
if (inetSocketAddress == null) {
throw new NullPointerException("inetSocketAddress == null");
}
if (connectionConfiguration == null) {
throw new NullPointerException("connectionConfiguration == null");
}
this.address = address;
this.proxy = proxy;
this.inetSocketAddress = inetSocketAddress;
this.tlsConfiguration = tlsConfiguration;
this.connectionConfiguration = connectionConfiguration;
}
public Address getAddress() {
@@ -73,17 +74,12 @@ public final class Route {
return proxy;
}
/** @deprecated replaced with {@link #getTlsConfiguration()}. */
public String getTlsVersion() {
return tlsConfiguration.tlsVersions().get(0);
}
public InetSocketAddress getSocketAddress() {
return inetSocketAddress;
}
public TlsConfiguration getTlsConfiguration() {
return tlsConfiguration;
public ConnectionConfiguration getConnectionConfiguration() {
return connectionConfiguration;
}
/**
@@ -100,7 +96,7 @@ public final class Route {
return address.equals(other.address)
&& proxy.equals(other.proxy)
&& inetSocketAddress.equals(other.inetSocketAddress)
&& tlsConfiguration.equals(other.tlsConfiguration);
&& connectionConfiguration.equals(other.connectionConfiguration);
}
return false;
}
@@ -110,7 +106,7 @@ public final class Route {
result = 31 * result + address.hashCode();
result = 31 * result + proxy.hashCode();
result = 31 * result + inetSocketAddress.hashCode();
result = 31 * result + tlsConfiguration.hashCode();
result = 31 * result + connectionConfiguration.hashCode();
return result;
}
}

View File

@@ -18,25 +18,25 @@ package com.squareup.okhttp.internal.http;
import com.squareup.okhttp.Address;
import com.squareup.okhttp.CertificatePinner;
import com.squareup.okhttp.Connection;
import com.squareup.okhttp.ConnectionConfiguration;
import com.squareup.okhttp.ConnectionPool;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Route;
import com.squareup.okhttp.TlsConfiguration;
import com.squareup.okhttp.internal.Internal;
import com.squareup.okhttp.internal.Network;
import com.squareup.okhttp.internal.RouteDatabase;
import com.squareup.okhttp.internal.Util;
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.SocketException;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
import javax.net.ssl.HostnameVerifier;
@@ -52,9 +52,6 @@ import static com.squareup.okhttp.internal.Util.getEffectivePort;
* recycled.
*/
public final class RouteSelector {
public static final List<TlsConfiguration> TLS_CONFIGURATIONS = Util.immutableList(
TlsConfiguration.PREFERRED, TlsConfiguration.FALLBACK);
private final Address address;
private final URI uri;
private final Network network;
@@ -67,19 +64,19 @@ public final class RouteSelector {
/* The most recently attempted route. */
private Proxy lastProxy;
private InetSocketAddress lastInetSocketAddress;
private ConnectionConfiguration lastConfiguration;
/* State for negotiating the next proxy to use. */
private boolean hasNextProxy;
private Proxy userSpecifiedProxy;
private Iterator<Proxy> proxySelectorProxies;
private List<Proxy> proxies = Collections.emptyList();
private int nextProxyIndex;
/* State for negotiating the next InetSocketAddress to use. */
private InetAddress[] inetAddresses;
private int nextSocketAddressIndex;
private int socketPort;
/* State for negotiating the next socket address to use. */
private List<InetSocketAddress> inetSocketAddresses = Collections.emptyList();
private int nextInetSocketAddressIndex;
/* TLS configuration to attempt with the connection. */
private int nextTlsConfigurationIndex = TLS_CONFIGURATIONS.size();
private List<ConnectionConfiguration> connectionConfigurations = Collections.emptyList();
private int nextConfigurationIndex;
/* State for negotiating failed routes */
private final List<Route> postponedRoutes = new ArrayList<>();
@@ -114,7 +111,8 @@ public final class RouteSelector {
Address address = new Address(uriHost, getEffectivePort(request.url()),
client.getSocketFactory(), sslSocketFactory, hostnameVerifier, certificatePinner,
client.getAuthenticator(), client.getProxy(), client.getProtocols());
client.getAuthenticator(), client.getProxy(), client.getProtocols(),
client.getConnectionConfigurations());
return new RouteSelector(address, request.uri(), client, request);
}
@@ -124,7 +122,7 @@ public final class RouteSelector {
* least one route.
*/
public boolean hasNext() {
return hasNextTlsConfiguration()
return hasNextConnectionConfiguration()
|| hasNextInetSocketAddress()
|| hasNextProxy()
|| hasNextPostponed();
@@ -150,7 +148,7 @@ public final class RouteSelector {
}
// Compute the next route to attempt.
if (!hasNextTlsConfiguration()) {
if (!hasNextConnectionConfiguration()) {
if (!hasNextInetSocketAddress()) {
if (!hasNextProxy()) {
if (!hasNextPostponed()) {
@@ -159,18 +157,15 @@ public final class RouteSelector {
return new Connection(pool, nextPostponed());
}
lastProxy = nextProxy();
resetNextInetSocketAddress(lastProxy);
}
lastInetSocketAddress = nextInetSocketAddress();
resetNextTlsConfiguration();
}
lastConfiguration = nextConnectionConfiguration();
TlsConfiguration tlsConfiguration = nextTlsConfiguration();
Route route = new Route(address, lastProxy, lastInetSocketAddress, tlsConfiguration);
Route route = new Route(address, lastProxy, lastInetSocketAddress, lastConfiguration);
if (routeDatabase.shouldPostpone(route)) {
postponedRoutes.add(route);
// We will only recurse in order to skip previously failed routes. They will be
// tried last.
// We will only recurse in order to skip previously failed routes. They will be tried last.
return nextUnconnected();
}
@@ -193,65 +188,60 @@ public final class RouteSelector {
routeDatabase.failed(failedRoute);
// If the previously returned route's problem was not related to TLS, and
// the next route only changes the TLS mode, we shouldn't even attempt it.
// This suppresses it in both this selector and also in the route database.
// If the previously returned route's problem was not related to the
// connection's configuration, and the next route only changes that, we
// shouldn't even attempt it. This suppresses it in both this selector
// and also in the route database.
if (!(failure instanceof SSLHandshakeException) && !(failure instanceof SSLProtocolException)) {
while (hasNextTlsConfiguration()) {
while (nextConfigurationIndex < connectionConfigurations.size()) {
Route toSuppress = new Route(address, lastProxy, lastInetSocketAddress,
nextTlsConfiguration());
connectionConfigurations.get(nextConfigurationIndex++));
routeDatabase.failed(toSuppress);
}
}
}
/** Resets {@link #nextProxy} to the first option. */
/** Prepares the proxy servers to try. */
private void resetNextProxy(URI uri, Proxy proxy) {
this.hasNextProxy = true; // This includes NO_PROXY!
if (proxy != null) {
this.userSpecifiedProxy = proxy;
// If the user specifies a proxy, try that and only that.
proxies = Collections.singletonList(proxy);
} else {
List<Proxy> proxyList = proxySelector.select(uri);
if (proxyList != null) {
this.proxySelectorProxies = proxyList.iterator();
}
// Try each of the ProxySelector choices until one connection succeeds. If none succeed
// then we'll try a direct connection below.
proxies = new ArrayList<>();
List<Proxy> selectedProxies = proxySelector.select(uri);
if (selectedProxies != null) proxies.addAll(selectedProxies);
// Finally try a direct connection. We only try it once!
proxies.removeAll(Collections.singleton(Proxy.NO_PROXY));
proxies.add(Proxy.NO_PROXY);
}
nextProxyIndex = 0;
}
/** Returns true if there's another proxy to try. */
private boolean hasNextProxy() {
return hasNextProxy;
return nextProxyIndex < proxies.size();
}
/** Returns the next proxy to try. May be PROXY.NO_PROXY but never null. */
private Proxy nextProxy() {
// If the user specifies a proxy, try that and only that.
if (userSpecifiedProxy != null) {
hasNextProxy = false;
return userSpecifiedProxy;
private Proxy nextProxy() throws IOException {
if (!hasNextProxy()) {
throw new SocketException("No route to " + address.getUriHost()
+ "; exhausted proxy configurations: " + proxies);
}
// Try each of the ProxySelector choices until one connection succeeds. If none succeed
// then we'll try a direct connection below.
if (proxySelectorProxies != null) {
while (proxySelectorProxies.hasNext()) {
Proxy candidate = proxySelectorProxies.next();
if (candidate.type() != Proxy.Type.DIRECT) {
return candidate;
}
}
}
// Finally try a direct connection.
hasNextProxy = false;
return Proxy.NO_PROXY;
Proxy result = proxies.get(nextProxyIndex++);
resetNextInetSocketAddress(result);
return result;
}
/** Resets {@link #nextInetSocketAddress} to the first option. */
/** Prepares the socket addresses to attempt for the current proxy or host. */
private void resetNextInetSocketAddress(Proxy proxy) throws UnknownHostException {
inetAddresses = null; // Clear the addresses. Necessary if getAllByName() below throws!
// Clear the addresses. Necessary if getAllByName() below throws!
inetSocketAddresses = new ArrayList<>();
String socketHost;
int socketPort;
if (proxy.type() == Proxy.Type.DIRECT) {
socketHost = uri.getHost();
socketPort = getEffectivePort(uri);
@@ -267,47 +257,51 @@ public final class RouteSelector {
}
// Try each address for best behavior in mixed IPv4/IPv6 environments.
inetAddresses = network.resolveInetAddresses(socketHost);
nextSocketAddressIndex = 0;
for (InetAddress inetAddress : network.resolveInetAddresses(socketHost)) {
inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort));
}
nextInetSocketAddressIndex = 0;
}
/** Returns true if there's another socket address to try. */
private boolean hasNextInetSocketAddress() {
return inetAddresses != null;
return nextInetSocketAddressIndex < inetSocketAddresses.size();
}
/** Returns the next socket address to try. */
private InetSocketAddress nextInetSocketAddress() throws UnknownHostException {
InetSocketAddress result =
new InetSocketAddress(inetAddresses[nextSocketAddressIndex++], socketPort);
if (nextSocketAddressIndex == inetAddresses.length) {
inetAddresses = null; // So that hasNextInetSocketAddress() returns false.
nextSocketAddressIndex = 0;
private InetSocketAddress nextInetSocketAddress() throws IOException {
if (!hasNextInetSocketAddress()) {
throw new SocketException("No route to " + address.getUriHost()
+ "; exhausted inet socket addresses: " + inetSocketAddresses);
}
InetSocketAddress result = inetSocketAddresses.get(nextInetSocketAddressIndex++);
resetConnectionConfigurations();
return result;
}
/**
* Resets {@link #nextTlsConfiguration} to the first option. For routes that
* don't use TLS, this returns the fallback parameters so that there are no
* unnecessary retries.
*/
private void resetNextTlsConfiguration() {
nextTlsConfigurationIndex = address.getSslSocketFactory() != null
? 0
: TLS_CONFIGURATIONS.size() - 1;
/** Prepares the connection configurations to attempt. */
private void resetConnectionConfigurations() {
connectionConfigurations = new ArrayList<>();
for (ConnectionConfiguration configuration : address.getConnectionConfigurations()) {
if (request.isHttps() == configuration.isTls()) {
connectionConfigurations.add(configuration);
}
}
nextConfigurationIndex = 0;
}
/** Returns true if there's another TLS configuration to try. */
private boolean hasNextTlsConfiguration() {
return nextTlsConfigurationIndex < TLS_CONFIGURATIONS.size();
/** Returns true if there's another connection configuration to try. */
private boolean hasNextConnectionConfiguration() {
return nextConfigurationIndex < connectionConfigurations.size();
}
/** Returns the next TLS configuration to try. */
private TlsConfiguration nextTlsConfiguration() {
if (!hasNextTlsConfiguration()) throw new IllegalStateException("No next TLS configuration");
return TLS_CONFIGURATIONS.get(nextTlsConfigurationIndex++);
/** Returns the next connection configuration to try. */
private ConnectionConfiguration nextConnectionConfiguration() throws IOException {
if (!hasNextConnectionConfiguration()) {
throw new SocketException("No route to " + address.getUriHost()
+ "; exhausted connection configurations: " + connectionConfigurations);
}
return connectionConfigurations.get(nextConfigurationIndex++);
}
/** Returns true if there is another postponed route to try. */