/* GENERATED SOURCE. DO NOT MODIFY. */ /* * Copyright (C) 2010 The Android Open Source Project * * 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.android.okhttp; import com.android.okhttp.internal.DiskLruCache; import com.android.okhttp.internal.InternalCache; import com.android.okhttp.internal.Util; import com.android.okhttp.internal.http.CacheRequest; import com.android.okhttp.internal.http.CacheStrategy; import com.android.okhttp.internal.http.HttpMethod; import com.android.okhttp.internal.http.OkHeaders; import com.android.okhttp.internal.http.StatusLine; import com.android.okhttp.internal.io.FileSystem; import java.io.File; import java.io.IOException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import com.android.okhttp.okio.Buffer; import com.android.okhttp.okio.BufferedSink; import com.android.okhttp.okio.BufferedSource; import com.android.okhttp.okio.ByteString; import com.android.okhttp.okio.ForwardingSink; import com.android.okhttp.okio.ForwardingSource; import com.android.okhttp.okio.Okio; import com.android.okhttp.okio.Sink; import com.android.okhttp.okio.Source; /** * Caches HTTP and HTTPS responses to the filesystem so they may be reused, saving time and * bandwidth. * *
The best way to improve the cache hit rate is by configuring the web server to return * cacheable responses. Although this client honors all HTTP/1.1 (RFC 7234) cache headers, it doesn't cache * partial responses. * *
{@code * * Request request = new Request.Builder() * .cacheControl(new CacheControl.Builder().noCache().build()) * .url("http://publicobject.com/helloworld.txt") * .build(); * }* * If it is only necessary to force a cached response to be validated by the server, use the more * efficient {@code max-age=0} directive instead:
{@code * * Request request = new Request.Builder() * .cacheControl(new CacheControl.Builder() * .maxAge(0, TimeUnit.SECONDS) * .build()) * .url("http://publicobject.com/helloworld.txt") * .build(); * }* *
{@code * * Request request = new Request.Builder() * .cacheControl(new CacheControl.Builder() * .onlyIfCached() * .build()) * .url("http://publicobject.com/helloworld.txt") * .build(); * Response forceCacheResponse = client.newCall(request).execute(); * if (forceCacheResponse.code() != 504) { * // The resource was cached! Show it. * } else { * // The resource was not cached. * } * }* This technique works even better in situations where a stale response is better than no response. * To permit stale cached responses, use the {@code max-stale} directive with the maximum staleness * in seconds:
{@code * * Request request = new Request.Builder() * .cacheControl(new CacheControl.Builder() * .maxStale(365, TimeUnit.DAYS) * .build()) * .url("http://publicobject.com/helloworld.txt") * .build(); * }* *
The {@link CacheControl} class can configure request caching directives and parse response * caching directives. It even offers convenient constants {@link CacheControl#FORCE_NETWORK} and * {@link CacheControl#FORCE_CACHE} that address the use cases above. * @hide This class is not part of the Android public SDK API */ public final class Cache { private static final int VERSION = 201105; private static final int ENTRY_METADATA = 0; private static final int ENTRY_BODY = 1; private static final int ENTRY_COUNT = 2; // Android-changed: internalCache made public so it can be used from Android internalapi package. public final InternalCache internalCache = new InternalCache() { @Override public Response get(Request request) throws IOException { return Cache.this.get(request); } @Override public CacheRequest put(Response response) throws IOException { return Cache.this.put(response); } @Override public void remove(Request request) throws IOException { Cache.this.remove(request); } @Override public void update(Response cached, Response network) throws IOException { Cache.this.update(cached, network); } @Override public void trackConditionalCacheHit() { Cache.this.trackConditionalCacheHit(); } @Override public void trackResponse(CacheStrategy cacheStrategy) { Cache.this.trackResponse(cacheStrategy); } }; private final DiskLruCache cache; /* read and write statistics, all guarded by 'this' */ private int writeSuccessCount; private int writeAbortCount; private int networkCount; private int hitCount; private int requestCount; public Cache(File directory, long maxSize) { this(directory, maxSize, FileSystem.SYSTEM); } Cache(File directory, long maxSize, FileSystem fileSystem) { this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize); } private static String urlToKey(Request request) { return Util.md5Hex(request.urlString()); } Response get(Request request) { String key = urlToKey(request); DiskLruCache.Snapshot snapshot; Entry entry; try { snapshot = cache.get(key); if (snapshot == null) { return null; } } catch (IOException e) { // Give up because the cache cannot be read. return null; } try { entry = new Entry(snapshot.getSource(ENTRY_METADATA)); } catch (IOException e) { Util.closeQuietly(snapshot); return null; } Response response = entry.response(request, snapshot); if (!entry.matches(request, response)) { Util.closeQuietly(response.body()); return null; } return response; } private CacheRequest put(Response response) throws IOException { String requestMethod = response.request().method(); if (HttpMethod.invalidatesCache(response.request().method())) { try { remove(response.request()); } catch (IOException ignored) { // The cache cannot be written. } return null; } if (!requestMethod.equals("GET")) { // Don't cache non-GET responses. We're technically allowed to cache // HEAD requests and some POST requests, but the complexity of doing // so is high and the benefit is low. return null; } if (OkHeaders.hasVaryAll(response)) { return null; } Entry entry = new Entry(response); DiskLruCache.Editor editor = null; try { editor = cache.edit(urlToKey(response.request())); if (editor == null) { return null; } entry.writeTo(editor); return new CacheRequestImpl(editor); } catch (IOException e) { abortQuietly(editor); return null; } } private void remove(Request request) throws IOException { cache.remove(urlToKey(request)); } private 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. if (editor != null) { entry.writeTo(editor); editor.commit(); } } catch (IOException e) { abortQuietly(editor); } } private void abortQuietly(DiskLruCache.Editor editor) { // Give up because the cache cannot be written. try { if (editor != null) { editor.abort(); } } catch (IOException ignored) { } } /** * Initialize the cache. This will include reading the journal files from * the storage and building up the necessary in-memory cache information. *
* The initialization time may vary depending on the journal file size and * the current actual cache size. The application needs to be aware of calling * this function during the initialization phase and preferably in a background * worker thread. *
* Note that if the application chooses to not call this method to initialize * the cache. By default, the okhttp will perform lazy initialization upon the * first usage of the cache. */ public void initialize() throws IOException { cache.initialize(); } /** * 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 * the cache. */ public void delete() throws IOException { cache.delete(); } /** * Deletes all values stored in the cache. In-flight writes to the cache will * complete normally, but the corresponding responses will not be stored. */ public void evictAll() throws IOException { cache.evictAll(); } /** * Returns an iterator over the URLs in this cache. This iterator doesn't throw {@code * ConcurrentModificationException}, but if new responses are added while iterating, their URLs * will not be returned. If existing responses are evicted during iteration, they will be absent * (unless they were already returned). * *
The iterator supports {@linkplain Iterator#remove}. Removing a URL from the iterator evicts
* the corresponding response from the cache. Use this to evict selected responses.
*/
public Iterator A typical HTTPS file looks like this:
* Next is the response status line, followed by the number of HTTP
* response header lines, followed by those lines.
*
* HTTPS responses also contain SSL session information. This begins
* with a blank line, and then a line containing the cipher suite. Next
* is the length of the peer certificate chain. These certificates are
* base64-encoded and appear each on their own line. The next line
* contains the length of the local certificate chain. These
* certificates are also base64-encoded and appear each on their own
* line. A length of -1 is used to encode a null array.
*/
public Entry(Source in) throws IOException {
try {
BufferedSource source = Okio.buffer(in);
url = source.readUtf8LineStrict();
requestMethod = source.readUtf8LineStrict();
Headers.Builder varyHeadersBuilder = new Headers.Builder();
int varyRequestHeaderLineCount = readInt(source);
for (int i = 0; i < varyRequestHeaderLineCount; i++) {
varyHeadersBuilder.addLenient(source.readUtf8LineStrict());
}
varyHeaders = varyHeadersBuilder.build();
StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());
protocol = statusLine.protocol;
code = statusLine.code;
message = statusLine.message;
Headers.Builder responseHeadersBuilder = new Headers.Builder();
int responseHeaderLineCount = readInt(source);
for (int i = 0; i < responseHeaderLineCount; i++) {
responseHeadersBuilder.addLenient(source.readUtf8LineStrict());
}
responseHeaders = responseHeadersBuilder.build();
if (isHttps()) {
String blank = source.readUtf8LineStrict();
if (blank.length() > 0) {
throw new IOException("expected \"\" but was \"" + blank + "\"");
}
String cipherSuite = source.readUtf8LineStrict();
List{@code
* http://google.com/foo
* GET
* 2
* Accept-Language: fr-CA
* Accept-Charset: UTF-8
* HTTP/1.1 200 OK
* 3
* Content-Type: image/png
* Content-Length: 100
* Cache-Control: max-age=600
* }
*
* {@code
* https://google.com/foo
* GET
* 2
* Accept-Language: fr-CA
* Accept-Charset: UTF-8
* HTTP/1.1 200 OK
* 3
* Content-Type: image/png
* Content-Length: 100
* Cache-Control: max-age=600
*
* AES_256_WITH_MD5
* 2
* base64-encoded peerCertificate[0]
* base64-encoded peerCertificate[1]
* -1
* }
* The file is newline separated. The first two lines are the URL and
* the request method. Next is the number of HTTP Vary request header
* lines, followed by those lines.
*
*