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

Permit the caller to disable transports.

This is currently an underpowered API; in practice the only thing
you can do is disable spdy/3. In the future we may support more
transports (spdy/4, http/2.0) and I don't want to make that an
API change.
This commit is contained in:
jwilson
2013-04-28 13:39:08 -04:00
parent 6abac65309
commit aee4bff804
9 changed files with 135 additions and 27 deletions

View File

@@ -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;
@@ -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. */
@@ -91,6 +96,14 @@ public final class Address {
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.
@@ -107,7 +120,8 @@ public final class Address {
&& 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;
}
@@ -120,6 +134,7 @@ public final class Address {
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;
}
}

View File

@@ -134,7 +134,8 @@ public final class Connection implements Closeable {
platform.supportTlsIntolerantServer(sslSocket);
}
if (route.modernTls) {
boolean useNpn = route.modernTls && route.address.transports.contains("spdy/3");
if (useNpn) {
platform.setNpnProtocols(sslSocket, NPN_PROTOCOLS);
}
@@ -150,8 +151,7 @@ public final class Connection implements Closeable {
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)

View File

@@ -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;
@@ -27,8 +28,10 @@ import java.net.Proxy;
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;
@@ -36,8 +39,12 @@ 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;
@@ -47,6 +54,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
@@ -199,6 +214,49 @@ public final class OkHttpClient {
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();
@@ -216,9 +274,8 @@ public final class OkHttpClient {
* 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();
@@ -233,6 +290,7 @@ public final class OkHttpClient {
: HttpAuthenticator.SYSTEM_DEFAULT;
result.connectionPool = connectionPool != null ? connectionPool : ConnectionPool.getDefault();
result.followProtocolRedirects = followProtocolRedirects;
result.transports = transports != null ? transports : DEFAULT_TRANSPORTS;
return result;
}
}

View File

@@ -29,6 +29,9 @@ import java.net.URI;
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. */
@@ -324,4 +327,9 @@ public final class Util {
}
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));
}
}

View File

@@ -278,7 +278,7 @@ public class HttpEngine {
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());
}

View File

@@ -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;
@@ -115,6 +116,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
this.connectionPool = client.getConnectionPool();
this.sslSocketFactory = client.getSslSocketFactory();
this.hostnameVerifier = client.getHostnameVerifier();
this.transports = client.getTransports();
this.authenticator = client.getAuthenticator();
this.responseCache = responseCache;
}

View File

@@ -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());

View File

@@ -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);
@@ -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);
@@ -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);
@@ -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);
@@ -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,
@@ -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);
@@ -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);
@@ -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,
@@ -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);
@@ -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);
@@ -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);
@@ -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,
@@ -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,

View File

@@ -2476,6 +2476,29 @@ public final class URLConnectionTest {
assertTrue(call, call.contains("challenges=[Basic realm=\"protected area\"]"));
}
@Test public void setTransports() throws Exception {
server.enqueue(new MockResponse().setBody("A"));
server.play();
client.setTransports(Arrays.asList("http/1.1"));
assertContent("A", client.open(server.getUrl("/")));
}
@Test public void setTransportsWithoutHttp11() throws Exception {
try {
client.setTransports(Arrays.asList("spdy/3"));
fail();
} catch (IllegalArgumentException expected) {
}
}
@Test public void setTransportsWithNull() throws Exception {
try {
client.setTransports(Arrays.asList("http/1.1", null));
fail();
} catch (IllegalArgumentException expected) {
}
}
/** Returns a gzipped copy of {@code bytes}. */
public byte[] gzip(byte[] bytes) throws IOException {
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();