transports;
private ProxySelector proxySelector;
private CookieHandler cookieHandler;
- private ResponseCache responseCache;
+ private OkResponseCache responseCache;
private SSLSocketFactory sslSocketFactory;
private HostnameVerifier hostnameVerifier;
private OkAuthenticator authenticator;
@@ -169,24 +168,40 @@ public CookieHandler getCookieHandler() {
*
* If unset, the {@link ResponseCache#getDefault() system-wide default}
* response cache will be used.
+ *
+ * @deprecated OkHttp 2 dropped support for java.net.ResponseCache. That API
+ * is broken for many reasons: URI instead of URL, no conditional updates,
+ * no invalidation, and no mechanism for tracking hit rates. Use
+ * {@link #setOkResponseCache} instead.
*/
+ @Deprecated
public OkHttpClient setResponseCache(ResponseCache responseCache) {
- this.responseCache = responseCache;
- return this;
+ if (responseCache instanceof OkResponseCache) {
+ return setOkResponseCache((OkResponseCache) responseCache);
+ }
+ throw new UnsupportedOperationException("OkHttp 2 dropped support for java.net.ResponseCache. "
+ + "Use setOkResponseCache() instead.");
}
+ /**
+ * @deprecated OkHttp 2 dropped support for java.net.ResponseCache. That API
+ * is broken for many reasons: URI instead of URL, no conditional updates,
+ * no invalidation, and no mechanism for tracking hit rates. Use
+ * {@link #setOkResponseCache} instead.
+ */
+ @Deprecated
public ResponseCache getResponseCache() {
- return responseCache;
+ throw new UnsupportedOperationException("OkHttp 2 dropped support for java.net.ResponseCache. "
+ + "Use setOkResponseCache() instead.");
+ }
+
+ public OkHttpClient setOkResponseCache(OkResponseCache responseCache) {
+ this.responseCache = responseCache;
+ return this;
}
public OkResponseCache getOkResponseCache() {
- if (responseCache instanceof HttpResponseCache) {
- return ((HttpResponseCache) responseCache).okResponseCache;
- } else if (responseCache != null) {
- return new OkResponseCacheAdapter(responseCache);
- } else {
- return null;
- }
+ return responseCache;
}
/**
@@ -319,19 +334,24 @@ public List getTransports() {
/**
* Schedules {@code request} to be executed.
+ *
+ * Warning: Experimental OkHttp 2.0 API
+ * This method is in beta. APIs are subject to change!
*/
- /* OkHttp 2.0: public */ void enqueue(Request request, Response.Receiver responseReceiver) {
- // Create the HttpURLConnection immediately so the enqueued job gets the current settings of
- // this client. Otherwise changes to this client (socket factory, redirect policy, etc.) may
- // incorrectly be reflected in the request when it is dispatched later.
+ public void enqueue(Request request, Response.Receiver responseReceiver) {
+ // Copy this client. Otherwise changes (socket factory, redirect policy,
+ // etc.) may incorrectly be reflected in the request when it is dispatched.
dispatcher.enqueue(copyWithDefaults(), request, responseReceiver);
}
/**
* Cancels all scheduled tasks tagged with {@code tag}. Requests that are already
* in flight might not be canceled.
+ *
+ * Warning: Experimental OkHttp 2.0 API
+ * This method is in beta. APIs are subject to change!
*/
- /* OkHttp 2.0: public */ void cancel(Object tag) {
+ public void cancel(Object tag) {
dispatcher.cancel(tag);
}
@@ -358,7 +378,9 @@ private OkHttpClient copyWithDefaults() {
result.proxy = proxy;
result.proxySelector = proxySelector != null ? proxySelector : ProxySelector.getDefault();
result.cookieHandler = cookieHandler != null ? cookieHandler : CookieHandler.getDefault();
- result.responseCache = responseCache != null ? responseCache : ResponseCache.getDefault();
+ result.responseCache = responseCache != null
+ ? responseCache
+ : toOkResponseCacheOrNull(ResponseCache.getDefault());
result.sslSocketFactory = sslSocketFactory != null
? sslSocketFactory
: HttpsURLConnection.getDefaultSSLSocketFactory();
@@ -376,6 +398,10 @@ private OkHttpClient copyWithDefaults() {
return result;
}
+ private OkResponseCache toOkResponseCacheOrNull(ResponseCache cache) {
+ return cache instanceof OkResponseCache ? ((OkResponseCache) cache) : null;
+ }
+
/**
* Creates a URLStreamHandler as a {@link URL#setURLStreamHandlerFactory}.
*
diff --git a/okhttp/src/main/java/com/squareup/okhttp/OkResponseCache.java b/okhttp/src/main/java/com/squareup/okhttp/OkResponseCache.java
index ffe6f54b1045..1ddeb9a53ed6 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/OkResponseCache.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/OkResponseCache.java
@@ -17,12 +17,6 @@
import java.io.IOException;
import java.net.CacheRequest;
-import java.net.CacheResponse;
-import java.net.HttpURLConnection;
-import java.net.URI;
-import java.net.URLConnection;
-import java.util.List;
-import java.util.Map;
/**
* An extended response cache API. Unlike {@link java.net.ResponseCache}, this
@@ -32,21 +26,25 @@
* This class is in beta. APIs are subject to change!
*/
public interface OkResponseCache {
- CacheResponse get(URI uri, String requestMethod, Map> requestHeaders)
- throws IOException;
+ Response get(Request request) throws IOException;
- CacheRequest put(URI uri, URLConnection urlConnection) throws IOException;
+ CacheRequest put(Response response) throws IOException;
- /** Remove any cache entries for the supplied {@code uri} if the request method invalidates. */
- void maybeRemove(String requestMethod, URI uri) throws IOException;
+ /**
+ * Remove any cache entries for the supplied {@code uri}. Returns true if the
+ * supplied {@code requestMethod} potentially invalidates an entry in the
+ * cache.
+ */
+ // TODO: this shouldn't return a boolean.
+ boolean maybeRemove(Request request) throws IOException;
/**
* Handles a conditional request hit by updating the stored cache response
- * with the headers from {@code httpConnection}. The cached response body is
- * not updated. If the stored response has changed since {@code
- * conditionalCacheHit} was returned, this does nothing.
+ * with the headers from {@code network}. The cached response body is not
+ * updated. If the stored response has changed since {@code cached} was
+ * returned, this does nothing.
*/
- void update(CacheResponse conditionalCacheHit, HttpURLConnection connection) throws IOException;
+ void update(Response cached, Response network) throws IOException;
/** Track an conditional GET that was satisfied by this cache. */
void trackConditionalCacheHit();
diff --git a/okhttp/src/main/java/com/squareup/okhttp/Request.java b/okhttp/src/main/java/com/squareup/okhttp/Request.java
index 124cd973a2c3..449fff9d9c58 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/Request.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/Request.java
@@ -35,7 +35,7 @@
* Warning: Experimental OkHttp 2.0 API
* This class is in beta. APIs are subject to change!
*/
-/* OkHttp 2.0: public */ final class Request {
+public final class Request {
private final URL url;
private final String method;
private final RawHeaders headers;
@@ -237,7 +237,8 @@ public Builder addHeader(String name, String value) {
return this;
}
- Builder rawHeaders(RawHeaders rawHeaders) {
+ // TODO: this shouldn't be public.
+ public Builder rawHeaders(RawHeaders rawHeaders) {
headers = new RawHeaders(rawHeaders);
return this;
}
diff --git a/okhttp/src/main/java/com/squareup/okhttp/Response.java b/okhttp/src/main/java/com/squareup/okhttp/Response.java
index 2ee6b9423dbc..2d24e7122ae5 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/Response.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/Response.java
@@ -18,6 +18,7 @@
import com.squareup.okhttp.internal.Util;
import com.squareup.okhttp.internal.http.RawHeaders;
import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -36,7 +37,7 @@
* Warning: Experimental OkHttp 2.0 API
* This class is in beta. APIs are subject to change!
*/
-/* OkHttp 2.0: public */ final class Response {
+public final class Response {
private final Request request;
private final int code;
private final Handshake handshake;
@@ -106,7 +107,8 @@ public String headerName(int index) {
return headers.getFieldName(index);
}
- RawHeaders rawHeaders() {
+ // TODO: this shouldn't be public.
+ public RawHeaders rawHeaders() {
return new RawHeaders(headers);
}
@@ -128,7 +130,7 @@ public Response redirectedBy() {
return redirectedBy;
}
- public abstract static class Body {
+ public abstract static class Body implements Closeable {
/** Multiple calls to {@link #charStream()} must return the same instance. */
private Reader reader;
@@ -154,7 +156,7 @@ public abstract static class Body {
*/
public abstract long contentLength();
- public abstract InputStream byteStream() throws IOException;
+ public abstract InputStream byteStream();
public final byte[] bytes() throws IOException {
long contentLength = contentLength();
@@ -181,7 +183,7 @@ public final byte[] bytes() throws IOException {
* of the Content-Type header. If that header is either absent or lacks a
* charset, this will attempt to decode the response body as UTF-8.
*/
- public final Reader charStream() throws IOException {
+ public final Reader charStream() {
if (reader == null) {
reader = new InputStreamReader(byteStream(), charset());
}
@@ -201,6 +203,10 @@ private Charset charset() {
MediaType contentType = contentType();
return contentType != null ? contentType.charset(UTF_8) : UTF_8;
}
+
+ @Override public void close() throws IOException {
+ byteStream().close();
+ }
}
public interface Receiver {
@@ -282,7 +288,8 @@ public Builder addHeader(String name, String value) {
return this;
}
- Builder rawHeaders(RawHeaders rawHeaders) {
+ // TODO: this shouldn't be public.
+ public Builder rawHeaders(RawHeaders rawHeaders) {
headers = new RawHeaders(rawHeaders);
return this;
}
diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
index a5d39b30a4cf..612693644a08 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/AbstractHttpInputStream.java
@@ -46,7 +46,7 @@ abstract class AbstractHttpInputStream extends InputStream {
OutputStream cacheBody = cacheRequest != null ? cacheRequest.getBody() : null;
- // some apps return a null body; for compatibility we treat that like a null cache request
+ // Some apps return a null body; for compatibility we treat that like a null cache request.
if (cacheBody == null) {
cacheRequest = null;
}
diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java
index 2bea85337a5f..568b1aaef9d9 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java
@@ -20,37 +20,33 @@
import com.squareup.okhttp.Address;
import com.squareup.okhttp.Connection;
import com.squareup.okhttp.Handshake;
+import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.OkResponseCache;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseSource;
import com.squareup.okhttp.TunnelRequest;
import com.squareup.okhttp.internal.Dns;
import com.squareup.okhttp.internal.Platform;
import com.squareup.okhttp.internal.Util;
-import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.CacheRequest;
-import java.net.CacheResponse;
import java.net.CookieHandler;
-import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
-import java.util.Collections;
import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
import java.util.zip.GZIPInputStream;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
-import static com.squareup.okhttp.internal.Util.EMPTY_BYTE_ARRAY;
+import static com.squareup.okhttp.internal.Util.EMPTY_INPUT_STREAM;
import static com.squareup.okhttp.internal.Util.getDefaultPort;
import static com.squareup.okhttp.internal.Util.getEffectivePort;
@@ -82,16 +78,21 @@
* implementation type of its HttpEngine.
*/
public class HttpEngine {
- private static final CacheResponse GATEWAY_TIMEOUT_RESPONSE = new CacheResponse() {
- @Override public Map> getHeaders() throws IOException {
- Map> result = new HashMap>();
- result.put(null, Collections.singletonList("HTTP/1.1 504 Gateway Timeout"));
- return result;
+ private static final Response.Body EMPTY_BODY = new Response.Body() {
+ @Override public boolean ready() throws IOException {
+ return true;
+ }
+ @Override public MediaType contentType() {
+ return null;
+ }
+ @Override public long contentLength() {
+ return 0;
}
- @Override public InputStream getBody() throws IOException {
- return new ByteArrayInputStream(EMPTY_BYTE_ARRAY);
+ @Override public InputStream byteStream() {
+ return EMPTY_INPUT_STREAM;
}
};
+
public static final int HTTP_CONTINUE = 100;
protected final Policy policy;
@@ -111,15 +112,9 @@ public class HttpEngine {
private InputStream responseTransferIn;
private InputStream responseBodyIn;
- private CacheResponse cacheResponse;
- private CacheRequest cacheRequest;
-
/** The time when the request headers were written, or -1 if they haven't been written yet. */
long sentRequestMillis = -1;
- /** Whether the connection has been established. */
- boolean connected;
-
/**
* True if this client added an "Accept-Encoding: gzip" header field and is
* therefore responsible for also decompressing the transfer stream.
@@ -133,12 +128,16 @@ public class HttpEngine {
/** Null until a response is received from the network or the cache. */
ResponseHeaders responseHeaders;
- // The cache response currently being validated on a conditional get. Null
- // if the cached response doesn't exist or doesn't need validation. If the
- // conditional get succeeds, these will be used for the response headers and
- // body. If it fails, these be closed and set to null.
- private ResponseHeaders cachedResponseHeaders;
- private InputStream cachedResponseBody;
+ /**
+ * The cache response currently being validated on a conditional get. Null
+ * if the cached response doesn't exist or doesn't need validation. If the
+ * conditional get succeeds, these will be used for the response. If it fails,
+ * it will be set to null.
+ */
+ private Response validatingResponse;
+
+ /** The cache request currently being populated from a network response. */
+ private CacheRequest cacheRequest;
/**
* True if the socket connection should be released to the connection pool
@@ -173,11 +172,8 @@ public HttpEngine(OkHttpClient client, Policy policy, String method, RawHeaders
this.requestHeaders = new RequestHeaders(uri, new RawHeaders(requestHeaders));
- if (connection != null) {
- connected = true;
- if (connection.getSocket() instanceof SSLSocket) {
- handshake = Handshake.get(((SSLSocket) connection.getSocket()).getSession());
- }
+ if (connection != null && connection.getSocket() instanceof SSLSocket) {
+ handshake = Handshake.get(((SSLSocket) connection.getSocket()).getSession());
}
}
@@ -204,16 +200,21 @@ public final void sendRequest() throws IOException {
// The raw response source may require the network, but the request
// headers may forbid network use. In that case, dispose of the network
- // response and use a GATEWAY_TIMEOUT response instead, as specified
+ // response and use a gateway timeout response instead, as specified
// by http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.4.
if (requestHeaders.isOnlyIfCached() && responseSource.requiresConnection()) {
if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
- Util.closeQuietly(cachedResponseBody);
+ Util.closeQuietly(validatingResponse.body());
}
this.responseSource = ResponseSource.CACHE;
- this.cacheResponse = GATEWAY_TIMEOUT_RESPONSE;
- RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(cacheResponse.getHeaders(), true);
- setResponse(new ResponseHeaders(uri, rawResponseHeaders), cacheResponse.getBody());
+
+ RawHeaders gatewayTimeoutHeaders = new RawHeaders();
+ gatewayTimeoutHeaders.setStatusLine("HTTP/1.1 504 Gateway Timeout");
+ this.validatingResponse = new Response.Builder(request(), 504)
+ .rawHeaders(gatewayTimeoutHeaders)
+ .body(EMPTY_BODY)
+ .build();
+ promoteValidatingResponse(new ResponseHeaders(uri, gatewayTimeoutHeaders));
}
if (responseSource.requiresConnection()) {
@@ -235,35 +236,52 @@ private void initResponseSource() throws IOException {
OkResponseCache responseCache = client.getOkResponseCache();
if (responseCache == null) return;
- CacheResponse candidate = responseCache.get(
- uri, method, requestHeaders.getHeaders().toMultimap(false));
+ Response candidate = responseCache.get(request());
if (candidate == null) return;
- Map> responseHeadersMap = candidate.getHeaders();
- cachedResponseBody = candidate.getBody();
- if (!acceptCacheResponseType(candidate)
- || responseHeadersMap == null
- || cachedResponseBody == null) {
- Util.closeQuietly(cachedResponseBody);
+ if (!acceptCacheResponseType(candidate)) {
+ Util.closeQuietly(candidate.body());
return;
}
- RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(responseHeadersMap, true);
- cachedResponseHeaders = new ResponseHeaders(uri, rawResponseHeaders);
+ ResponseHeaders cachedResponseHeaders = new ResponseHeaders(uri, candidate.rawHeaders());
long now = System.currentTimeMillis();
this.responseSource = cachedResponseHeaders.chooseResponseSource(now, requestHeaders);
if (responseSource == ResponseSource.CACHE) {
- this.cacheResponse = candidate;
- setResponse(cachedResponseHeaders, cachedResponseBody);
+ this.validatingResponse = candidate;
+ promoteValidatingResponse(cachedResponseHeaders);
} else if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
- this.cacheResponse = candidate;
+ this.validatingResponse = candidate;
} else if (responseSource == ResponseSource.NETWORK) {
- Util.closeQuietly(cachedResponseBody);
+ Util.closeQuietly(candidate.body());
} else {
throw new AssertionError();
}
}
+ private Request request() {
+ // This doesn't have a body. When we're sending requests to the cache, we don't need it.
+ return new Request.Builder(policy.getURL())
+ .method(method, null)
+ .rawHeaders(requestHeaders.getHeaders())
+ .build();
+ }
+
+ private Response response() {
+ RawHeaders rawHeaders = responseHeaders.getHeaders();
+
+ // Use an unreadable response body when offering the response to the cache. The cache isn't
+ // allowed to consume the response body bytes!
+ Response.Body body = new UnreadableResponseBody(responseHeaders.getContentType(),
+ responseHeaders.getContentLength());
+
+ return new Response.Builder(request(), rawHeaders.getResponseCode())
+ .body(body)
+ .rawHeaders(rawHeaders)
+ .handshake(handshake)
+ .build();
+ }
+
private void sendSocketRequest() throws IOException {
if (connection == null) {
connect();
@@ -322,12 +340,11 @@ protected final void connect() throws IOException {
* Called after a socket connection has been created or retrieved from the
* pool. Subclasses use this hook to get a reference to the TLS data.
*/
- protected void connected(Connection connection) {
+ private void connected(Connection connection) {
if (handshake == null && connection.getSocket() instanceof SSLSocket) {
handshake = Handshake.get(((SSLSocket) connection.getSocket()).getSession());
}
policy.setSelectedProxy(connection.getRoute().getProxy());
- connected = true;
}
/**
@@ -341,17 +358,13 @@ public void writingRequestHeaders() {
sentRequestMillis = System.currentTimeMillis();
}
- /**
- * @param body the response body, or null if it doesn't exist or isn't
- * available.
- */
- private void setResponse(ResponseHeaders headers, InputStream body) throws IOException {
- if (this.responseBodyIn != null) {
- throw new IllegalStateException();
- }
- this.responseHeaders = headers;
- if (body != null) {
- initContentStream(body);
+ private void promoteValidatingResponse(ResponseHeaders responseHeaders) throws IOException {
+ if (this.responseBodyIn != null) throw new IllegalStateException();
+
+ this.responseHeaders = responseHeaders;
+ this.handshake = validatingResponse.handshake();
+ if (validatingResponse.body() != null) {
+ initContentStream(validatingResponse.body().byteStream());
}
}
@@ -396,20 +409,15 @@ public final InputStream getResponseBody() {
return responseBodyIn;
}
- public final CacheResponse getCacheResponse() {
- return cacheResponse;
- }
-
public final Connection getConnection() {
return connection;
}
/**
- * Returns true if {@code cacheResponse} is of the right type. This
- * condition is necessary but not sufficient for the cached response to
- * be used.
+ * Returns true if {@code response} is of the right type. This condition is
+ * necessary but not sufficient for the cached response to be used.
*/
- protected boolean acceptCacheResponseType(CacheResponse cacheResponse) {
+ protected boolean acceptCacheResponseType(Response response) {
return true;
}
@@ -419,16 +427,14 @@ private void maybeCache() throws IOException {
OkResponseCache responseCache = client.getOkResponseCache();
if (responseCache == null) return;
- HttpURLConnection connectionToCache = policy.getHttpConnectionToCache();
-
// Should we cache this response for this request?
if (!responseHeaders.isCacheable(requestHeaders)) {
- responseCache.maybeRemove(connectionToCache.getRequestMethod(), uri);
+ responseCache.maybeRemove(request());
return;
}
// Offer this request to the cache.
- cacheRequest = responseCache.put(uri, connectionToCache);
+ cacheRequest = responseCache.put(response());
}
/**
@@ -452,7 +458,9 @@ public final void automaticallyReleaseConnectionToPool() {
*/
public final void release(boolean streamCanceled) {
// If the response body comes from the cache, close it.
- if (responseBodyIn == cachedResponseBody) {
+ if (validatingResponse != null
+ && validatingResponse.body() != null
+ && responseBodyIn == validatingResponse.body().byteStream()) {
Util.closeQuietly(responseBodyIn);
}
@@ -477,9 +485,9 @@ private void initContentStream(InputStream transferStream) throws IOException {
// so clients don't double decompress. http://b/3009828
//
// Also remove the Content-Length in this case because it contains the
- // length 528 of the gzipped response. This isn't terribly useful and is
- // dangerous because 529 clients can query the content length, but not
- // the content encoding.
+ // length of the gzipped response. This isn't terribly useful and is
+ // dangerous because clients can query the content length, but not the
+ // content encoding.
responseHeaders.stripContentEncoding();
responseHeaders.stripContentLength();
responseBodyIn = new GZIPInputStream(transferStream);
@@ -614,7 +622,6 @@ protected boolean includeAuthorityInRequestLine() {
* no TLS connection was made.
*/
public Handshake getHandshake() {
- // TODO: initialize handshake when populating a response from the cache.
return handshake;
}
@@ -672,23 +679,26 @@ public final void readResponse() throws IOException {
responseHeaders.setResponseSource(responseSource);
if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
- if (cachedResponseHeaders.validate(responseHeaders)) {
+ ResponseHeaders validatingResponseHeaders = new ResponseHeaders(
+ uri, validatingResponse.rawHeaders());
+
+ if (validatingResponseHeaders.validate(responseHeaders)) {
release(false);
- ResponseHeaders combinedHeaders = cachedResponseHeaders.combine(responseHeaders);
- this.responseHeaders = combinedHeaders;
+ responseHeaders = validatingResponseHeaders.combine(responseHeaders);
+ handshake = validatingResponse.handshake();
- // Update the cache after applying the combined headers but before initializing the content
- // stream, otherwise the Content-Encoding header (if present) will be stripped from the
- // combined headers and not end up in the cache file if transparent gzip compression is
- // turned on.
+ // Update the cache after combining headers but before stripping the
+ // Content-Encoding header (as performed by initContentStream()).
OkResponseCache responseCache = client.getOkResponseCache();
responseCache.trackConditionalCacheHit();
- responseCache.update(cacheResponse, policy.getHttpConnectionToCache());
+ responseCache.update(validatingResponse, response());
- initContentStream(cachedResponseBody);
+ if (validatingResponse.body() != null) {
+ initContentStream(validatingResponse.body().byteStream());
+ }
return;
} else {
- Util.closeQuietly(cachedResponseBody);
+ Util.closeQuietly(validatingResponse.body());
}
}
@@ -709,4 +719,30 @@ public void receiveHeaders(RawHeaders headers) throws IOException {
cookieHandler.put(uri, headers.toMultimap(true));
}
}
+
+ static class UnreadableResponseBody extends Response.Body {
+ private final String contentType;
+ private final long contentLength;
+
+ public UnreadableResponseBody(String contentType, long contentLength) {
+ this.contentType = contentType;
+ this.contentLength = contentLength;
+ }
+
+ @Override public boolean ready() throws IOException {
+ throw new IllegalStateException("It is an error to read this response body at this time.");
+ }
+
+ @Override public MediaType contentType() {
+ return contentType != null ? MediaType.parse(contentType) : null;
+ }
+
+ @Override public long contentLength() {
+ return contentLength;
+ }
+
+ @Override public InputStream byteStream() {
+ throw new IllegalStateException("It is an error to read this response body at this time.");
+ }
+ }
}
diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java
index fb4a7048d33e..39c099719a25 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java
@@ -266,10 +266,6 @@ private void initHttpEngine() throws IOException {
}
}
- @Override public HttpURLConnection getHttpConnectionToCache() {
- return this;
- }
-
private HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
Connection connection, RetryableOutputStream requestBody) throws IOException {
if (url.getProtocol().equals("http")) {
diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpsEngine.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpsEngine.java
index 0f1bdd232588..8c6e0b6fb86f 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpsEngine.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpsEngine.java
@@ -18,10 +18,9 @@
import com.squareup.okhttp.Connection;
import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Response;
import com.squareup.okhttp.TunnelRequest;
import java.io.IOException;
-import java.net.CacheResponse;
-import java.net.SecureCacheResponse;
import java.net.URL;
import static com.squareup.okhttp.internal.Util.getEffectivePort;
@@ -32,12 +31,8 @@ public HttpsEngine(OkHttpClient client, Policy policy, String method, RawHeaders
super(client, policy, method, requestHeaders, connection, requestBody);
}
- @Override protected void connected(Connection connection) {
- super.connected(connection);
- }
-
- @Override protected boolean acceptCacheResponseType(CacheResponse cacheResponse) {
- return cacheResponse instanceof SecureCacheResponse;
+ @Override protected boolean acceptCacheResponseType(Response response) {
+ return response.handshake() != null;
}
@Override protected boolean includeAuthorityInRequestLine() {
diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java
index d9c38b032bfe..594240f39fb9 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpsURLConnectionImpl.java
@@ -21,9 +21,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.net.HttpURLConnection;
import java.net.ProtocolException;
-import java.net.SecureCacheResponse;
import java.net.URL;
import java.security.Permission;
import java.security.Principal;
@@ -37,29 +35,20 @@
public final class HttpsURLConnectionImpl extends HttpsURLConnection {
- /** HttpUrlConnectionDelegate allows reuse of HttpURLConnectionImpl. */
- private final HttpUrlConnectionDelegate delegate;
+ /** Reuse HttpURLConnectionImpl. */
+ private final HttpURLConnectionImpl delegate;
public HttpsURLConnectionImpl(URL url, OkHttpClient client) {
super(url);
- delegate = new HttpUrlConnectionDelegate(url, client);
+ delegate = new HttpURLConnectionImpl(url, client);
}
@Override public String getCipherSuite() {
- SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse();
- if (cacheResponse != null) {
- return cacheResponse.getCipherSuite();
- }
Handshake handshake = handshake();
return handshake != null ? handshake.cipherSuite() : null;
}
@Override public Certificate[] getLocalCertificates() {
- SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse();
- if (cacheResponse != null) {
- List result = cacheResponse.getLocalCertificateChain();
- return result != null ? result.toArray(new Certificate[result.size()]) : null;
- }
Handshake handshake = handshake();
if (handshake == null) return null;
List result = handshake.localCertificates();
@@ -67,11 +56,6 @@ public HttpsURLConnectionImpl(URL url, OkHttpClient client) {
}
@Override public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException {
- SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse();
- if (cacheResponse != null) {
- List result = cacheResponse.getServerCertificateChain();
- return result != null ? result.toArray(new Certificate[result.size()]) : null;
- }
Handshake handshake = handshake();
if (handshake == null) return null;
List result = handshake.peerCertificates();
@@ -79,25 +63,17 @@ public HttpsURLConnectionImpl(URL url, OkHttpClient client) {
}
@Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
- SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse();
- if (cacheResponse != null) return cacheResponse.getPeerPrincipal();
Handshake handshake = handshake();
return handshake != null ? handshake.peerPrincipal() : null;
}
@Override public Principal getLocalPrincipal() {
- SecureCacheResponse cacheResponse = delegate.getSecureCacheResponse();
- if (cacheResponse != null) return cacheResponse.getLocalPrincipal();
Handshake handshake = handshake();
return handshake != null ? handshake.localPrincipal() : null;
}
- public HttpEngine getHttpEngine() {
- return delegate.getHttpEngine();
- }
-
private Handshake handshake() {
- if (delegate.httpEngine == null || !delegate.httpEngine.connected) {
+ if (delegate.httpEngine == null) {
throw new IllegalStateException("Connection has not yet been established");
}
return delegate.httpEngine.getHandshake();
@@ -324,20 +300,4 @@ private Handshake handshake() {
@Override public SSLSocketFactory getSSLSocketFactory() {
return delegate.client.getSslSocketFactory();
}
-
- private final class HttpUrlConnectionDelegate extends HttpURLConnectionImpl {
- private HttpUrlConnectionDelegate(URL url, OkHttpClient client) {
- super(url, client);
- }
-
- @Override public HttpURLConnection getHttpConnectionToCache() {
- return HttpsURLConnectionImpl.this;
- }
-
- public SecureCacheResponse getSecureCacheResponse() {
- return httpEngine instanceof HttpsEngine
- ? (SecureCacheResponse) httpEngine.getCacheResponse()
- : null;
- }
- }
}
diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/OkResponseCacheAdapter.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/OkResponseCacheAdapter.java
deleted file mode 100644
index 5335c2bce895..000000000000
--- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/OkResponseCacheAdapter.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.OkResponseCache;
-import com.squareup.okhttp.ResponseSource;
-import java.io.IOException;
-import java.net.CacheRequest;
-import java.net.CacheResponse;
-import java.net.HttpURLConnection;
-import java.net.ResponseCache;
-import java.net.URI;
-import java.net.URLConnection;
-import java.util.List;
-import java.util.Map;
-
-public final class OkResponseCacheAdapter implements OkResponseCache {
- private final ResponseCache responseCache;
- public OkResponseCacheAdapter(ResponseCache responseCache) {
- this.responseCache = responseCache;
- }
-
- @Override public CacheResponse get(URI uri, String requestMethod,
- Map> requestHeaders) throws IOException {
- return responseCache.get(uri, requestMethod, requestHeaders);
- }
-
- @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException {
- return responseCache.put(uri, urlConnection);
- }
-
- @Override public void maybeRemove(String requestMethod, URI uri) throws IOException {
- }
-
- @Override public void update(CacheResponse conditionalCacheHit, HttpURLConnection connection)
- throws IOException {
- }
-
- @Override public void trackConditionalCacheHit() {
- }
-
- @Override public void trackResponse(ResponseSource source) {
- }
-}
diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/Policy.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/Policy.java
index 0a29d4b1ab66..edc07cf33292 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/Policy.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/Policy.java
@@ -15,7 +15,6 @@
*/
package com.squareup.okhttp.internal.http;
-import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL;
@@ -23,9 +22,6 @@ public interface Policy {
/** Returns true if HTTP response caches should be used. */
boolean getUseCaches();
- /** Returns the HttpURLConnection instance to store in the cache. */
- HttpURLConnection getHttpConnectionToCache();
-
/** Returns the current destination URL, possibly a redirect. */
URL getURL();
diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java
index 69e86568f903..31e7869d0ed1 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/ResponseHeaders.java
@@ -16,6 +16,7 @@
package com.squareup.okhttp.internal.http;
+import com.squareup.okhttp.Request;
import com.squareup.okhttp.ResponseSource;
import com.squareup.okhttp.internal.Platform;
import java.io.IOException;
@@ -23,8 +24,6 @@
import java.net.URI;
import java.util.Collections;
import java.util.Date;
-import java.util.List;
-import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
@@ -381,12 +380,9 @@ public boolean hasVaryAll() {
* Returns true if none of the Vary headers on this response have changed
* between {@code cachedRequest} and {@code newRequest}.
*/
- public boolean varyMatches(Map> cachedRequest,
- Map> newRequest) {
+ public boolean varyMatches(RawHeaders varyHeaders, Request newRequest) {
for (String field : varyFields) {
- if (!equal(cachedRequest.get(field), newRequest.get(field))) {
- return false;
- }
+ if (!equal(varyHeaders.values(field), newRequest.headers(field))) return false;
}
return true;
}
diff --git a/okhttp/src/test/java/com/squareup/okhttp/AsyncApiTest.java b/okhttp/src/test/java/com/squareup/okhttp/AsyncApiTest.java
index 8493da717ddd..3115296885bf 100644
--- a/okhttp/src/test/java/com/squareup/okhttp/AsyncApiTest.java
+++ b/okhttp/src/test/java/com/squareup/okhttp/AsyncApiTest.java
@@ -20,11 +20,16 @@
import com.squareup.okhttp.mockwebserver.MockResponse;
import com.squareup.okhttp.mockwebserver.MockWebServer;
import com.squareup.okhttp.mockwebserver.RecordedRequest;
+import java.io.File;
+import java.net.HttpURLConnection;
+import java.util.UUID;
import javax.net.ssl.SSLContext;
import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public final class AsyncApiTest {
@@ -33,9 +38,17 @@ public final class AsyncApiTest {
private RecordingReceiver receiver = new RecordingReceiver();
private static final SSLContext sslContext = SslContextBuilder.localhost();
+ private HttpResponseCache cache;
+
+ @Before public void setUp() throws Exception {
+ String tmp = System.getProperty("java.io.tmpdir");
+ File cacheDir = new File(tmp, "HttpCache-" + UUID.randomUUID());
+ cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE);
+ }
@After public void tearDown() throws Exception {
server.shutdown();
+ cache.delete();
}
@Test public void get() throws Exception {
@@ -91,4 +104,22 @@ public final class AsyncApiTest {
assertEquals("3", recordedRequest.getHeader("Content-Length"));
assertEquals("text/plain; charset=utf-8", recordedRequest.getHeader("Content-Type"));
}
+
+ @Test public void cache() throws Exception {
+ server.enqueue(new MockResponse().setBody("A").addHeader("ETag: v1"));
+ server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
+ server.play();
+
+ client.setOkResponseCache(cache);
+
+ Request request1 = new Request.Builder(server.getUrl("/")).build();
+ client.enqueue(request1, receiver);
+ receiver.await(request1).assertCode(200).assertBody("A");
+ assertNull(server.takeRequest().getHeader("If-None-Match"));
+
+ Request request2 = new Request.Builder(server.getUrl("/")).build();
+ client.enqueue(request2, receiver);
+ receiver.await(request2).assertCode(200).assertBody("A");
+ assertEquals("v1", server.takeRequest().getHeader("If-None-Match"));
+ }
}
diff --git a/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java b/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java
index 4b1e5c6c311c..82726dc03018 100644
--- a/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java
+++ b/okhttp/src/test/java/com/squareup/okhttp/internal/http/HttpResponseCacheTest.java
@@ -18,6 +18,9 @@
import com.squareup.okhttp.HttpResponseCache;
import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.OkResponseCache;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseSource;
import com.squareup.okhttp.internal.SslContextBuilder;
import com.squareup.okhttp.internal.Util;
@@ -51,7 +54,6 @@
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
@@ -60,6 +62,7 @@
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPOutputStream;
@@ -201,11 +204,10 @@ private void assertCached(boolean shouldPut, int responseCode) throws Exception
// exhaust the content stream
readAscii(conn);
- CacheResponse cached =
- cache.get(url.toURI(), "GET", Collections.>emptyMap());
+ Response cached = cache.get(new Request.Builder(url).build());
if (shouldPut) {
assertNotNull(Integer.toString(responseCode), cached);
- cached.getBody().close();
+ cached.body().close();
} else {
assertNull(Integer.toString(responseCode), cached);
}
@@ -220,40 +222,25 @@ private void assertCached(boolean shouldPut, int responseCode) throws Exception
final String body = "ABCDE";
final AtomicInteger cacheCount = new AtomicInteger();
- server.enqueue(
- new MockResponse().setStatus("HTTP/1.1 200 Fantastic").addHeader("fgh: ijk").setBody(body));
- server.play();
-
- ResponseCache.setDefault(new ResponseCache() {
- @Override public CacheResponse get(URI uri, String requestMethod,
- Map> requestHeaders) throws IOException {
- return null;
- }
-
- @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException {
- HttpURLConnection httpConnection = (HttpURLConnection) conn;
- try {
- httpConnection.getRequestProperties();
- fail();
- } catch (IllegalStateException expected) {
- }
+ server.enqueue(new MockResponse()
+ .setStatus("HTTP/1.1 200 Fantastic")
+ .addHeader("Content-Type: text/plain")
+ .addHeader("fgh: ijk")
+ .setBody(body));
+ server.play();
+
+ client.setOkResponseCache(new AbstractOkResponseCache() {
+ @Override public CacheRequest put(Response response) throws IOException {
+ assertEquals(server.getUrl("/"), response.request().url());
+ assertEquals(200, response.code());
+ assertEquals(body.length(), response.body().contentLength());
+ assertEquals("text/plain", response.body().contentType().toString());
+ assertEquals("ijk", response.header("fgh"));
try {
- httpConnection.addRequestProperty("K", "V");
+ response.body().byteStream(); // the RI doesn't forbid this, but it should
fail();
} catch (IllegalStateException expected) {
}
- assertEquals("HTTP/1.1 200 Fantastic", httpConnection.getHeaderField(null));
- assertEquals(Arrays.asList("HTTP/1.1 200 Fantastic"),
- httpConnection.getHeaderFields().get(null));
- assertEquals(200, httpConnection.getResponseCode());
- assertEquals("Fantastic", httpConnection.getResponseMessage());
- assertEquals(body.length(), httpConnection.getContentLength());
- assertEquals("ijk", httpConnection.getHeaderField("fgh"));
- try {
- httpConnection.getInputStream(); // the RI doesn't forbid this, but it should
- fail();
- } catch (IOException expected) {
- }
cacheCount.incrementAndGet();
return null;
}
@@ -265,6 +252,32 @@ private void assertCached(boolean shouldPut, int responseCode) throws Exception
assertEquals(1, cacheCount.get());
}
+ /** Don't explode if the cache returns a null body. http://b/3373699 */
+ @Test public void responseCacheReturnsNullOutputStream() throws Exception {
+ final AtomicBoolean aborted = new AtomicBoolean();
+ client.setOkResponseCache(new AbstractOkResponseCache() {
+ @Override public CacheRequest put(Response response) throws IOException {
+ return new CacheRequest() {
+ @Override public void abort() {
+ aborted.set(true);
+ }
+
+ @Override public OutputStream getBody() throws IOException {
+ return null;
+ }
+ };
+ }
+ });
+
+ server.enqueue(new MockResponse().setBody("abcdef"));
+ server.play();
+
+ HttpURLConnection connection = client.open(server.getUrl("/"));
+ assertEquals("abc", readAscii(connection, 3));
+ connection.getInputStream().close();
+ assertFalse(aborted.get()); // The best behavior is ambiguous, but RI 6 doesn't abort here
+ }
+
@Test public void responseCachingAndInputStreamSkipWithFixedLength() throws IOException {
testResponseCaching(TransferKind.FIXED_LENGTH);
}
@@ -323,32 +336,32 @@ private void testResponseCaching(TransferKind transferKind) throws IOException {
.setBody("ABC"));
server.play();
- HttpsURLConnection connection = (HttpsURLConnection) client.open(server.getUrl("/"));
- connection.setSSLSocketFactory(sslContext.getSocketFactory());
- connection.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
- assertEquals("ABC", readAscii(connection));
+ HttpsURLConnection c1 = (HttpsURLConnection) client.open(server.getUrl("/"));
+ c1.setSSLSocketFactory(sslContext.getSocketFactory());
+ c1.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
+ assertEquals("ABC", readAscii(c1));
// OpenJDK 6 fails on this line, complaining that the connection isn't open yet
- String suite = connection.getCipherSuite();
- List localCerts = toListOrNull(connection.getLocalCertificates());
- List serverCerts = toListOrNull(connection.getServerCertificates());
- Principal peerPrincipal = connection.getPeerPrincipal();
- Principal localPrincipal = connection.getLocalPrincipal();
-
- connection = (HttpsURLConnection) client.open(server.getUrl("/")); // cached!
- connection.setSSLSocketFactory(sslContext.getSocketFactory());
- connection.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
- assertEquals("ABC", readAscii(connection));
+ String suite = c1.getCipherSuite();
+ List localCerts = toListOrNull(c1.getLocalCertificates());
+ List serverCerts = toListOrNull(c1.getServerCertificates());
+ Principal peerPrincipal = c1.getPeerPrincipal();
+ Principal localPrincipal = c1.getLocalPrincipal();
+
+ HttpsURLConnection c2 = (HttpsURLConnection) client.open(server.getUrl("/")); // cached!
+ c2.setSSLSocketFactory(sslContext.getSocketFactory());
+ c2.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
+ assertEquals("ABC", readAscii(c2));
assertEquals(2, cache.getRequestCount());
assertEquals(1, cache.getNetworkCount());
assertEquals(1, cache.getHitCount());
- assertEquals(suite, connection.getCipherSuite());
- assertEquals(localCerts, toListOrNull(connection.getLocalCertificates()));
- assertEquals(serverCerts, toListOrNull(connection.getServerCertificates()));
- assertEquals(peerPrincipal, connection.getPeerPrincipal());
- assertEquals(localPrincipal, connection.getLocalPrincipal());
+ assertEquals(suite, c2.getCipherSuite());
+ assertEquals(localCerts, toListOrNull(c2.getLocalCertificates()));
+ assertEquals(serverCerts, toListOrNull(c2.getServerCertificates()));
+ assertEquals(peerPrincipal, c2.getPeerPrincipal());
+ assertEquals(localPrincipal, c2.getLocalPrincipal());
}
@Test public void cacheReturnsInsecureResponseForSecureRequest() throws IOException {
@@ -486,15 +499,10 @@ private void testResponseCaching(TransferKind transferKind) throws IOException {
server.enqueue(new MockResponse().setBody("ABC"));
server.play();
- final AtomicReference