Skip to content

Commit

Permalink
Merge pull request square#156 from square/jwilson/transports
Browse files Browse the repository at this point in the history
Permit the caller to disable transports.
  • Loading branch information
JakeWharton committed May 4, 2013
2 parents 46ea47b + aee4bff commit 3bbe04a
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 27 deletions.
21 changes: 18 additions & 3 deletions okhttp/src/main/java/com/squareup/okhttp/Address.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*/
package com.squareup.okhttp;

import com.squareup.okhttp.internal.Util;
import java.net.Proxy;
import java.net.UnknownHostException;
import java.util.List;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;

Expand All @@ -39,19 +41,22 @@ public final class Address {
final SSLSocketFactory sslSocketFactory;
final HostnameVerifier hostnameVerifier;
final OkAuthenticator authenticator;
final List<String> transports;

public Address(String uriHost, int uriPort, SSLSocketFactory sslSocketFactory,
HostnameVerifier hostnameVerifier, OkAuthenticator authenticator, Proxy proxy)
throws UnknownHostException {
HostnameVerifier hostnameVerifier, OkAuthenticator authenticator, Proxy proxy,
List<String> transports) throws UnknownHostException {
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");
if (transports == null) throw new IllegalArgumentException("transports == null");
this.proxy = proxy;
this.uriHost = uriHost;
this.uriPort = uriPort;
this.sslSocketFactory = sslSocketFactory;
this.hostnameVerifier = hostnameVerifier;
this.authenticator = authenticator;
this.transports = Util.immutableList(transports);
}

/** Returns the hostname of the origin server. */
Expand Down Expand Up @@ -91,6 +96,14 @@ public OkAuthenticator getAuthenticator() {
return authenticator;
}

/**
* Returns the client's transports. This method always returns a non-null list
* that contains "http/1.1", possibly among other transports.
*/
public List<String> getTransports() {
return transports;
}

/**
* Returns this address's explicitly-specified HTTP proxy, or null to
* delegate to the HTTP client's proxy selector.
Expand All @@ -107,7 +120,8 @@ public Proxy getProxy() {
&& this.uriPort == that.uriPort
&& equal(this.sslSocketFactory, that.sslSocketFactory)
&& equal(this.hostnameVerifier, that.hostnameVerifier)
&& equal(this.authenticator, that.authenticator);
&& equal(this.authenticator, that.authenticator)
&& equal(this.transports, that.transports);
}
return false;
}
Expand All @@ -120,6 +134,7 @@ && equal(this.hostnameVerifier, that.hostnameVerifier)
result = 31 * result + (hostnameVerifier != null ? hostnameVerifier.hashCode() : 0);
result = 31 * result + (authenticator != null ? authenticator.hashCode() : 0);
result = 31 * result + (proxy != null ? proxy.hashCode() : 0);
result = 31 * result + transports.hashCode();
return result;
}
}
6 changes: 3 additions & 3 deletions okhttp/src/main/java/com/squareup/okhttp/Connection.java
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ private void upgradeToTls(TunnelRequest tunnelRequest) throws IOException {
platform.supportTlsIntolerantServer(sslSocket);
}

if (route.modernTls) {
boolean useNpn = route.modernTls && route.address.transports.contains("spdy/3");
if (useNpn) {
platform.setNpnProtocols(sslSocket, NPN_PROTOCOLS);
}

Expand All @@ -150,8 +151,7 @@ private void upgradeToTls(TunnelRequest tunnelRequest) throws IOException {
in = sslSocket.getInputStream();

byte[] selectedProtocol;
if (route.modernTls
&& (selectedProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) {
if (useNpn && (selectedProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) {
if (Arrays.equals(selectedProtocol, SPDY3)) {
sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream.
spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, in, out)
Expand Down
64 changes: 61 additions & 3 deletions okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.squareup.okhttp;

import com.squareup.okhttp.internal.Util;
import com.squareup.okhttp.internal.http.HttpAuthenticator;
import com.squareup.okhttp.internal.http.HttpURLConnectionImpl;
import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;
Expand All @@ -26,17 +27,23 @@
import java.net.ProxySelector;
import java.net.ResponseCache;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;

/** Configures and creates HTTP connections. */
public final class OkHttpClient {
private static final List<String> DEFAULT_TRANSPORTS
= Util.immutableList(Arrays.asList("spdy/3", "http/1.1"));

private Proxy proxy;
private Set<Route> failedRoutes = Collections.synchronizedSet(new LinkedHashSet<Route>());
private List<String> transports;
private final Set<Route> failedRoutes;
private ProxySelector proxySelector;
private CookieHandler cookieHandler;
private ResponseCache responseCache;
Expand All @@ -46,6 +53,14 @@ public final class OkHttpClient {
private ConnectionPool connectionPool;
private boolean followProtocolRedirects = true;

public OkHttpClient() {
this.failedRoutes = Collections.synchronizedSet(new LinkedHashSet<Route>());
}

private OkHttpClient(OkHttpClient copyFrom) {
this.failedRoutes = copyFrom.failedRoutes; // Avoid allocating an unnecessary LinkedHashSet.
}

/**
* Sets the HTTP proxy that will be used by connections created by this
* client. This takes precedence over {@link #setProxySelector}, which is
Expand Down Expand Up @@ -198,6 +213,49 @@ public boolean getFollowProtocolRedirects() {
return followProtocolRedirects;
}

/**
* Configure the transports used by this client to communicate with remote
* servers. By default this client will prefer the most efficient transport
* available, falling back to more ubiquitous transports. Applications should
* only call this method to avoid specific compatibility problems, such as web
* servers that behave incorrectly when SPDY is enabled.
*
* <p>The following transports are currently supported:
* <ul>
* <li><a href="http://www.w3.org/Protocols/rfc2616/rfc2616.html">http/1.1</a>
* <li><a href="http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3">spdy/3</a>
* </ul>
*
* <p><strong>This is an evolving set.</strong> Future releases may drop
* support for transitional transports (like spdy/3), in favor of their
* successors (spdy/4 or http/2.0). The http/1.1 transport will never be
* dropped.
*
* <p>If multiple protocols are specified, <a
* href="https://technotes.googlecode.com/git/nextprotoneg.html">NPN</a> will
* be used to negotiate a transport. Future releases may use another mechanism
* (such as <a href="http://tools.ietf.org/html/draft-friedl-tls-applayerprotoneg-02">ALPN</a>)
* to negotiate a transport.
*
* @param transports the transports to use, in order of preference. The list
* must contain "http/1.1". It must not contain null.
*/
public OkHttpClient setTransports(List<String> transports) {
transports = Util.immutableList(transports);
if (!transports.contains("http/1.1")) {
throw new IllegalArgumentException("transports doesn't contain http/1.1: " + transports);
}
if (transports.contains(null)) {
throw new IllegalArgumentException("transports must not contain null");
}
this.transports = transports;
return this;
}

public List<String> getTransports() {
return transports;
}

public HttpURLConnection open(URL url) {
String protocol = url.getProtocol();
OkHttpClient copy = copyWithDefaults();
Expand All @@ -215,9 +273,8 @@ public HttpURLConnection open(URL url) {
* each field that hasn't been explicitly configured.
*/
private OkHttpClient copyWithDefaults() {
OkHttpClient result = new OkHttpClient();
OkHttpClient result = new OkHttpClient(this);
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();
Expand All @@ -232,6 +289,7 @@ private OkHttpClient copyWithDefaults() {
: HttpAuthenticator.SYSTEM_DEFAULT;
result.connectionPool = connectionPool != null ? connectionPool : ConnectionPool.getDefault();
result.followProtocolRedirects = followProtocolRedirects;
result.transports = transports != null ? transports : DEFAULT_TRANSPORTS;
return result;
}
}
8 changes: 8 additions & 0 deletions okhttp/src/main/java/com/squareup/okhttp/internal/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
import java.net.URL;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

/** Junk drawer of utility methods. */
Expand Down Expand Up @@ -324,4 +327,9 @@ public static String readAsciiLine(InputStream in) throws IOException {
}
return result.toString();
}

/** Returns an immutable copy of {@code list}. */
public static <T> List<T> immutableList(List<T> list) {
return Collections.unmodifiableList(new ArrayList<T>(list));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ protected final void connect() throws IOException {
hostnameVerifier = policy.hostnameVerifier;
}
Address address = new Address(uriHost, getEffectivePort(uri), sslSocketFactory,
hostnameVerifier, policy.authenticator, policy.requestedProxy);
hostnameVerifier, policy.authenticator, policy.requestedProxy, policy.transports);
routeSelector = new RouteSelector(address, uri, policy.proxySelector, policy.connectionPool,
Dns.DEFAULT, policy.getFailedRoutes());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
/* SSL configuration; necessary for HTTP requests that get redirected to HTTPS. */
SSLSocketFactory sslSocketFactory;
HostnameVerifier hostnameVerifier;
List<String> transports;
OkAuthenticator authenticator;
final Set<Route> failedRoutes;

Expand All @@ -115,6 +116,7 @@ public HttpURLConnectionImpl(URL url, OkHttpClient client, OkResponseCache respo
this.connectionPool = client.getConnectionPool();
this.sslSocketFactory = client.getSslSocketFactory();
this.hostnameVerifier = client.getHostnameVerifier();
this.transports = client.getTransports();
this.authenticator = client.getAuthenticator();
this.responseCache = responseCache;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,14 @@ public final class ConnectionPoolTest {
@Before public void setUp() throws Exception {
httpServer.play();
httpAddress = new Address(httpServer.getHostName(), httpServer.getPort(), null, null,
HttpAuthenticator.SYSTEM_DEFAULT, null);
HttpAuthenticator.SYSTEM_DEFAULT, null, Arrays.asList("spdy/3", "http/1.1"));
httpSocketAddress = new InetSocketAddress(InetAddress.getByName(httpServer.getHostName()),
httpServer.getPort());

spdyServer.play();
spdyAddress =
new Address(spdyServer.getHostName(), spdyServer.getPort(), sslContext.getSocketFactory(),
new RecordingHostnameVerifier(), HttpAuthenticator.SYSTEM_DEFAULT, null);
spdyAddress = new Address(spdyServer.getHostName(), spdyServer.getPort(),
sslContext.getSocketFactory(), new RecordingHostnameVerifier(),
HttpAuthenticator.SYSTEM_DEFAULT, null, Arrays.asList("spdy/3", "http/1.1"));
spdySocketAddress = new InetSocketAddress(InetAddress.getByName(spdyServer.getHostName()),
spdyServer.getPort());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,12 @@ public final class RouteSelectorTest {
}

private final OkAuthenticator authenticator = HttpAuthenticator.SYSTEM_DEFAULT;
private final List<String> transports = Arrays.asList("http/1.1");
private final FakeDns dns = new FakeDns();
private final FakeProxySelector proxySelector = new FakeProxySelector();

@Test public void singleRoute() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, authenticator, null);
Address address = new Address(uriHost, uriPort, null, null, authenticator, null, transports);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);

Expand All @@ -102,7 +103,7 @@ public final class RouteSelectorTest {
}

@Test public void singleRouteReturnsFailedRoute() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, authenticator, null);
Address address = new Address(uriHost, uriPort, null, null, authenticator, null, transports);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);

Expand All @@ -123,7 +124,7 @@ public final class RouteSelectorTest {
}

@Test public void explicitProxyTriesThatProxiesAddressesOnly() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, authenticator, proxyA);
Address address = new Address(uriHost, uriPort, null, null, authenticator, proxyA, transports);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);

Expand All @@ -140,7 +141,8 @@ public final class RouteSelectorTest {
}

@Test public void explicitDirectProxy() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, authenticator, NO_PROXY);
Address address = new Address(uriHost, uriPort, null, null, authenticator, NO_PROXY,
transports);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);

Expand All @@ -155,7 +157,7 @@ public final class RouteSelectorTest {
}

@Test public void proxySelectorReturnsNull() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, authenticator, null);
Address address = new Address(uriHost, uriPort, null, null, authenticator, null, transports);

proxySelector.proxies = null;
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Expand All @@ -171,7 +173,7 @@ public final class RouteSelectorTest {
}

@Test public void proxySelectorReturnsNoProxies() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, authenticator, null);
Address address = new Address(uriHost, uriPort, null, null, authenticator, null, transports);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);

Expand All @@ -186,7 +188,7 @@ public final class RouteSelectorTest {
}

@Test public void proxySelectorReturnsMultipleProxies() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, authenticator, null);
Address address = new Address(uriHost, uriPort, null, null, authenticator, null, transports);

proxySelector.proxies.add(proxyA);
proxySelector.proxies.add(proxyB);
Expand Down Expand Up @@ -220,7 +222,7 @@ public final class RouteSelectorTest {
}

@Test public void proxySelectorDirectConnectionsAreSkipped() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, authenticator, null);
Address address = new Address(uriHost, uriPort, null, null, authenticator, null, transports);

proxySelector.proxies.add(NO_PROXY);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Expand All @@ -237,7 +239,7 @@ public final class RouteSelectorTest {
}

@Test public void proxyDnsFailureContinuesToNextProxy() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, authenticator, null);
Address address = new Address(uriHost, uriPort, null, null, authenticator, null, transports);

proxySelector.proxies.add(proxyA);
proxySelector.proxies.add(proxyB);
Expand Down Expand Up @@ -277,7 +279,7 @@ public final class RouteSelectorTest {

@Test public void nonSslErrorAddsAllTlsModesToFailedRoute() throws Exception {
Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator,
Proxy.NO_PROXY);
Proxy.NO_PROXY, transports);
Set<Route> failedRoutes = new LinkedHashSet<Route>();
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
failedRoutes);
Expand All @@ -290,7 +292,7 @@ public final class RouteSelectorTest {

@Test public void sslErrorAddsOnlyFailedTlsModeToFailedRoute() throws Exception {
Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator,
Proxy.NO_PROXY);
Proxy.NO_PROXY, transports);
Set<Route> failedRoutes = new LinkedHashSet<Route>();
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
failedRoutes);
Expand All @@ -303,7 +305,7 @@ public final class RouteSelectorTest {

@Test public void multipleProxiesMultipleInetAddressesMultipleTlsModes() throws Exception {
Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator,
null);
null, transports);
proxySelector.proxies.add(proxyA);
proxySelector.proxies.add(proxyB);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Expand Down Expand Up @@ -342,7 +344,7 @@ public final class RouteSelectorTest {

@Test public void failedRoutesAreLast() throws Exception {
Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator,
Proxy.NO_PROXY);
Proxy.NO_PROXY, transports);

Set<Route> failedRoutes = new LinkedHashSet<Route>(1);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Expand Down
Loading

0 comments on commit 3bbe04a

Please sign in to comment.