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

Push state from HttpURLConnectionImpl to OkHttpClient

We aren't going to use HttpURLConnectionImpl for
requests with the new API.

This is safe because we always do a shallow copy of
the OkHttpClient before using it to create a
HttpURLConnectionImpl.
This commit is contained in:
jwilson
2013-07-07 21:41:07 -04:00
parent 76d3637d47
commit 39b3b59f6f
14 changed files with 285 additions and 273 deletions

View File

@@ -23,7 +23,6 @@ import com.squareup.okhttp.internal.Util;
import com.squareup.okhttp.internal.http.HttpEngine;
import com.squareup.okhttp.internal.http.HttpURLConnectionImpl;
import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;
import com.squareup.okhttp.internal.http.OkResponseCache;
import com.squareup.okhttp.internal.http.RawHeaders;
import com.squareup.okhttp.internal.http.ResponseHeaders;
import java.io.BufferedWriter;

View File

@@ -16,10 +16,10 @@
package com.squareup.okhttp;
import com.squareup.okhttp.internal.Util;
import com.squareup.okhttp.internal.http.Dispatcher;
import com.squareup.okhttp.internal.http.HttpAuthenticator;
import com.squareup.okhttp.internal.http.HttpURLConnectionImpl;
import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;
import com.squareup.okhttp.internal.http.OkResponseCache;
import com.squareup.okhttp.internal.http.OkResponseCacheAdapter;
import com.squareup.okhttp.internal.tls.OkHostnameVerifier;
import java.net.CookieHandler;
@@ -32,10 +32,7 @@ import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
@@ -46,9 +43,10 @@ public final class OkHttpClient implements URLStreamHandlerFactory {
private static final List<String> DEFAULT_TRANSPORTS
= Util.immutableList(Arrays.asList("spdy/3", "http/1.1"));
private final RouteDatabase routeDatabase;
private final Dispatcher dispatcher;
private Proxy proxy;
private List<String> transports;
private final Set<Route> failedRoutes;
private ProxySelector proxySelector;
private CookieHandler cookieHandler;
private ResponseCache responseCache;
@@ -59,14 +57,15 @@ public final class OkHttpClient implements URLStreamHandlerFactory {
private boolean followProtocolRedirects = true;
private int connectTimeout;
private int readTimeout;
private Dispatcher dispatcher = new Dispatcher();
public OkHttpClient() {
this.failedRoutes = Collections.synchronizedSet(new LinkedHashSet<Route>());
routeDatabase = new RouteDatabase();
dispatcher = new Dispatcher();
}
private OkHttpClient(OkHttpClient copyFrom) {
this.failedRoutes = copyFrom.failedRoutes; // Avoid allocating an unnecessary LinkedHashSet.
routeDatabase = copyFrom.routeDatabase;
dispatcher = copyFrom.dispatcher;
}
/**
@@ -109,7 +108,7 @@ public final class OkHttpClient implements URLStreamHandlerFactory {
if (millis > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Timeout too large.");
}
connectTimeout = (int) millis;
readTimeout = (int) millis;
}
/** Default read timeout (in milliseconds). */
@@ -181,7 +180,7 @@ public final class OkHttpClient implements URLStreamHandlerFactory {
return responseCache;
}
private OkResponseCache okResponseCache() {
public OkResponseCache getOkResponseCache() {
if (responseCache instanceof HttpResponseCache) {
return ((HttpResponseCache) responseCache).okResponseCache;
} else if (responseCache != null) {
@@ -269,6 +268,10 @@ public final class OkHttpClient implements URLStreamHandlerFactory {
return followProtocolRedirects;
}
public RouteDatabase getRoutesDatabase() {
return routeDatabase;
}
/**
* Configure the transports used by this client to communicate with remote
* servers. By default this client will prefer the most efficient transport
@@ -304,6 +307,9 @@ public final class OkHttpClient implements URLStreamHandlerFactory {
if (transports.contains(null)) {
throw new IllegalArgumentException("transports must not contain null");
}
if (transports.contains("")) {
throw new IllegalArgumentException("transports contains an empty string");
}
this.transports = transports;
return this;
}
@@ -331,15 +337,7 @@ public final class OkHttpClient implements URLStreamHandlerFactory {
}
public HttpURLConnection open(URL url) {
String protocol = url.getProtocol();
OkHttpClient copy = copyWithDefaults();
if (protocol.equals("http")) {
return new HttpURLConnectionImpl(url, copy, copy.okResponseCache(), copy.failedRoutes);
} else if (protocol.equals("https")) {
return new HttpsURLConnectionImpl(url, copy, copy.okResponseCache(), copy.failedRoutes);
} else {
throw new IllegalArgumentException("Unexpected protocol: " + protocol);
}
return open(url, proxy);
}
HttpURLConnection open(URL url, Proxy proxy) {
@@ -347,18 +345,9 @@ public final class OkHttpClient implements URLStreamHandlerFactory {
OkHttpClient copy = copyWithDefaults();
copy.proxy = proxy;
HttpURLConnection connection;
if (protocol.equals("http")) {
connection = new HttpURLConnectionImpl(url, copy, copy.okResponseCache(), copy.failedRoutes);
} else if (protocol.equals("https")) {
connection = new HttpsURLConnectionImpl(url, copy, copy.okResponseCache(), copy.failedRoutes);
} else {
throw new IllegalArgumentException("Unexpected protocol: " + protocol);
}
connection.setReadTimeout(readTimeout);
connection.setConnectTimeout(connectTimeout);
return connection;
if (protocol.equals("http")) return new HttpURLConnectionImpl(url, copy);
if (protocol.equals("https")) return new HttpsURLConnectionImpl(url, copy);
throw new IllegalArgumentException("Unexpected protocol: " + protocol);
}
/**

View File

@@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.okhttp.internal.http;
package com.squareup.okhttp;
import com.squareup.okhttp.ResponseSource;
import java.io.IOException;
import java.net.CacheRequest;
import java.net.CacheResponse;
@@ -29,9 +28,8 @@ import java.util.Map;
* An extended response cache API. Unlike {@link java.net.ResponseCache}, this
* interface supports conditional caching and statistics.
*
* <p>Along with the rest of the {@code internal} package, this is not a public
* API. Applications wishing to supply their own caches must use the more
* limited {@link java.net.ResponseCache} interface.
* <h3>Warning: Experimental OkHttp 2.0 API</h3>
* This class is in beta. APIs are subject to change!
*/
public interface OkResponseCache {
CacheResponse get(URI uri, String requestMethod, Map<String, List<String>> requestHeaders)

View File

@@ -59,13 +59,13 @@ public class Route {
return inetSocketAddress;
}
/** Returns true if this route uses modern tls. */
/** 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() {
/** Returns a copy of this route with flipped TLS mode. */
Route flipTlsMode() {
return new Route(address, proxy, inetSocketAddress, !modernTls);
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2013 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;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.net.ssl.SSLHandshakeException;
/**
* A blacklist of failed routes to avoid when creating a new connection to a
* target address. This is used so that OkHttp can learn from its mistakes: if
* there was a failure attempting to connect to a specific IP address, proxy
* server or TLS mode, that failure is remembered and alternate routes are
* preferred.
*/
public final class RouteDatabase {
private final Set<Route> failedRoutes = new LinkedHashSet<Route>();
/** Records a failure connecting to {@code failedRoute}. */
public synchronized void failed(Route failedRoute, IOException 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());
}
}
/** Records success connecting to {@code failedRoute}. */
public synchronized void connected(Route route) {
failedRoutes.remove(route);
}
/** Returns true if {@code route} has failed recently and should be avoided. */
public synchronized boolean shouldPostpone(Route route) {
return failedRoutes.contains(route);
}
public synchronized int failedRoutesCount() {
return failedRoutes.size();
}
}

View File

@@ -13,8 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.okhttp;
package com.squareup.okhttp.internal.http;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
@@ -26,7 +28,7 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
class Dispatcher {
public final class Dispatcher {
// TODO: thread pool size should be configurable; possibly configurable per host.
private final ThreadPoolExecutor executorService = new ThreadPoolExecutor(
8, 8, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
@@ -34,7 +36,7 @@ class Dispatcher {
public synchronized void enqueue(
HttpURLConnection connection, Request request, Response.Receiver responseReceiver) {
Job job = new Job(connection, request, responseReceiver);
Job job = new Job(this, connection, request, responseReceiver);
List<Job> jobsForTag = enqueuedJobs.get(request.tag());
if (jobsForTag == null) {
jobsForTag = new ArrayList<Job>(2);
@@ -52,75 +54,11 @@ class Dispatcher {
}
}
private synchronized void finished(Job job) {
synchronized void finished(Job job) {
List<Job> jobs = enqueuedJobs.get(job.request.tag());
if (jobs != null) jobs.remove(job);
}
public class Job implements Runnable {
private final HttpURLConnection connection;
private final Request request;
private final Response.Receiver responseReceiver;
public Job(HttpURLConnection connection, Request request, Response.Receiver responseReceiver) {
this.connection = connection;
this.request = request;
this.responseReceiver = responseReceiver;
}
@Override public void run() {
try {
sendRequest();
Response response = readResponse();
responseReceiver.onResponse(response);
} catch (IOException e) {
responseReceiver.onFailure(new Failure.Builder()
.request(request)
.exception(e)
.build());
} finally {
connection.disconnect();
finished(this);
}
}
private HttpURLConnection sendRequest()
throws IOException {
for (int i = 0; i < request.headerCount(); i++) {
connection.addRequestProperty(request.headerName(i), request.headerValue(i));
}
Request.Body body = request.body();
if (body != null) {
connection.setDoOutput(true);
long contentLength = body.contentLength();
if (contentLength == -1 || contentLength > Integer.MAX_VALUE) {
connection.setChunkedStreamingMode(0);
} else {
// Don't call setFixedLengthStreamingMode(long); that's only available on Java 1.7+.
connection.setFixedLengthStreamingMode((int) contentLength);
}
body.writeTo(connection.getOutputStream());
}
return connection;
}
private Response readResponse() throws IOException {
int responseCode = connection.getResponseCode();
Response.Builder responseBuilder = new Response.Builder(request, responseCode);
for (int i = 0; true; i++) {
String name = connection.getHeaderFieldKey(i);
if (name == null) break;
String value = connection.getHeaderField(i);
responseBuilder.addHeader(name, value);
}
responseBuilder.body(new RealResponseBody(connection, connection.getInputStream()));
// TODO: set redirectedBy
return responseBuilder.build();
}
}
static class RealResponseBody extends Response.Body {
private final HttpURLConnection connection;
private final InputStream in;

View File

@@ -19,6 +19,8 @@ package com.squareup.okhttp.internal.http;
import com.squareup.okhttp.Address;
import com.squareup.okhttp.Connection;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.OkResponseCache;
import com.squareup.okhttp.ResponseSource;
import com.squareup.okhttp.TunnelRequest;
import com.squareup.okhttp.internal.Dns;
@@ -87,6 +89,7 @@ public class HttpEngine {
public static final int HTTP_CONTINUE = 100;
protected final HttpURLConnectionImpl policy;
protected final OkHttpClient client;
protected final String method;
@@ -147,6 +150,7 @@ public class HttpEngine {
public HttpEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders,
Connection connection, RetryableOutputStream requestBodyOut) throws IOException {
this.policy = policy;
this.client = policy.client;
this.method = method;
this.connection = connection;
this.requestBodyOut = requestBodyOut;
@@ -176,8 +180,9 @@ public class HttpEngine {
prepareRawRequestHeaders();
initResponseSource();
if (policy.responseCache != null) {
policy.responseCache.trackResponse(responseSource);
OkResponseCache responseCache = client.getOkResponseCache();
if (responseCache != null) {
responseCache.trackResponse(responseSource);
}
// The raw response source may require the network, but the request
@@ -197,7 +202,7 @@ public class HttpEngine {
if (responseSource.requiresConnection()) {
sendSocketRequest();
} else if (connection != null) {
policy.connectionPool.recycle(connection);
client.getConnectionPool().recycle(connection);
connection = null;
}
}
@@ -208,15 +213,14 @@ public class HttpEngine {
*/
private void initResponseSource() throws IOException {
responseSource = ResponseSource.NETWORK;
if (!policy.getUseCaches() || policy.responseCache == null) {
return;
}
if (!policy.getUseCaches()) return;
CacheResponse candidate =
policy.responseCache.get(uri, method, requestHeaders.getHeaders().toMultimap(false));
if (candidate == null) {
return;
}
OkResponseCache responseCache = client.getOkResponseCache();
if (responseCache == null) return;
CacheResponse candidate = responseCache.get(
uri, method, requestHeaders.getHeaders().toMultimap(false));
if (candidate == null) return;
Map<String, List<String>> responseHeadersMap = candidate.getHeaders();
cachedResponseBody = candidate.getBody();
@@ -274,22 +278,22 @@ public class HttpEngine {
SSLSocketFactory sslSocketFactory = null;
HostnameVerifier hostnameVerifier = null;
if (uri.getScheme().equalsIgnoreCase("https")) {
sslSocketFactory = policy.sslSocketFactory;
hostnameVerifier = policy.hostnameVerifier;
sslSocketFactory = client.getSslSocketFactory();
hostnameVerifier = client.getHostnameVerifier();
}
Address address = new Address(uriHost, getEffectivePort(uri), sslSocketFactory,
hostnameVerifier, policy.authenticator, policy.requestedProxy, policy.getTransports());
routeSelector = new RouteSelector(address, uri, policy.proxySelector, policy.connectionPool,
Dns.DEFAULT, policy.getFailedRoutes());
hostnameVerifier, client.getAuthenticator(), client.getProxy(), client.getTransports());
routeSelector = new RouteSelector(address, uri, client.getProxySelector(),
client.getConnectionPool(), Dns.DEFAULT, client.getRoutesDatabase());
}
connection = routeSelector.next();
if (!connection.isConnected()) {
connection.connect(policy.getConnectTimeout(), policy.getReadTimeout(), getTunnelConfig());
policy.connectionPool.maybeShare(connection);
policy.getFailedRoutes().remove(connection.getRoute());
connection.connect(client.getConnectTimeout(), client.getReadTimeout(), getTunnelConfig());
client.getConnectionPool().maybeShare(connection);
client.getRoutesDatabase().connected(connection.getRoute());
}
connected(connection);
if (connection.getRoute().getProxy() != policy.requestedProxy) {
if (connection.getRoute().getProxy() != client.getProxy()) {
// Update the request line if the proxy changed; it may need a host name.
requestHeaders.getHeaders().setRequestLine(getRequestLine());
}
@@ -387,20 +391,20 @@ public class HttpEngine {
private void maybeCache() throws IOException {
// Are we caching at all?
if (!policy.getUseCaches() || policy.responseCache == null) {
return;
}
if (!policy.getUseCaches()) return;
OkResponseCache responseCache = client.getOkResponseCache();
if (responseCache == null) return;
HttpURLConnection connectionToCache = policy.getHttpConnectionToCache();
// Should we cache this response for this request?
if (!responseHeaders.isCacheable(requestHeaders)) {
policy.responseCache.maybeRemove(connectionToCache.getRequestMethod(), uri);
responseCache.maybeRemove(connectionToCache.getRequestMethod(), uri);
return;
}
// Offer this request to the cache.
cacheRequest = policy.responseCache.put(uri, connectionToCache);
cacheRequest = responseCache.put(uri, connectionToCache);
}
/**
@@ -412,7 +416,7 @@ public class HttpEngine {
public final void automaticallyReleaseConnectionToPool() {
automaticallyReleaseConnectionToPool = true;
if (connection != null && connectionReleased) {
policy.connectionPool.recycle(connection);
client.getConnectionPool().recycle(connection);
connection = null;
}
}
@@ -436,7 +440,7 @@ public class HttpEngine {
Util.closeQuietly(connection);
connection = null;
} else if (automaticallyReleaseConnectionToPool) {
policy.connectionPool.recycle(connection);
client.getConnectionPool().recycle(connection);
connection = null;
}
}
@@ -524,7 +528,7 @@ public class HttpEngine {
requestHeaders.setIfModifiedSince(new Date(ifModifiedSince));
}
CookieHandler cookieHandler = policy.cookieHandler;
CookieHandler cookieHandler = client.getCookieHandler();
if (cookieHandler != null) {
requestHeaders.addCookies(
cookieHandler.get(uri, requestHeaders.getHeaders().toMultimap(false)));
@@ -639,8 +643,9 @@ public class HttpEngine {
release(false);
ResponseHeaders combinedHeaders = cachedResponseHeaders.combine(responseHeaders);
setResponse(combinedHeaders, cachedResponseBody);
policy.responseCache.trackConditionalCacheHit();
policy.responseCache.update(cacheResponse, policy.getHttpConnectionToCache());
OkResponseCache responseCache = client.getOkResponseCache();
responseCache.trackConditionalCacheHit();
responseCache.update(cacheResponse, policy.getHttpConnectionToCache());
return;
} else {
Util.closeQuietly(cachedResponseBody);
@@ -659,7 +664,7 @@ public class HttpEngine {
}
public void receiveHeaders(RawHeaders headers) throws IOException {
CookieHandler cookieHandler = policy.cookieHandler;
CookieHandler cookieHandler = client.getCookieHandler();
if (cookieHandler != null) {
cookieHandler.put(uri, headers.toMultimap(true));
}

View File

@@ -18,10 +18,7 @@
package com.squareup.okhttp.internal.http;
import com.squareup.okhttp.Connection;
import com.squareup.okhttp.ConnectionPool;
import com.squareup.okhttp.OkAuthenticator;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Route;
import com.squareup.okhttp.internal.AbstractOutputStream;
import com.squareup.okhttp.internal.FaultRecoveringOutputStream;
import com.squareup.okhttp.internal.Platform;
@@ -30,13 +27,11 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.CookieHandler;
import java.net.HttpRetryException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.ProtocolException;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketPermission;
import java.net.URL;
import java.security.Permission;
@@ -44,10 +39,8 @@ import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.net.ssl.HostnameVerifier;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocketFactory;
import static com.squareup.okhttp.internal.Util.getEffectivePort;
@@ -83,52 +76,17 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
*/
private static final int MAX_REPLAY_BUFFER_LENGTH = 8192;
private final boolean followProtocolRedirects;
/** The proxy requested by the client, or null for a proxy to be selected automatically. */
final Proxy requestedProxy;
final ProxySelector proxySelector;
final CookieHandler cookieHandler;
final OkResponseCache responseCache;
final ConnectionPool connectionPool;
/* SSL configuration; necessary for HTTP requests that get redirected to HTTPS. */
SSLSocketFactory sslSocketFactory;
HostnameVerifier hostnameVerifier;
private List<String> transports;
OkAuthenticator authenticator;
final Set<Route> failedRoutes;
final OkHttpClient client;
private final RawHeaders rawRequestHeaders = new RawHeaders();
private int redirectionCount;
private FaultRecoveringOutputStream faultRecoveringRequestBody;
protected IOException httpEngineFailure;
protected HttpEngine httpEngine;
public HttpURLConnectionImpl(URL url, OkHttpClient client, OkResponseCache responseCache,
Set<Route> failedRoutes) {
public HttpURLConnectionImpl(URL url, OkHttpClient client) {
super(url);
this.followProtocolRedirects = client.getFollowProtocolRedirects();
this.failedRoutes = failedRoutes;
this.requestedProxy = client.getProxy();
this.proxySelector = client.getProxySelector();
this.cookieHandler = client.getCookieHandler();
this.connectionPool = client.getConnectionPool();
this.sslSocketFactory = client.getSslSocketFactory();
this.hostnameVerifier = client.getHostnameVerifier();
this.transports = client.getTransports();
this.authenticator = client.getAuthenticator();
this.responseCache = responseCache;
}
Set<Route> getFailedRoutes() {
return failedRoutes;
}
List<String> getTransports() {
return transports;
this.client = client;
}
@Override public final void connect() throws IOException {
@@ -274,7 +232,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
String hostName = getURL().getHost();
int hostPort = Util.getEffectivePort(getURL());
if (usingProxy()) {
InetSocketAddress proxyAddress = (InetSocketAddress) requestedProxy.address();
InetSocketAddress proxyAddress = (InetSocketAddress) client.getProxy().address();
hostName = proxyAddress.getHostName();
hostPort = proxyAddress.getPort();
}
@@ -288,6 +246,22 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
return rawRequestHeaders.get(field);
}
@Override public void setConnectTimeout(int timeoutMillis) {
client.setConnectTimeout(timeoutMillis, TimeUnit.MILLISECONDS);
}
@Override public int getConnectTimeout() {
return client.getConnectTimeout();
}
@Override public void setReadTimeout(int timeoutMillis) {
client.setReadTimeout(timeoutMillis, TimeUnit.MILLISECONDS);
}
@Override public int getReadTimeout() {
return client.getReadTimeout();
}
private void initHttpEngine() throws IOException {
if (httpEngineFailure != null) {
throw httpEngineFailure;
@@ -468,7 +442,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
private Retry processResponseHeaders() throws IOException {
Proxy selectedProxy = httpEngine.connection != null
? httpEngine.connection.getRoute().getProxy()
: requestedProxy;
: client.getProxy();
final int responseCode = getResponseCode();
switch (responseCode) {
case HTTP_PROXY_AUTH:
@@ -477,7 +451,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
}
// fall-through
case HTTP_UNAUTHORIZED:
boolean credentialsFound = HttpAuthenticator.processAuthHeader(authenticator,
boolean credentialsFound = HttpAuthenticator.processAuthHeader(client.getAuthenticator(),
getResponseCode(), httpEngine.getResponseHeaders().getHeaders(), rawRequestHeaders,
selectedProxy, url);
return credentialsFound ? Retry.SAME_CONNECTION : Retry.NONE;
@@ -508,7 +482,7 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
return Retry.NONE; // Don't follow redirects to unsupported protocols.
}
boolean sameProtocol = previousUrl.getProtocol().equals(url.getProtocol());
if (!sameProtocol && !followProtocolRedirects) {
if (!sameProtocol && !client.getFollowProtocolRedirects()) {
return Retry.NONE; // This client doesn't follow redirects across protocols.
}
boolean sameHost = previousUrl.getHost().equals(url.getHost());
@@ -535,7 +509,8 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
}
@Override public final boolean usingProxy() {
return (requestedProxy != null && requestedProxy.type() != Proxy.Type.DIRECT);
Proxy proxy = client.getProxy();
return proxy != null && proxy.type() != Proxy.Type.DIRECT;
}
@Override public String getResponseMessage() throws IOException {
@@ -599,37 +574,13 @@ public class HttpURLConnectionImpl extends HttpURLConnection {
* When append == false, we require that the transport list contains "http/1.1".
*/
private void setTransports(String transportsString, boolean append) {
String[] transports = transportsString.split(",", -1);
ArrayList<String> transportsList = new ArrayList<String>();
if (!append) {
// If we're not appending to the list, we need to make sure
// the list contains "http/1.1". We do this in a separate loop
// to avoid modifying any state before we validate the input.
boolean containsHttp = false;
for (String transport : transports) {
if ("http/1.1".equals(transport)) {
containsHttp = true;
break;
}
}
if (!containsHttp) {
throw new IllegalArgumentException("Transport list doesn't contain http/1.1");
}
} else {
transportsList.addAll(this.transports);
List<String> transportsList = new ArrayList<String>();
if (append) {
transportsList.addAll(client.getTransports());
}
for (String transport : transports) {
if (transport.length() == 0) {
throw new IllegalArgumentException("Transport list contains an empty transport");
}
if (!transportsList.contains(transport)) {
transportsList.add(transport);
}
for (String transport : transportsString.split(",", -1)) {
transportsList.add(transport);
}
this.transports = Util.immutableList(transportsList);
client.setTransports(transportsList);
}
}

View File

@@ -18,7 +18,6 @@ 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;
@@ -33,7 +32,6 @@ 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;
@@ -47,10 +45,9 @@ public final class HttpsURLConnectionImpl extends HttpsURLConnection {
/** HttpUrlConnectionDelegate allows reuse of HttpURLConnectionImpl. */
private final HttpUrlConnectionDelegate delegate;
public HttpsURLConnectionImpl(URL url, OkHttpClient client, OkResponseCache responseCache,
Set<Route> failedRoutes) {
public HttpsURLConnectionImpl(URL url, OkHttpClient client) {
super(url);
delegate = new HttpUrlConnectionDelegate(url, client, responseCache, failedRoutes);
delegate = new HttpUrlConnectionDelegate(url, client);
}
@Override public String getCipherSuite() {
@@ -386,25 +383,24 @@ public final class HttpsURLConnectionImpl extends HttpsURLConnection {
}
@Override public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
delegate.hostnameVerifier = hostnameVerifier;
delegate.client.setHostnameVerifier(hostnameVerifier);
}
@Override public HostnameVerifier getHostnameVerifier() {
return delegate.hostnameVerifier;
return delegate.client.getHostnameVerifier();
}
@Override public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) {
delegate.sslSocketFactory = sslSocketFactory;
delegate.client.setSslSocketFactory(sslSocketFactory);
}
@Override public SSLSocketFactory getSSLSocketFactory() {
return delegate.sslSocketFactory;
return delegate.client.getSslSocketFactory();
}
private final class HttpUrlConnectionDelegate extends HttpURLConnectionImpl {
private HttpUrlConnectionDelegate(URL url, OkHttpClient client, OkResponseCache responseCache,
Set<Route> failedRoutes) {
super(url, client, responseCache, failedRoutes);
private HttpUrlConnectionDelegate(URL url, OkHttpClient client) {
super(url, client);
}
@Override protected HttpURLConnection getHttpConnectionToCache() {

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2013 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.http;
import com.squareup.okhttp.Failure;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import java.io.IOException;
import java.net.HttpURLConnection;
public final class Job implements Runnable {
final HttpURLConnection connection;
final Request request;
final Response.Receiver responseReceiver;
final Dispatcher dispatcher;
public Job(Dispatcher dispatcher, HttpURLConnection connection, Request request,
Response.Receiver responseReceiver) {
this.dispatcher = dispatcher;
this.connection = connection;
this.request = request;
this.responseReceiver = responseReceiver;
}
@Override public void run() {
try {
sendRequest();
Response response = readResponse();
responseReceiver.onResponse(response);
} catch (IOException e) {
responseReceiver.onFailure(new Failure.Builder()
.request(request)
.exception(e)
.build());
} finally {
connection.disconnect();
dispatcher.finished(this);
}
}
private HttpURLConnection sendRequest() throws IOException {
for (int i = 0; i < request.headerCount(); i++) {
connection.addRequestProperty(request.headerName(i), request.headerValue(i));
}
Request.Body body = request.body();
if (body != null) {
connection.setDoOutput(true);
long contentLength = body.contentLength();
if (contentLength == -1 || contentLength > Integer.MAX_VALUE) {
connection.setChunkedStreamingMode(0);
} else {
// Don't call setFixedLengthStreamingMode(long); that's only available on Java 1.7+.
connection.setFixedLengthStreamingMode((int) contentLength);
}
body.writeTo(connection.getOutputStream());
}
return connection;
}
private Response readResponse() throws IOException {
int responseCode = connection.getResponseCode();
Response.Builder responseBuilder = new Response.Builder(request, responseCode);
for (int i = 0; true; i++) {
String name = connection.getHeaderFieldKey(i);
if (name == null) break;
String value = connection.getHeaderField(i);
responseBuilder.addHeader(name, value);
}
responseBuilder.body(new Dispatcher.RealResponseBody(connection, connection.getInputStream()));
// TODO: set redirectedBy
return responseBuilder.build();
}
}

View File

@@ -15,6 +15,7 @@
*/
package com.squareup.okhttp.internal.http;
import com.squareup.okhttp.OkResponseCache;
import com.squareup.okhttp.ResponseSource;
import java.io.IOException;
import java.net.CacheRequest;

View File

@@ -19,6 +19,7 @@ import com.squareup.okhttp.Address;
import com.squareup.okhttp.Connection;
import com.squareup.okhttp.ConnectionPool;
import com.squareup.okhttp.Route;
import com.squareup.okhttp.RouteDatabase;
import com.squareup.okhttp.internal.Dns;
import java.io.IOException;
import java.net.InetAddress;
@@ -32,8 +33,6 @@ 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;
@@ -55,7 +54,7 @@ public final class RouteSelector {
private final ProxySelector proxySelector;
private final ConnectionPool pool;
private final Dns dns;
private final Set<Route> failedRoutes;
private final RouteDatabase routeDatabase;
/* The most recently attempted route. */
private Proxy lastProxy;
@@ -78,13 +77,13 @@ public final class RouteSelector {
private final List<Route> postponedRoutes;
public RouteSelector(Address address, URI uri, ProxySelector proxySelector, ConnectionPool pool,
Dns dns, Set<Route> failedRoutes) {
Dns dns, RouteDatabase routeDatabase) {
this.address = address;
this.uri = uri;
this.proxySelector = proxySelector;
this.pool = pool;
this.dns = dns;
this.failedRoutes = failedRoutes;
this.routeDatabase = routeDatabase;
this.postponedRoutes = new LinkedList<Route>();
resetNextProxy(uri, address.getProxy());
@@ -128,7 +127,7 @@ public final class RouteSelector {
boolean modernTls = nextTlsMode() == TLS_MODE_MODERN;
Route route = new Route(address, lastProxy, lastInetSocketAddress, modernTls);
if (failedRoutes.contains(route)) {
if (routeDatabase.shouldPostpone(route)) {
postponedRoutes.add(route);
// We will only recurse in order to skip previously failed routes. They will be
// tried last.
@@ -149,12 +148,7 @@ public final class RouteSelector {
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());
}
routeDatabase.failed(failedRoute, failure);
}
/** Resets {@link #nextProxy} to the first option. */

View File

@@ -55,7 +55,7 @@ public final class SpdyTransport implements Transport {
boolean hasResponseBody = true;
stream = spdyConnection.newStream(requestHeaders.toNameValueBlock(), hasRequestBody,
hasResponseBody);
stream.setReadTimeout(httpEngine.policy.getReadTimeout());
stream.setReadTimeout(httpEngine.client.getReadTimeout());
}
@Override public void writeRequestBody(RetryableOutputStream requestBody) throws IOException {

View File

@@ -19,7 +19,7 @@ import com.squareup.okhttp.Address;
import com.squareup.okhttp.Connection;
import com.squareup.okhttp.ConnectionPool;
import com.squareup.okhttp.OkAuthenticator;
import com.squareup.okhttp.Route;
import com.squareup.okhttp.RouteDatabase;
import com.squareup.okhttp.internal.Dns;
import com.squareup.okhttp.internal.SslContextBuilder;
import java.io.IOException;
@@ -32,11 +32,8 @@ 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;
@@ -87,7 +84,7 @@ public final class RouteSelectorTest {
@Test public void singleRoute() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, authenticator, null, transports);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
new RouteDatabase());
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 1);
@@ -105,15 +102,14 @@ public final class RouteSelectorTest {
@Test public void singleRouteReturnsFailedRoute() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, authenticator, null, transports);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
new RouteDatabase());
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);
RouteDatabase routeDatabase = new RouteDatabase();
routeDatabase.failed(connection.getRoute(), new IOException());
routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, routeDatabase);
assertConnection(routeSelector.next(), address, NO_PROXY, dns.inetAddresses[0], uriPort, false);
assertFalse(routeSelector.hasNext());
try {
@@ -126,7 +122,7 @@ public final class RouteSelectorTest {
@Test public void explicitProxyTriesThatProxiesAddressesOnly() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, authenticator, proxyA, transports);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
new RouteDatabase());
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 2);
@@ -144,7 +140,7 @@ public final class RouteSelectorTest {
Address address = new Address(uriHost, uriPort, null, null, authenticator, NO_PROXY,
transports);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
new RouteDatabase());
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 2);
@@ -161,7 +157,7 @@ public final class RouteSelectorTest {
proxySelector.proxies = null;
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
new RouteDatabase());
proxySelector.assertRequests(uri);
assertTrue(routeSelector.hasNext());
@@ -175,7 +171,7 @@ public final class RouteSelectorTest {
@Test public void proxySelectorReturnsNoProxies() throws Exception {
Address address = new Address(uriHost, uriPort, null, null, authenticator, null, transports);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
new RouteDatabase());
assertTrue(routeSelector.hasNext());
dns.inetAddresses = makeFakeAddresses(255, 2);
@@ -193,7 +189,7 @@ public final class RouteSelectorTest {
proxySelector.proxies.add(proxyA);
proxySelector.proxies.add(proxyB);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
new RouteDatabase());
proxySelector.assertRequests(uri);
// First try the IP addresses of the first proxy, in sequence.
@@ -226,7 +222,7 @@ public final class RouteSelectorTest {
proxySelector.proxies.add(NO_PROXY);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
new RouteDatabase());
proxySelector.assertRequests(uri);
// Only the origin server will be attempted.
@@ -245,7 +241,7 @@ public final class RouteSelectorTest {
proxySelector.proxies.add(proxyB);
proxySelector.proxies.add(proxyA);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
new RouteDatabase());
proxySelector.assertRequests(uri);
assertTrue(routeSelector.hasNext());
@@ -280,27 +276,27 @@ public final class RouteSelectorTest {
@Test public void nonSslErrorAddsAllTlsModesToFailedRoute() throws Exception {
Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator,
Proxy.NO_PROXY, transports);
Set<Route> failedRoutes = new LinkedHashSet<Route>();
RouteDatabase routeDatabase = new RouteDatabase();
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
failedRoutes);
routeDatabase);
dns.inetAddresses = makeFakeAddresses(255, 1);
Connection connection = routeSelector.next();
routeSelector.connectFailed(connection, new IOException("Non SSL exception"));
assertTrue(failedRoutes.size() == 2);
assertTrue(routeDatabase.failedRoutesCount() == 2);
}
@Test public void sslErrorAddsOnlyFailedTlsModeToFailedRoute() throws Exception {
Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator,
Proxy.NO_PROXY, transports);
Set<Route> failedRoutes = new LinkedHashSet<Route>();
RouteDatabase routeDatabase = new RouteDatabase();
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
failedRoutes);
routeDatabase);
dns.inetAddresses = makeFakeAddresses(255, 1);
Connection connection = routeSelector.next();
routeSelector.connectFailed(connection, new SSLHandshakeException("SSL exception"));
assertTrue(failedRoutes.size() == 1);
assertTrue(routeDatabase.failedRoutesCount() == 1);
}
@Test public void multipleProxiesMultipleInetAddressesMultipleTlsModes() throws Exception {
@@ -309,7 +305,7 @@ public final class RouteSelectorTest {
proxySelector.proxies.add(proxyA);
proxySelector.proxies.add(proxyB);
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
Collections.EMPTY_SET);
new RouteDatabase());
// Proxy A
dns.inetAddresses = makeFakeAddresses(255, 2);
@@ -346,9 +342,9 @@ public final class RouteSelectorTest {
Address address = new Address(uriHost, uriPort, socketFactory, hostnameVerifier, authenticator,
Proxy.NO_PROXY, transports);
Set<Route> failedRoutes = new LinkedHashSet<Route>(1);
RouteDatabase routeDatabase = new RouteDatabase();
RouteSelector routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns,
failedRoutes);
routeDatabase);
dns.inetAddresses = makeFakeAddresses(255, 1);
// Extract the regular sequence of routes from selector.
@@ -360,9 +356,9 @@ public final class RouteSelectorTest {
// 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());
routeDatabase.failed(regularRoutes.get(0).getRoute(), new SSLHandshakeException("none"));
// Reset selector
routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, failedRoutes);
routeSelector = new RouteSelector(address, uri, proxySelector, pool, dns, routeDatabase);
List<Connection> routesWithFailedRoute = new ArrayList<Connection>();
while (routeSelector.hasNext()) {