diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Util.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Util.java index 20b69d65d105..dbe84d14013e 100644 --- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Util.java +++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Util.java @@ -16,6 +16,7 @@ package com.squareup.okhttp.internal; +import java.io.ByteArrayInputStream; import java.io.Closeable; import java.io.EOFException; import java.io.File; @@ -44,9 +45,7 @@ public final class Util { public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; public static final String[] EMPTY_STRING_ARRAY = new String[0]; - - /** A cheap and type-safe constant for the ISO-8859-1 Charset. */ - public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + public static final InputStream EMPTY_INPUT_STREAM = new ByteArrayInputStream(EMPTY_BYTE_ARRAY); /** A cheap and type-safe constant for the US-ASCII Charset. */ public static final Charset US_ASCII = Charset.forName("US-ASCII"); diff --git a/okhttp/src/main/java/com/squareup/okhttp/Dispatcher.java b/okhttp/src/main/java/com/squareup/okhttp/Dispatcher.java index 1982a8ac4bab..554674d64522 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/Dispatcher.java +++ b/okhttp/src/main/java/com/squareup/okhttp/Dispatcher.java @@ -79,7 +79,7 @@ static class RealResponseBody extends Response.Body { return responseHeaders.getContentLength(); } - @Override public InputStream byteStream() throws IOException { + @Override public InputStream byteStream() { return in; } } diff --git a/okhttp/src/main/java/com/squareup/okhttp/Failure.java b/okhttp/src/main/java/com/squareup/okhttp/Failure.java index a3547003a558..b5c69c6e0173 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/Failure.java +++ b/okhttp/src/main/java/com/squareup/okhttp/Failure.java @@ -21,7 +21,7 @@ *

Warning: Experimental OkHttp 2.0 API

* This class is in beta. APIs are subject to change! */ -/* OkHttp 2.0: public */ class Failure { +public final class Failure { private final Request request; private final Throwable exception; diff --git a/okhttp/src/main/java/com/squareup/okhttp/HttpResponseCache.java b/okhttp/src/main/java/com/squareup/okhttp/HttpResponseCache.java index 21f214c1f868..646031bdfe47 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/HttpResponseCache.java +++ b/okhttp/src/main/java/com/squareup/okhttp/HttpResponseCache.java @@ -20,9 +20,6 @@ import com.squareup.okhttp.internal.DiskLruCache; import com.squareup.okhttp.internal.StrictLineReader; 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.RawHeaders; import com.squareup.okhttp.internal.http.ResponseHeaders; import java.io.BufferedWriter; @@ -37,12 +34,9 @@ import java.io.Writer; import java.net.CacheRequest; import java.net.CacheResponse; -import java.net.HttpURLConnection; import java.net.ResponseCache; -import java.net.SecureCacheResponse; import java.net.URI; import java.net.URLConnection; -import java.security.Principal; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; @@ -51,7 +45,6 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import javax.net.ssl.SSLPeerUnverifiedException; import static com.squareup.okhttp.internal.Util.US_ASCII; import static com.squareup.okhttp.internal.Util.UTF_8; @@ -113,7 +106,7 @@ * connection.addRequestProperty("Cache-Control", "max-stale=" + maxStale); * } */ -public final class HttpResponseCache extends ResponseCache { +public final class HttpResponseCache extends ResponseCache implements OkResponseCache { // TODO: add APIs to iterate the cache? private static final int VERSION = 201105; private static final int ENTRY_METADATA = 0; @@ -129,51 +122,25 @@ public final class HttpResponseCache extends ResponseCache { private int hitCount; private int requestCount; - /** - * Although this class only exposes the limited ResponseCache API, it - * implements the full OkResponseCache interface. This field is used as a - * package private handle to the complete implementation. It delegates to - * public and private members of this type. - */ - final OkResponseCache okResponseCache = new OkResponseCache() { - @Override public CacheResponse get(URI uri, String requestMethod, - Map> requestHeaders) throws IOException { - return HttpResponseCache.this.get(uri, requestMethod, requestHeaders); - } - - @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException { - return HttpResponseCache.this.put(uri, connection); - } - - @Override public void maybeRemove(String requestMethod, URI uri) throws IOException { - HttpResponseCache.this.maybeRemove(requestMethod, uri); - } - - @Override public void update( - CacheResponse conditionalCacheHit, HttpURLConnection connection) throws IOException { - HttpResponseCache.this.update(conditionalCacheHit, connection); - } - - @Override public void trackConditionalCacheHit() { - HttpResponseCache.this.trackConditionalCacheHit(); - } - - @Override public void trackResponse(ResponseSource source) { - HttpResponseCache.this.trackResponse(source); - } - }; - public HttpResponseCache(File directory, long maxSize) throws IOException { cache = DiskLruCache.open(directory, VERSION, ENTRY_COUNT, maxSize); } - private String uriToKey(URI uri) { - return Util.hash(uri.toString()); + @Override public CacheResponse get(URI uri, String s, Map> stringListMap) + throws IOException { + throw new UnsupportedOperationException("This is not a general purpose response cache."); } - @Override public CacheResponse get(URI uri, String requestMethod, - Map> requestHeaders) { - String key = uriToKey(uri); + @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException { + throw new UnsupportedOperationException("This is not a general purpose response cache."); + } + + private static String urlToKey(Request requst) { + return Util.hash(requst.urlString()); + } + + @Override public Response get(Request request) { + String key = urlToKey(request); DiskLruCache.Snapshot snapshot; Entry entry; try { @@ -187,25 +154,18 @@ private String uriToKey(URI uri) { return null; } - if (!entry.matches(uri, requestMethod, requestHeaders)) { + if (!entry.matches(request)) { snapshot.close(); return null; } - return entry.isHttps() - ? new EntrySecureCacheResponse(entry, snapshot) - : new EntryCacheResponse(entry, snapshot); + return entry.response(request, snapshot); } - @Override public CacheRequest put(URI uri, URLConnection urlConnection) throws IOException { - if (!(urlConnection instanceof HttpURLConnection)) { - return null; - } - - HttpURLConnection httpConnection = (HttpURLConnection) urlConnection; - String requestMethod = httpConnection.getRequestMethod(); + @Override public CacheRequest put(Response response) throws IOException { + String requestMethod = response.request().method(); - if (maybeRemove(requestMethod, uri)) { + if (maybeRemove(response.request())) { return null; } if (!requestMethod.equals("GET")) { @@ -215,23 +175,15 @@ private String uriToKey(URI uri) { return null; } - HttpEngine httpEngine = getHttpEngine(httpConnection); - if (httpEngine == null) { - // Don't cache unless the HTTP implementation is ours. + ResponseHeaders responseHeaders = new ResponseHeaders(null, response.rawHeaders()); + if (responseHeaders.hasVaryAll()) { return null; } - ResponseHeaders response = httpEngine.getResponseHeaders(); - if (response.hasVaryAll()) { - return null; - } - - RawHeaders varyHeaders = - httpEngine.getRequestHeaders().getHeaders().getAll(response.getVaryFields()); - Entry entry = new Entry(uri, varyHeaders, httpConnection); + Entry entry = new Entry(response); DiskLruCache.Editor editor = null; try { - editor = cache.edit(uriToKey(uri)); + editor = cache.edit(urlToKey(response.request())); if (editor == null) { return null; } @@ -243,15 +195,11 @@ private String uriToKey(URI uri) { } } - /** - * Returns true if the supplied {@code requestMethod} potentially invalidates an entry in the - * cache. - */ - private boolean maybeRemove(String requestMethod, URI uri) { - if (requestMethod.equals("POST") || requestMethod.equals("PUT") || requestMethod.equals( - "DELETE")) { + @Override public boolean maybeRemove(Request request) { + String method = request.method(); + if (method.equals("POST") || method.equals("PUT") || method.equals("DELETE")) { try { - cache.remove(uriToKey(uri)); + cache.remove(urlToKey(request)); } catch (IOException ignored) { // The cache cannot be written. } @@ -260,20 +208,12 @@ private boolean maybeRemove(String requestMethod, URI uri) { return false; } - private void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection) - throws IOException { - HttpEngine httpEngine = getHttpEngine(httpConnection); - URI uri = httpEngine.getUri(); - ResponseHeaders response = httpEngine.getResponseHeaders(); - RawHeaders varyHeaders = - httpEngine.getRequestHeaders().getHeaders().getAll(response.getVaryFields()); - Entry entry = new Entry(uri, varyHeaders, httpConnection); - DiskLruCache.Snapshot snapshot = (conditionalCacheHit instanceof EntryCacheResponse) - ? ((EntryCacheResponse) conditionalCacheHit).snapshot - : ((EntrySecureCacheResponse) conditionalCacheHit).snapshot; + @Override public void update(Response cached, Response network) { + Entry entry = new Entry(network); + DiskLruCache.Snapshot snapshot = ((CacheResponseBody) cached.body()).snapshot; DiskLruCache.Editor editor = null; try { - editor = snapshot.edit(); // returns null if snapshot is not current + editor = snapshot.edit(); // Returns null if snapshot is not current. if (editor != null) { entry.writeTo(editor); editor.commit(); @@ -293,16 +233,6 @@ private void abortQuietly(DiskLruCache.Editor editor) { } } - private HttpEngine getHttpEngine(URLConnection httpConnection) { - if (httpConnection instanceof HttpURLConnectionImpl) { - return ((HttpURLConnectionImpl) httpConnection).getHttpEngine(); - } else if (httpConnection instanceof HttpsURLConnectionImpl) { - return ((HttpsURLConnectionImpl) httpConnection).getHttpEngine(); - } else { - return null; - } - } - /** * Closes the cache and deletes all of its stored values. This will delete * all files in the cache directory including files that weren't created by @@ -344,7 +274,7 @@ public boolean isClosed() { return cache.isClosed(); } - private synchronized void trackResponse(ResponseSource source) { + @Override public synchronized void trackResponse(ResponseSource source) { requestCount++; switch (source) { @@ -358,7 +288,7 @@ private synchronized void trackResponse(ResponseSource source) { } } - private synchronized void trackConditionalCacheHit() { + @Override public synchronized void trackConditionalCacheHit() { hitCount++; } @@ -425,7 +355,7 @@ public CacheRequestImpl(final DiskLruCache.Editor editor) throws IOException { } private static final class Entry { - private final String uri; + private final String url; private final RawHeaders varyHeaders; private final String requestMethod; private final RawHeaders responseHeaders; @@ -483,7 +413,7 @@ private static final class Entry { public Entry(InputStream in) throws IOException { try { StrictLineReader reader = new StrictLineReader(in, US_ASCII); - uri = reader.readLine(); + url = reader.readLine(); requestMethod = reader.readLine(); varyHeaders = new RawHeaders(); int varyRequestHeaderLineCount = reader.readInt(); @@ -515,26 +445,20 @@ public Entry(InputStream in) throws IOException { } } - public Entry(URI uri, RawHeaders varyHeaders, HttpURLConnection httpConnection) - throws IOException { - this.uri = uri.toString(); - this.varyHeaders = varyHeaders; - this.requestMethod = httpConnection.getRequestMethod(); - this.responseHeaders = RawHeaders.fromMultimap(httpConnection.getHeaderFields(), true); - this.handshake = getHttpEngine(httpConnection).getHandshake(); - } - - private HttpEngine getHttpEngine(HttpURLConnection httpConnection) { - return httpConnection instanceof HttpsURLConnectionImpl - ? ((HttpsURLConnectionImpl) httpConnection).getHttpEngine() - : ((HttpURLConnectionImpl) httpConnection).getHttpEngine(); + public Entry(Response response) { + this.url = response.request().urlString(); + this.varyHeaders = response.request().rawHeaders().getAll( + new ResponseHeaders(null, response.rawHeaders()).getVaryFields()); + this.requestMethod = response.request().method(); + this.responseHeaders = response.rawHeaders(); + this.handshake = response.handshake(); } public void writeTo(DiskLruCache.Editor editor) throws IOException { OutputStream out = editor.newOutputStream(ENTRY_METADATA); Writer writer = new BufferedWriter(new OutputStreamWriter(out, UTF_8)); - writer.write(uri + '\n'); + writer.write(url + '\n'); writer.write(requestMethod + '\n'); writer.write(Integer.toString(varyHeaders.length()) + '\n'); for (int i = 0; i < varyHeaders.length(); i++) { @@ -557,7 +481,7 @@ public void writeTo(DiskLruCache.Editor editor) throws IOException { } private boolean isHttps() { - return uri.startsWith("https://"); + return url.startsWith("https://"); } private List readCertificateList(StrictLineReader reader) throws IOException { @@ -591,90 +515,62 @@ private void writeCertArray(Writer writer, List certificates) throw } } - public boolean matches(URI uri, String requestMethod, - Map> requestHeaders) { - return this.uri.equals(uri.toString()) - && this.requestMethod.equals(requestMethod) - && new ResponseHeaders(uri, responseHeaders).varyMatches(varyHeaders.toMultimap(false), - requestHeaders); - } - } - - /** - * Returns an input stream that reads the body of a snapshot, closing the - * snapshot when the stream is closed. - */ - private static InputStream newBodyInputStream(final DiskLruCache.Snapshot snapshot) { - return new FilterInputStream(snapshot.getInputStream(ENTRY_BODY)) { - @Override public void close() throws IOException { - snapshot.close(); - super.close(); - } - }; - } - - static class EntryCacheResponse extends CacheResponse { - private final Entry entry; - private final DiskLruCache.Snapshot snapshot; - private final InputStream in; - - public EntryCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) { - this.entry = entry; - this.snapshot = snapshot; - this.in = newBodyInputStream(snapshot); - } - - @Override public Map> getHeaders() { - return entry.responseHeaders.toMultimap(true); + public boolean matches(Request request) { + return url.equals(request.urlString()) + && requestMethod.equals(request.method()) + && new ResponseHeaders(null, responseHeaders).varyMatches(varyHeaders, request); } - @Override public InputStream getBody() { - return in; + public Response response(Request request, DiskLruCache.Snapshot snapshot) { + String contentType = responseHeaders.get("Content-Type"); + String contentLength = responseHeaders.get("Content-Length"); + return new Response.Builder(request, responseHeaders.getResponseCode()) + .rawHeaders(responseHeaders) + .body(new CacheResponseBody(snapshot, contentType, contentLength)) + .handshake(handshake) + .build(); } } - static class EntrySecureCacheResponse extends SecureCacheResponse { - private final Entry entry; + private static class CacheResponseBody extends Response.Body { private final DiskLruCache.Snapshot snapshot; - private final InputStream in; + private final InputStream bodyIn; + private final String contentType; + private final String contentLength; - public EntrySecureCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) { - this.entry = entry; + public CacheResponseBody(final DiskLruCache.Snapshot snapshot, + String contentType, String contentLength) { this.snapshot = snapshot; - this.in = newBodyInputStream(snapshot); - } - - @Override public Map> getHeaders() { - return entry.responseHeaders.toMultimap(true); - } - - @Override public InputStream getBody() { - return in; - } + this.contentType = contentType; + this.contentLength = contentLength; - @Override public String getCipherSuite() { - return entry.handshake.cipherSuite(); + // This input stream closes the snapshot when the stream is closed. + this.bodyIn = new FilterInputStream(snapshot.getInputStream(ENTRY_BODY)) { + @Override public void close() throws IOException { + snapshot.close(); + super.close(); + } + }; } - @Override public List getServerCertificateChain() - throws SSLPeerUnverifiedException { - if (entry.handshake.peerCertificates().isEmpty()) throw new SSLPeerUnverifiedException(null); - return entry.handshake.peerCertificates(); + @Override public boolean ready() throws IOException { + return true; } - @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { - if (entry.handshake.peerCertificates().isEmpty()) throw new SSLPeerUnverifiedException(null); - return entry.handshake.peerPrincipal(); + @Override public MediaType contentType() { + return contentType != null ? MediaType.parse(contentType) : null; } - @Override public List getLocalCertificateChain() { - return !entry.handshake.localCertificates().isEmpty() - ? entry.handshake.localCertificates() - : null; + @Override public long contentLength() { + try { + return contentLength != null ? Long.parseLong(contentLength) : -1; + } catch (NumberFormatException e) { + return -1; + } } - @Override public Principal getLocalPrincipal() { - return entry.handshake.localPrincipal(); + @Override public InputStream byteStream() { + return bodyIn; } } } diff --git a/okhttp/src/main/java/com/squareup/okhttp/Job.java b/okhttp/src/main/java/com/squareup/okhttp/Job.java index f1ffa2824b4a..cacda366d8ce 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/Job.java +++ b/okhttp/src/main/java/com/squareup/okhttp/Job.java @@ -22,7 +22,6 @@ import com.squareup.okhttp.internal.http.Policy; import com.squareup.okhttp.internal.http.RawHeaders; import java.io.IOException; -import java.net.HttpURLConnection; import java.net.ProtocolException; import java.net.Proxy; import java.net.URL; @@ -61,11 +60,7 @@ public Job(Dispatcher dispatcher, OkHttpClient client, Request request, } @Override public boolean getUseCaches() { - return false; // TODO. - } - - @Override public HttpURLConnection getHttpConnectionToCache() { - return null; + return true; } @Override public URL getURL() { diff --git a/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java b/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java index f78592fcbfde..731f6894b66d 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java +++ b/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java @@ -19,7 +19,6 @@ 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.OkResponseCacheAdapter; import com.squareup.okhttp.internal.tls.OkHostnameVerifier; import java.net.CookieHandler; import java.net.HttpURLConnection; @@ -48,7 +47,7 @@ public final class OkHttpClient implements URLStreamHandlerFactory { private List 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>> requestHeadersRef = - new AtomicReference>>(); - ResponseCache.setDefault(new ResponseCache() { - @Override public CacheResponse get(URI uri, String requestMethod, - Map> requestHeaders) throws IOException { - requestHeadersRef.set(requestHeaders); - return null; - } - @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException { + final AtomicReference requestRef = new AtomicReference(); + client.setOkResponseCache(new AbstractOkResponseCache() { + @Override public Response get(Request request) throws IOException { + requestRef.set(request); return null; } }); @@ -503,7 +511,7 @@ private void testResponseCaching(TransferKind transferKind) throws IOException { URLConnection urlConnection = openConnection(url); urlConnection.addRequestProperty("A", "android"); readAscii(urlConnection); - assertEquals(Arrays.asList("android"), requestHeadersRef.get().get("A")); + assertEquals(Arrays.asList("android"), requestRef.get().headers("A")); } @Test public void serverDisconnectsPrematurelyWithContentLengthHeader() throws IOException { @@ -1903,7 +1911,8 @@ private MockResponse truncateViolently(MockResponse response, int numBytesToKeep private String readAscii(URLConnection connection, int count) throws IOException { HttpURLConnection httpConnection = (HttpURLConnection) connection; InputStream in = httpConnection.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST - ? connection.getInputStream() : httpConnection.getErrorStream(); + ? connection.getInputStream() + : httpConnection.getErrorStream(); StringBuilder result = new StringBuilder(); for (int i = 0; i < count; i++) { int value = in.read(); @@ -2002,4 +2011,27 @@ private class InsecureResponseCache extends ResponseCache { return response; } } + + static abstract class AbstractOkResponseCache implements OkResponseCache { + @Override public Response get(Request request) throws IOException { + return null; + } + + @Override public CacheRequest put(Response response) throws IOException { + return null; + } + + @Override public boolean maybeRemove(Request request) throws IOException { + return false; + } + + @Override public void update(Response cached, Response network) throws IOException { + } + + @Override public void trackConditionalCacheHit() { + } + + @Override public void trackResponse(ResponseSource source) { + } + } } diff --git a/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java b/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java index f2c29430fbc6..87cc0a372318 100644 --- a/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java +++ b/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java @@ -60,7 +60,6 @@ import java.util.Random; import java.util.Set; import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import javax.net.ssl.HttpsURLConnection; @@ -2113,35 +2112,22 @@ private void testFlushAfterStreamTransmitted(TransferKind transferKind) throws I } /** 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.setResponseCache(new ResponseCache() { + @Test public void installDeprecatedJavaNetResponseCache() throws Exception { + ResponseCache cache = new ResponseCache() { @Override public CacheResponse get(URI uri, String requestMethod, Map> requestHeaders) throws IOException { return null; } - @Override public CacheRequest put(URI uri, URLConnection connection) throws IOException { - return new CacheRequest() { - @Override public void abort() { - aborted.set(true); - } - - @Override public OutputStream getBody() throws IOException { - return null; - } - }; + return null; } - }); - - server.enqueue(new MockResponse().setBody("abcdef")); - server.play(); + }; - HttpURLConnection connection = client.open(server.getUrl("/")); - InputStream in = connection.getInputStream(); - assertEquals("abc", readAscii(in, 3)); - in.close(); - assertFalse(aborted.get()); // The best behavior is ambiguous, but RI 6 doesn't abort here + try { + client.setResponseCache(cache); + fail(); + } catch (UnsupportedOperationException expected) { + } } /** http://code.google.com/p/android/issues/detail?id=14562 */