mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-05 23:38:38 +09:00
Issue #1053 - Drop support Android and remove Fennec - Part 1a: Remove mobile/android
This commit is contained in:
parent
85045bf6be
commit
c9b411d6cd
3891 changed files with 0 additions and 428084 deletions
|
|
@ -1,178 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog;
|
||||
/* LogFactory removed by HttpClient for Android script. */
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.HttpException;
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HeaderConstants;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware;
|
||||
import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper;
|
||||
import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse;
|
||||
import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext;
|
||||
import ch.boye.httpclientandroidlib.conn.routing.HttpRoute;
|
||||
|
||||
/**
|
||||
* Class used to represent an asynchronous revalidation event, such as with
|
||||
* "stale-while-revalidate"
|
||||
*/
|
||||
class AsynchronousValidationRequest implements Runnable {
|
||||
private final AsynchronousValidator parent;
|
||||
private final CachingExec cachingExec;
|
||||
private final HttpRoute route;
|
||||
private final HttpRequestWrapper request;
|
||||
private final HttpClientContext context;
|
||||
private final HttpExecutionAware execAware;
|
||||
private final HttpCacheEntry cacheEntry;
|
||||
private final String identifier;
|
||||
private final int consecutiveFailedAttempts;
|
||||
|
||||
public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass());
|
||||
|
||||
/**
|
||||
* Used internally by {@link AsynchronousValidator} to schedule a
|
||||
* revalidation.
|
||||
* @param request
|
||||
* @param context
|
||||
* @param cacheEntry
|
||||
* @param identifier
|
||||
* @param consecutiveFailedAttempts
|
||||
*/
|
||||
AsynchronousValidationRequest(
|
||||
final AsynchronousValidator parent,
|
||||
final CachingExec cachingExec,
|
||||
final HttpRoute route,
|
||||
final HttpRequestWrapper request,
|
||||
final HttpClientContext context,
|
||||
final HttpExecutionAware execAware,
|
||||
final HttpCacheEntry cacheEntry,
|
||||
final String identifier,
|
||||
final int consecutiveFailedAttempts) {
|
||||
this.parent = parent;
|
||||
this.cachingExec = cachingExec;
|
||||
this.route = route;
|
||||
this.request = request;
|
||||
this.context = context;
|
||||
this.execAware = execAware;
|
||||
this.cacheEntry = cacheEntry;
|
||||
this.identifier = identifier;
|
||||
this.consecutiveFailedAttempts = consecutiveFailedAttempts;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
if (revalidateCacheEntry()) {
|
||||
parent.jobSuccessful(identifier);
|
||||
} else {
|
||||
parent.jobFailed(identifier);
|
||||
}
|
||||
} finally {
|
||||
parent.markComplete(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revalidate the cache entry and return if the operation was successful.
|
||||
* Success means a connection to the server was established and replay did
|
||||
* not indicate a server error.
|
||||
* @return <code>true</code> if the cache entry was successfully validated;
|
||||
* otherwise <code>false</code>
|
||||
*/
|
||||
protected boolean revalidateCacheEntry() {
|
||||
try {
|
||||
final CloseableHttpResponse httpResponse = cachingExec.revalidateCacheEntry(route, request, context, execAware, cacheEntry);
|
||||
try {
|
||||
final int statusCode = httpResponse.getStatusLine().getStatusCode();
|
||||
return isNotServerError(statusCode) && isNotStale(httpResponse);
|
||||
} finally {
|
||||
httpResponse.close();
|
||||
}
|
||||
} catch (final IOException ioe) {
|
||||
log.debug("Asynchronous revalidation failed due to I/O error", ioe);
|
||||
return false;
|
||||
} catch (final HttpException pe) {
|
||||
log.error("HTTP protocol exception during asynchronous revalidation", pe);
|
||||
return false;
|
||||
} catch (final RuntimeException re) {
|
||||
log.error("RuntimeException thrown during asynchronous revalidation: " + re);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the status code indicates a server error or not.
|
||||
* @param statusCode the status code to be checked
|
||||
* @return if the status code indicates a server error or not
|
||||
*/
|
||||
private boolean isNotServerError(final int statusCode) {
|
||||
return statusCode < 500;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to detect if the returned response is generated from a stale cache entry.
|
||||
* @param httpResponse the response to be checked
|
||||
* @return whether the response is stale or not
|
||||
*/
|
||||
private boolean isNotStale(final HttpResponse httpResponse) {
|
||||
final Header[] warnings = httpResponse.getHeaders(HeaderConstants.WARNING);
|
||||
if (warnings != null)
|
||||
{
|
||||
for (final Header warning : warnings)
|
||||
{
|
||||
/**
|
||||
* warn-codes
|
||||
* 110 = Response is stale
|
||||
* 111 = Revalidation failed
|
||||
*/
|
||||
final String warningValue = warning.getValue();
|
||||
if (warningValue.startsWith("110") || warningValue.startsWith("111"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
String getIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of consecutively failed revalidation attempts.
|
||||
* @return the number of consecutively failed revalidation attempts.
|
||||
*/
|
||||
public int getConsecutiveFailedAttempts() {
|
||||
return consecutiveFailedAttempts;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog;
|
||||
/* LogFactory removed by HttpClient for Android script. */
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware;
|
||||
import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper;
|
||||
import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext;
|
||||
import ch.boye.httpclientandroidlib.conn.routing.HttpRoute;
|
||||
|
||||
/**
|
||||
* Class used for asynchronous revalidations to be used when the "stale-
|
||||
* while-revalidate" directive is present
|
||||
*/
|
||||
class AsynchronousValidator implements Closeable {
|
||||
private final SchedulingStrategy schedulingStrategy;
|
||||
private final Set<String> queued;
|
||||
private final CacheKeyGenerator cacheKeyGenerator;
|
||||
private final FailureCache failureCache;
|
||||
|
||||
public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass());
|
||||
|
||||
/**
|
||||
* Create AsynchronousValidator which will make revalidation requests
|
||||
* using an {@link ImmediateSchedulingStrategy}. Its thread
|
||||
* pool will be configured according to the given {@link CacheConfig}.
|
||||
* @param config specifies thread pool settings. See
|
||||
* {@link CacheConfig#getAsynchronousWorkersMax()},
|
||||
* {@link CacheConfig#getAsynchronousWorkersCore()},
|
||||
* {@link CacheConfig#getAsynchronousWorkerIdleLifetimeSecs()},
|
||||
* and {@link CacheConfig#getRevalidationQueueSize()}.
|
||||
*/
|
||||
public AsynchronousValidator(final CacheConfig config) {
|
||||
this(new ImmediateSchedulingStrategy(config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create AsynchronousValidator which will make revalidation requests
|
||||
* using the supplied {@link SchedulingStrategy}. Closing the validator
|
||||
* will also close the given schedulingStrategy.
|
||||
* @param schedulingStrategy used to maintain a pool of worker threads and
|
||||
* schedules when requests are executed
|
||||
*/
|
||||
AsynchronousValidator(final SchedulingStrategy schedulingStrategy) {
|
||||
this.schedulingStrategy = schedulingStrategy;
|
||||
this.queued = new HashSet<String>();
|
||||
this.cacheKeyGenerator = new CacheKeyGenerator();
|
||||
this.failureCache = new DefaultFailureCache();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
schedulingStrategy.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules an asynchronous revalidation
|
||||
*/
|
||||
public synchronized void revalidateCacheEntry(
|
||||
final CachingExec cachingExec,
|
||||
final HttpRoute route,
|
||||
final HttpRequestWrapper request,
|
||||
final HttpClientContext context,
|
||||
final HttpExecutionAware execAware,
|
||||
final HttpCacheEntry entry) {
|
||||
// getVariantURI will fall back on getURI if no variants exist
|
||||
final String uri = cacheKeyGenerator.getVariantURI(context.getTargetHost(), request, entry);
|
||||
|
||||
if (!queued.contains(uri)) {
|
||||
final int consecutiveFailedAttempts = failureCache.getErrorCount(uri);
|
||||
final AsynchronousValidationRequest revalidationRequest =
|
||||
new AsynchronousValidationRequest(
|
||||
this, cachingExec, route, request, context, execAware, entry, uri, consecutiveFailedAttempts);
|
||||
|
||||
try {
|
||||
schedulingStrategy.schedule(revalidationRequest);
|
||||
queued.add(uri);
|
||||
} catch (final RejectedExecutionException ree) {
|
||||
log.debug("Revalidation for [" + uri + "] not scheduled: " + ree);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an identifier from the internal list of revalidation jobs in
|
||||
* progress. This is meant to be called by
|
||||
* {@link AsynchronousValidationRequest#run()} once the revalidation is
|
||||
* complete, using the identifier passed in during constructions.
|
||||
* @param identifier
|
||||
*/
|
||||
synchronized void markComplete(final String identifier) {
|
||||
queued.remove(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* The revalidation job was successful thus the number of consecutive
|
||||
* failed attempts will be reset to zero. Should be called by
|
||||
* {@link AsynchronousValidationRequest#run()}.
|
||||
* @param identifier the revalidation job's unique identifier
|
||||
*/
|
||||
void jobSuccessful(final String identifier) {
|
||||
failureCache.resetErrorCount(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* The revalidation job did fail and thus the number of consecutive failed
|
||||
* attempts will be increased. Should be called by
|
||||
* {@link AsynchronousValidationRequest#run()}.
|
||||
* @param identifier the revalidation job's unique identifier
|
||||
*/
|
||||
void jobFailed(final String identifier) {
|
||||
failureCache.increaseErrorCount(identifier);
|
||||
}
|
||||
|
||||
Set<String> getScheduledIdentifiers() {
|
||||
return Collections.unmodifiableSet(queued);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,376 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog;
|
||||
/* LogFactory removed by HttpClient for Android script. */
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.HttpHost;
|
||||
import ch.boye.httpclientandroidlib.HttpRequest;
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.HttpStatus;
|
||||
import ch.boye.httpclientandroidlib.HttpVersion;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HeaderConstants;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheInvalidator;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheStorage;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheUpdateCallback;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheUpdateException;
|
||||
import ch.boye.httpclientandroidlib.client.cache.Resource;
|
||||
import ch.boye.httpclientandroidlib.client.cache.ResourceFactory;
|
||||
import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse;
|
||||
import ch.boye.httpclientandroidlib.entity.ByteArrayEntity;
|
||||
import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
|
||||
import ch.boye.httpclientandroidlib.protocol.HTTP;
|
||||
|
||||
class BasicHttpCache implements HttpCache {
|
||||
private static final Set<String> safeRequestMethods = new HashSet<String>(
|
||||
Arrays.asList(HeaderConstants.HEAD_METHOD,
|
||||
HeaderConstants.GET_METHOD, HeaderConstants.OPTIONS_METHOD,
|
||||
HeaderConstants.TRACE_METHOD));
|
||||
|
||||
private final CacheKeyGenerator uriExtractor;
|
||||
private final ResourceFactory resourceFactory;
|
||||
private final long maxObjectSizeBytes;
|
||||
private final CacheEntryUpdater cacheEntryUpdater;
|
||||
private final CachedHttpResponseGenerator responseGenerator;
|
||||
private final HttpCacheInvalidator cacheInvalidator;
|
||||
private final HttpCacheStorage storage;
|
||||
|
||||
public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass());
|
||||
|
||||
public BasicHttpCache(
|
||||
final ResourceFactory resourceFactory,
|
||||
final HttpCacheStorage storage,
|
||||
final CacheConfig config,
|
||||
final CacheKeyGenerator uriExtractor,
|
||||
final HttpCacheInvalidator cacheInvalidator) {
|
||||
this.resourceFactory = resourceFactory;
|
||||
this.uriExtractor = uriExtractor;
|
||||
this.cacheEntryUpdater = new CacheEntryUpdater(resourceFactory);
|
||||
this.maxObjectSizeBytes = config.getMaxObjectSize();
|
||||
this.responseGenerator = new CachedHttpResponseGenerator();
|
||||
this.storage = storage;
|
||||
this.cacheInvalidator = cacheInvalidator;
|
||||
}
|
||||
|
||||
public BasicHttpCache(
|
||||
final ResourceFactory resourceFactory,
|
||||
final HttpCacheStorage storage,
|
||||
final CacheConfig config,
|
||||
final CacheKeyGenerator uriExtractor) {
|
||||
this( resourceFactory, storage, config, uriExtractor,
|
||||
new CacheInvalidator(uriExtractor, storage));
|
||||
}
|
||||
|
||||
public BasicHttpCache(
|
||||
final ResourceFactory resourceFactory,
|
||||
final HttpCacheStorage storage,
|
||||
final CacheConfig config) {
|
||||
this( resourceFactory, storage, config, new CacheKeyGenerator());
|
||||
}
|
||||
|
||||
public BasicHttpCache(final CacheConfig config) {
|
||||
this(new HeapResourceFactory(), new BasicHttpCacheStorage(config), config);
|
||||
}
|
||||
|
||||
public BasicHttpCache() {
|
||||
this(CacheConfig.DEFAULT);
|
||||
}
|
||||
|
||||
public void flushCacheEntriesFor(final HttpHost host, final HttpRequest request)
|
||||
throws IOException {
|
||||
if (!safeRequestMethods.contains(request.getRequestLine().getMethod())) {
|
||||
final String uri = uriExtractor.getURI(host, request);
|
||||
storage.removeEntry(uri);
|
||||
}
|
||||
}
|
||||
|
||||
public void flushInvalidatedCacheEntriesFor(final HttpHost host, final HttpRequest request, final HttpResponse response) {
|
||||
if (!safeRequestMethods.contains(request.getRequestLine().getMethod())) {
|
||||
cacheInvalidator.flushInvalidatedCacheEntries(host, request, response);
|
||||
}
|
||||
}
|
||||
|
||||
void storeInCache(
|
||||
final HttpHost target, final HttpRequest request, final HttpCacheEntry entry) throws IOException {
|
||||
if (entry.hasVariants()) {
|
||||
storeVariantEntry(target, request, entry);
|
||||
} else {
|
||||
storeNonVariantEntry(target, request, entry);
|
||||
}
|
||||
}
|
||||
|
||||
void storeNonVariantEntry(
|
||||
final HttpHost target, final HttpRequest req, final HttpCacheEntry entry) throws IOException {
|
||||
final String uri = uriExtractor.getURI(target, req);
|
||||
storage.putEntry(uri, entry);
|
||||
}
|
||||
|
||||
void storeVariantEntry(
|
||||
final HttpHost target,
|
||||
final HttpRequest req,
|
||||
final HttpCacheEntry entry) throws IOException {
|
||||
final String parentURI = uriExtractor.getURI(target, req);
|
||||
final String variantURI = uriExtractor.getVariantURI(target, req, entry);
|
||||
storage.putEntry(variantURI, entry);
|
||||
|
||||
final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
|
||||
|
||||
public HttpCacheEntry update(final HttpCacheEntry existing) throws IOException {
|
||||
return doGetUpdatedParentEntry(
|
||||
req.getRequestLine().getUri(), existing, entry,
|
||||
uriExtractor.getVariantKey(req, entry),
|
||||
variantURI);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
try {
|
||||
storage.updateEntry(parentURI, callback);
|
||||
} catch (final HttpCacheUpdateException e) {
|
||||
log.warn("Could not update key [" + parentURI + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void reuseVariantEntryFor(final HttpHost target, final HttpRequest req,
|
||||
final Variant variant) throws IOException {
|
||||
final String parentCacheKey = uriExtractor.getURI(target, req);
|
||||
final HttpCacheEntry entry = variant.getEntry();
|
||||
final String variantKey = uriExtractor.getVariantKey(req, entry);
|
||||
final String variantCacheKey = variant.getCacheKey();
|
||||
|
||||
final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
|
||||
public HttpCacheEntry update(final HttpCacheEntry existing)
|
||||
throws IOException {
|
||||
return doGetUpdatedParentEntry(req.getRequestLine().getUri(),
|
||||
existing, entry, variantKey, variantCacheKey);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
storage.updateEntry(parentCacheKey, callback);
|
||||
} catch (final HttpCacheUpdateException e) {
|
||||
log.warn("Could not update key [" + parentCacheKey + "]", e);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isIncompleteResponse(final HttpResponse resp, final Resource resource) {
|
||||
final int status = resp.getStatusLine().getStatusCode();
|
||||
if (status != HttpStatus.SC_OK
|
||||
&& status != HttpStatus.SC_PARTIAL_CONTENT) {
|
||||
return false;
|
||||
}
|
||||
final Header hdr = resp.getFirstHeader(HTTP.CONTENT_LEN);
|
||||
if (hdr == null) {
|
||||
return false;
|
||||
}
|
||||
final int contentLength;
|
||||
try {
|
||||
contentLength = Integer.parseInt(hdr.getValue());
|
||||
} catch (final NumberFormatException nfe) {
|
||||
return false;
|
||||
}
|
||||
return (resource.length() < contentLength);
|
||||
}
|
||||
|
||||
CloseableHttpResponse generateIncompleteResponseError(
|
||||
final HttpResponse response, final Resource resource) {
|
||||
final int contentLength = Integer.parseInt(response.getFirstHeader(HTTP.CONTENT_LEN).getValue());
|
||||
final HttpResponse error =
|
||||
new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_BAD_GATEWAY, "Bad Gateway");
|
||||
error.setHeader("Content-Type","text/plain;charset=UTF-8");
|
||||
final String msg = String.format("Received incomplete response " +
|
||||
"with Content-Length %d but actual body length %d",
|
||||
contentLength, resource.length());
|
||||
final byte[] msgBytes = msg.getBytes();
|
||||
error.setHeader("Content-Length", Integer.toString(msgBytes.length));
|
||||
error.setEntity(new ByteArrayEntity(msgBytes));
|
||||
return Proxies.enhanceResponse(error);
|
||||
}
|
||||
|
||||
HttpCacheEntry doGetUpdatedParentEntry(
|
||||
final String requestId,
|
||||
final HttpCacheEntry existing,
|
||||
final HttpCacheEntry entry,
|
||||
final String variantKey,
|
||||
final String variantCacheKey) throws IOException {
|
||||
HttpCacheEntry src = existing;
|
||||
if (src == null) {
|
||||
src = entry;
|
||||
}
|
||||
|
||||
Resource resource = null;
|
||||
if (src.getResource() != null) {
|
||||
resource = resourceFactory.copy(requestId, src.getResource());
|
||||
}
|
||||
final Map<String,String> variantMap = new HashMap<String,String>(src.getVariantMap());
|
||||
variantMap.put(variantKey, variantCacheKey);
|
||||
return new HttpCacheEntry(
|
||||
src.getRequestDate(),
|
||||
src.getResponseDate(),
|
||||
src.getStatusLine(),
|
||||
src.getAllHeaders(),
|
||||
resource,
|
||||
variantMap);
|
||||
}
|
||||
|
||||
public HttpCacheEntry updateCacheEntry(final HttpHost target, final HttpRequest request,
|
||||
final HttpCacheEntry stale, final HttpResponse originResponse,
|
||||
final Date requestSent, final Date responseReceived) throws IOException {
|
||||
final HttpCacheEntry updatedEntry = cacheEntryUpdater.updateCacheEntry(
|
||||
request.getRequestLine().getUri(),
|
||||
stale,
|
||||
requestSent,
|
||||
responseReceived,
|
||||
originResponse);
|
||||
storeInCache(target, request, updatedEntry);
|
||||
return updatedEntry;
|
||||
}
|
||||
|
||||
public HttpCacheEntry updateVariantCacheEntry(final HttpHost target, final HttpRequest request,
|
||||
final HttpCacheEntry stale, final HttpResponse originResponse,
|
||||
final Date requestSent, final Date responseReceived, final String cacheKey) throws IOException {
|
||||
final HttpCacheEntry updatedEntry = cacheEntryUpdater.updateCacheEntry(
|
||||
request.getRequestLine().getUri(),
|
||||
stale,
|
||||
requestSent,
|
||||
responseReceived,
|
||||
originResponse);
|
||||
storage.putEntry(cacheKey, updatedEntry);
|
||||
return updatedEntry;
|
||||
}
|
||||
|
||||
public HttpResponse cacheAndReturnResponse(final HttpHost host, final HttpRequest request,
|
||||
final HttpResponse originResponse, final Date requestSent, final Date responseReceived)
|
||||
throws IOException {
|
||||
return cacheAndReturnResponse(host, request,
|
||||
Proxies.enhanceResponse(originResponse), requestSent,
|
||||
responseReceived);
|
||||
}
|
||||
|
||||
public CloseableHttpResponse cacheAndReturnResponse(
|
||||
final HttpHost host,
|
||||
final HttpRequest request,
|
||||
final CloseableHttpResponse originResponse,
|
||||
final Date requestSent,
|
||||
final Date responseReceived) throws IOException {
|
||||
|
||||
boolean closeOriginResponse = true;
|
||||
final SizeLimitedResponseReader responseReader = getResponseReader(request, originResponse);
|
||||
try {
|
||||
responseReader.readResponse();
|
||||
|
||||
if (responseReader.isLimitReached()) {
|
||||
closeOriginResponse = false;
|
||||
return responseReader.getReconstructedResponse();
|
||||
}
|
||||
|
||||
final Resource resource = responseReader.getResource();
|
||||
if (isIncompleteResponse(originResponse, resource)) {
|
||||
return generateIncompleteResponseError(originResponse, resource);
|
||||
}
|
||||
|
||||
final HttpCacheEntry entry = new HttpCacheEntry(
|
||||
requestSent,
|
||||
responseReceived,
|
||||
originResponse.getStatusLine(),
|
||||
originResponse.getAllHeaders(),
|
||||
resource);
|
||||
storeInCache(host, request, entry);
|
||||
return responseGenerator.generateResponse(entry);
|
||||
} finally {
|
||||
if (closeOriginResponse) {
|
||||
originResponse.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SizeLimitedResponseReader getResponseReader(final HttpRequest request,
|
||||
final CloseableHttpResponse backEndResponse) {
|
||||
return new SizeLimitedResponseReader(
|
||||
resourceFactory, maxObjectSizeBytes, request, backEndResponse);
|
||||
}
|
||||
|
||||
public HttpCacheEntry getCacheEntry(final HttpHost host, final HttpRequest request) throws IOException {
|
||||
final HttpCacheEntry root = storage.getEntry(uriExtractor.getURI(host, request));
|
||||
if (root == null) {
|
||||
return null;
|
||||
}
|
||||
if (!root.hasVariants()) {
|
||||
return root;
|
||||
}
|
||||
final String variantCacheKey = root.getVariantMap().get(uriExtractor.getVariantKey(request, root));
|
||||
if (variantCacheKey == null) {
|
||||
return null;
|
||||
}
|
||||
return storage.getEntry(variantCacheKey);
|
||||
}
|
||||
|
||||
public void flushInvalidatedCacheEntriesFor(final HttpHost host,
|
||||
final HttpRequest request) throws IOException {
|
||||
cacheInvalidator.flushInvalidatedCacheEntries(host, request);
|
||||
}
|
||||
|
||||
public Map<String, Variant> getVariantCacheEntriesWithEtags(final HttpHost host, final HttpRequest request)
|
||||
throws IOException {
|
||||
final Map<String,Variant> variants = new HashMap<String,Variant>();
|
||||
final HttpCacheEntry root = storage.getEntry(uriExtractor.getURI(host, request));
|
||||
if (root == null || !root.hasVariants()) {
|
||||
return variants;
|
||||
}
|
||||
for(final Map.Entry<String, String> variant : root.getVariantMap().entrySet()) {
|
||||
final String variantKey = variant.getKey();
|
||||
final String variantCacheKey = variant.getValue();
|
||||
addVariantWithEtag(variantKey, variantCacheKey, variants);
|
||||
}
|
||||
return variants;
|
||||
}
|
||||
|
||||
private void addVariantWithEtag(final String variantKey,
|
||||
final String variantCacheKey, final Map<String, Variant> variants)
|
||||
throws IOException {
|
||||
final HttpCacheEntry entry = storage.getEntry(variantCacheKey);
|
||||
if (entry == null) {
|
||||
return;
|
||||
}
|
||||
final Header etagHeader = entry.getFirstHeader(HeaderConstants.ETAG);
|
||||
if (etagHeader == null) {
|
||||
return;
|
||||
}
|
||||
variants.put(etagHeader.getValue(), new Variant(variantKey, variantCacheKey, entry));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ch.boye.httpclientandroidlib.annotation.ThreadSafe;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheStorage;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheUpdateCallback;
|
||||
|
||||
/**
|
||||
* Basic {@link HttpCacheStorage} implementation backed by an instance of
|
||||
* {@link java.util.LinkedHashMap}. In other words, cache entries and
|
||||
* the cached response bodies are held in-memory. This cache does NOT
|
||||
* deallocate resources associated with the cache entries; it is intended
|
||||
* for use with {@link HeapResource} and similar. This is the default cache
|
||||
* storage backend used by {@link CachingHttpClients}.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
@ThreadSafe
|
||||
public class BasicHttpCacheStorage implements HttpCacheStorage {
|
||||
|
||||
private final CacheMap entries;
|
||||
|
||||
public BasicHttpCacheStorage(final CacheConfig config) {
|
||||
super();
|
||||
this.entries = new CacheMap(config.getMaxCacheEntries());
|
||||
}
|
||||
|
||||
/**
|
||||
* Places a HttpCacheEntry in the cache
|
||||
*
|
||||
* @param url
|
||||
* Url to use as the cache key
|
||||
* @param entry
|
||||
* HttpCacheEntry to place in the cache
|
||||
*/
|
||||
public synchronized void putEntry(final String url, final HttpCacheEntry entry) throws IOException {
|
||||
entries.put(url, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an entry from the cache, if it exists
|
||||
*
|
||||
* @param url
|
||||
* Url that is the cache key
|
||||
* @return HttpCacheEntry if one exists, or null for cache miss
|
||||
*/
|
||||
public synchronized HttpCacheEntry getEntry(final String url) throws IOException {
|
||||
return entries.get(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a HttpCacheEntry from the cache
|
||||
*
|
||||
* @param url
|
||||
* Url that is the cache key
|
||||
*/
|
||||
public synchronized void removeEntry(final String url) throws IOException {
|
||||
entries.remove(url);
|
||||
}
|
||||
|
||||
public synchronized void updateEntry(
|
||||
final String url,
|
||||
final HttpCacheUpdateCallback callback) throws IOException {
|
||||
final HttpCacheEntry existingEntry = entries.get(url);
|
||||
entries.put(url, callback.update(existingEntry));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Formatter;
|
||||
import java.util.Locale;
|
||||
|
||||
import ch.boye.httpclientandroidlib.annotation.GuardedBy;
|
||||
import ch.boye.httpclientandroidlib.annotation.ThreadSafe;
|
||||
|
||||
/**
|
||||
* Should produce reasonably unique tokens.
|
||||
*/
|
||||
@ThreadSafe
|
||||
class BasicIdGenerator {
|
||||
|
||||
private final String hostname;
|
||||
private final SecureRandom rnd;
|
||||
|
||||
@GuardedBy("this")
|
||||
private long count;
|
||||
|
||||
public BasicIdGenerator() {
|
||||
super();
|
||||
String hostname;
|
||||
try {
|
||||
hostname = InetAddress.getLocalHost().getHostName();
|
||||
} catch (final UnknownHostException ex) {
|
||||
hostname = "localhost";
|
||||
}
|
||||
this.hostname = hostname;
|
||||
try {
|
||||
this.rnd = SecureRandom.getInstance("SHA1PRNG");
|
||||
} catch (final NoSuchAlgorithmException ex) {
|
||||
throw new Error(ex);
|
||||
}
|
||||
this.rnd.setSeed(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public synchronized void generate(final StringBuilder buffer) {
|
||||
this.count++;
|
||||
final int rndnum = this.rnd.nextInt();
|
||||
buffer.append(System.currentTimeMillis());
|
||||
buffer.append('.');
|
||||
final Formatter formatter = new Formatter(buffer, Locale.US);
|
||||
formatter.format("%1$016x-%2$08x", this.count, rndnum);
|
||||
formatter.close();
|
||||
buffer.append('.');
|
||||
buffer.append(this.hostname);
|
||||
}
|
||||
|
||||
public String generate() {
|
||||
final StringBuilder buffer = new StringBuilder();
|
||||
generate(buffer);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,764 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import ch.boye.httpclientandroidlib.util.Args;
|
||||
|
||||
/**
|
||||
* <p>Java Beans-style configuration for a {@link CachingHttpClient}. Any class
|
||||
* in the caching module that has configuration options should take a
|
||||
* {@link CacheConfig} argument in one of its constructors. A
|
||||
* {@code CacheConfig} instance has sane and conservative defaults, so the
|
||||
* easiest way to specify options is to get an instance and then set just
|
||||
* the options you want to modify from their defaults.</p>
|
||||
*
|
||||
* <p><b>N.B.</b> This class is only for caching-specific configuration; to
|
||||
* configure the behavior of the rest of the client, configure the
|
||||
* {@link ch.boye.httpclientandroidlib.client.HttpClient} used as the "backend"
|
||||
* for the {@code CachingHttpClient}.</p>
|
||||
*
|
||||
* <p>Cache configuration can be grouped into the following categories:</p>
|
||||
*
|
||||
* <p><b>Cache size.</b> If the backend storage supports these limits, you
|
||||
* can specify the {@link CacheConfig#getMaxCacheEntries maximum number of
|
||||
* cache entries} as well as the {@link CacheConfig#getMaxObjectSizeBytes
|
||||
* maximum cacheable response body size}.</p>
|
||||
*
|
||||
* <p><b>Public/private caching.</b> By default, the caching module considers
|
||||
* itself to be a shared (public) cache, and will not, for example, cache
|
||||
* responses to requests with {@code Authorization} headers or responses
|
||||
* marked with {@code Cache-Control: private}. If, however, the cache
|
||||
* is only going to be used by one logical "user" (behaving similarly to a
|
||||
* browser cache), then you will want to {@link
|
||||
* CacheConfig#setSharedCache(boolean) turn off the shared cache setting}.</p>
|
||||
*
|
||||
* <p><b>303 caching</b>. RFC2616 explicitly disallows caching 303 responses;
|
||||
* however, the HTTPbis working group says they can be cached
|
||||
* if explicitly indicated in the response headers and permitted by the request method.
|
||||
* (They also indicate that disallowing 303 caching is actually an unintended
|
||||
* spec error in RFC2616).
|
||||
* This behavior is off by default, to err on the side of a conservative
|
||||
* adherence to the existing standard, but you may want to
|
||||
* {@link Builder#setAllow303Caching(boolean) enable it}.
|
||||
*
|
||||
* <p><b>Weak ETags on PUT/DELETE If-Match requests</b>. RFC2616 explicitly
|
||||
* prohibits the use of weak validators in non-GET requests, however, the
|
||||
* HTTPbis working group says while the limitation for weak validators on ranged
|
||||
* requests makes sense, weak ETag validation is useful on full non-GET
|
||||
* requests; e.g., PUT with If-Match. This behavior is off by default, to err on
|
||||
* the side of a conservative adherence to the existing standard, but you may
|
||||
* want to {@link Builder#setWeakETagOnPutDeleteAllowed(boolean) enable it}.
|
||||
*
|
||||
* <p><b>Heuristic caching</b>. Per RFC2616, a cache may cache certain cache
|
||||
* entries even if no explicit cache control headers are set by the origin.
|
||||
* This behavior is off by default, but you may want to turn this on if you
|
||||
* are working with an origin that doesn't set proper headers but where you
|
||||
* still want to cache the responses. You will want to {@link
|
||||
* CacheConfig#setHeuristicCachingEnabled(boolean) enable heuristic caching},
|
||||
* then specify either a {@link CacheConfig#getHeuristicDefaultLifetime()
|
||||
* default freshness lifetime} and/or a {@link
|
||||
* CacheConfig#setHeuristicCoefficient(float) fraction of the time since
|
||||
* the resource was last modified}. See Sections
|
||||
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.2">
|
||||
* 13.2.2</a> and <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.4">
|
||||
* 13.2.4</a> of the HTTP/1.1 RFC for more details on heuristic caching.</p>
|
||||
*
|
||||
* <p><b>Background validation</b>. The cache module supports the
|
||||
* {@code stale-while-revalidate} directive of
|
||||
* <a href="http://tools.ietf.org/html/rfc5861">RFC5861</a>, which allows
|
||||
* certain cache entry revalidations to happen in the background. You may
|
||||
* want to tweak the settings for the {@link
|
||||
* CacheConfig#getAsynchronousWorkersCore() minimum} and {@link
|
||||
* CacheConfig#getAsynchronousWorkersMax() maximum} number of background
|
||||
* worker threads, as well as the {@link
|
||||
* CacheConfig#getAsynchronousWorkerIdleLifetimeSecs() maximum time they
|
||||
* can be idle before being reclaimed}. You can also control the {@link
|
||||
* CacheConfig#getRevalidationQueueSize() size of the queue} used for
|
||||
* revalidations when there aren't enough workers to keep up with demand.</b>
|
||||
*/
|
||||
public class CacheConfig implements Cloneable {
|
||||
|
||||
/** Default setting for the maximum object size that will be
|
||||
* cached, in bytes.
|
||||
*/
|
||||
public final static int DEFAULT_MAX_OBJECT_SIZE_BYTES = 8192;
|
||||
|
||||
/** Default setting for the maximum number of cache entries
|
||||
* that will be retained.
|
||||
*/
|
||||
public final static int DEFAULT_MAX_CACHE_ENTRIES = 1000;
|
||||
|
||||
/** Default setting for the number of retries on a failed
|
||||
* cache update
|
||||
*/
|
||||
public final static int DEFAULT_MAX_UPDATE_RETRIES = 1;
|
||||
|
||||
/** Default setting for 303 caching
|
||||
*/
|
||||
public final static boolean DEFAULT_303_CACHING_ENABLED = false;
|
||||
|
||||
/** Default setting to allow weak tags on PUT/DELETE methods
|
||||
*/
|
||||
public final static boolean DEFAULT_WEAK_ETAG_ON_PUTDELETE_ALLOWED = false;
|
||||
|
||||
/** Default setting for heuristic caching
|
||||
*/
|
||||
public final static boolean DEFAULT_HEURISTIC_CACHING_ENABLED = false;
|
||||
|
||||
/** Default coefficient used to heuristically determine freshness
|
||||
* lifetime from the Last-Modified time of a cache entry.
|
||||
*/
|
||||
public final static float DEFAULT_HEURISTIC_COEFFICIENT = 0.1f;
|
||||
|
||||
/** Default lifetime in seconds to be assumed when we cannot calculate
|
||||
* freshness heuristically.
|
||||
*/
|
||||
public final static long DEFAULT_HEURISTIC_LIFETIME = 0;
|
||||
|
||||
/** Default number of worker threads to allow for background revalidations
|
||||
* resulting from the stale-while-revalidate directive.
|
||||
*/
|
||||
public static final int DEFAULT_ASYNCHRONOUS_WORKERS_MAX = 1;
|
||||
|
||||
/** Default minimum number of worker threads to allow for background
|
||||
* revalidations resulting from the stale-while-revalidate directive.
|
||||
*/
|
||||
public static final int DEFAULT_ASYNCHRONOUS_WORKERS_CORE = 1;
|
||||
|
||||
/** Default maximum idle lifetime for a background revalidation thread
|
||||
* before it gets reclaimed.
|
||||
*/
|
||||
public static final int DEFAULT_ASYNCHRONOUS_WORKER_IDLE_LIFETIME_SECS = 60;
|
||||
|
||||
/** Default maximum queue length for background revalidation requests.
|
||||
*/
|
||||
public static final int DEFAULT_REVALIDATION_QUEUE_SIZE = 100;
|
||||
|
||||
public static final CacheConfig DEFAULT = new Builder().build();
|
||||
|
||||
// TODO: make final
|
||||
private long maxObjectSize;
|
||||
private int maxCacheEntries;
|
||||
private int maxUpdateRetries;
|
||||
private boolean allow303Caching;
|
||||
private boolean weakETagOnPutDeleteAllowed;
|
||||
private boolean heuristicCachingEnabled;
|
||||
private float heuristicCoefficient;
|
||||
private long heuristicDefaultLifetime;
|
||||
private boolean isSharedCache;
|
||||
private int asynchronousWorkersMax;
|
||||
private int asynchronousWorkersCore;
|
||||
private int asynchronousWorkerIdleLifetimeSecs;
|
||||
private int revalidationQueueSize;
|
||||
private boolean neverCacheHTTP10ResponsesWithQuery;
|
||||
|
||||
/**
|
||||
* @deprecated (4.3) use {@link Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public CacheConfig() {
|
||||
super();
|
||||
this.maxObjectSize = DEFAULT_MAX_OBJECT_SIZE_BYTES;
|
||||
this.maxCacheEntries = DEFAULT_MAX_CACHE_ENTRIES;
|
||||
this.maxUpdateRetries = DEFAULT_MAX_UPDATE_RETRIES;
|
||||
this.allow303Caching = DEFAULT_303_CACHING_ENABLED;
|
||||
this.weakETagOnPutDeleteAllowed = DEFAULT_WEAK_ETAG_ON_PUTDELETE_ALLOWED;
|
||||
this.heuristicCachingEnabled = DEFAULT_HEURISTIC_CACHING_ENABLED;
|
||||
this.heuristicCoefficient = DEFAULT_HEURISTIC_COEFFICIENT;
|
||||
this.heuristicDefaultLifetime = DEFAULT_HEURISTIC_LIFETIME;
|
||||
this.isSharedCache = true;
|
||||
this.asynchronousWorkersMax = DEFAULT_ASYNCHRONOUS_WORKERS_MAX;
|
||||
this.asynchronousWorkersCore = DEFAULT_ASYNCHRONOUS_WORKERS_CORE;
|
||||
this.asynchronousWorkerIdleLifetimeSecs = DEFAULT_ASYNCHRONOUS_WORKER_IDLE_LIFETIME_SECS;
|
||||
this.revalidationQueueSize = DEFAULT_REVALIDATION_QUEUE_SIZE;
|
||||
}
|
||||
|
||||
CacheConfig(
|
||||
final long maxObjectSize,
|
||||
final int maxCacheEntries,
|
||||
final int maxUpdateRetries,
|
||||
final boolean allow303Caching,
|
||||
final boolean weakETagOnPutDeleteAllowed,
|
||||
final boolean heuristicCachingEnabled,
|
||||
final float heuristicCoefficient,
|
||||
final long heuristicDefaultLifetime,
|
||||
final boolean isSharedCache,
|
||||
final int asynchronousWorkersMax,
|
||||
final int asynchronousWorkersCore,
|
||||
final int asynchronousWorkerIdleLifetimeSecs,
|
||||
final int revalidationQueueSize,
|
||||
final boolean neverCacheHTTP10ResponsesWithQuery) {
|
||||
super();
|
||||
this.maxObjectSize = maxObjectSize;
|
||||
this.maxCacheEntries = maxCacheEntries;
|
||||
this.maxUpdateRetries = maxUpdateRetries;
|
||||
this.allow303Caching = allow303Caching;
|
||||
this.weakETagOnPutDeleteAllowed = weakETagOnPutDeleteAllowed;
|
||||
this.heuristicCachingEnabled = heuristicCachingEnabled;
|
||||
this.heuristicCoefficient = heuristicCoefficient;
|
||||
this.heuristicDefaultLifetime = heuristicDefaultLifetime;
|
||||
this.isSharedCache = isSharedCache;
|
||||
this.asynchronousWorkersMax = asynchronousWorkersMax;
|
||||
this.asynchronousWorkersCore = asynchronousWorkersCore;
|
||||
this.asynchronousWorkerIdleLifetimeSecs = asynchronousWorkerIdleLifetimeSecs;
|
||||
this.revalidationQueueSize = revalidationQueueSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current maximum response body size that will be cached.
|
||||
* @return size in bytes
|
||||
*
|
||||
* @deprecated (4.2) use {@link #getMaxObjectSize()}
|
||||
*/
|
||||
@Deprecated
|
||||
public int getMaxObjectSizeBytes() {
|
||||
return maxObjectSize > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) maxObjectSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the maximum response body size that will be eligible for caching.
|
||||
* @param maxObjectSizeBytes size in bytes
|
||||
*
|
||||
* @deprecated (4.2) use {@link Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setMaxObjectSizeBytes(final int maxObjectSizeBytes) {
|
||||
if (maxObjectSizeBytes > Integer.MAX_VALUE) {
|
||||
this.maxObjectSize = Integer.MAX_VALUE;
|
||||
} else {
|
||||
this.maxObjectSize = maxObjectSizeBytes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current maximum response body size that will be cached.
|
||||
* @return size in bytes
|
||||
*
|
||||
* @since 4.2
|
||||
*/
|
||||
public long getMaxObjectSize() {
|
||||
return maxObjectSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the maximum response body size that will be eligible for caching.
|
||||
* @param maxObjectSize size in bytes
|
||||
*
|
||||
* @since 4.2
|
||||
*
|
||||
* @deprecated (4.3) use {@link Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setMaxObjectSize(final long maxObjectSize) {
|
||||
this.maxObjectSize = maxObjectSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the cache will never cache HTTP 1.0 responses with a query string or not.
|
||||
* @return {@code true} to not cache query string responses, {@code false} to cache if explicit cache headers are
|
||||
* found
|
||||
*/
|
||||
public boolean isNeverCacheHTTP10ResponsesWithQuery() {
|
||||
return neverCacheHTTP10ResponsesWithQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of cache entries the cache will retain.
|
||||
*/
|
||||
public int getMaxCacheEntries() {
|
||||
return maxCacheEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of cache entries the cache will retain.
|
||||
*
|
||||
* @deprecated (4.3) use {@link Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setMaxCacheEntries(final int maxCacheEntries) {
|
||||
this.maxCacheEntries = maxCacheEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of times to retry a cache update on failure
|
||||
*/
|
||||
public int getMaxUpdateRetries(){
|
||||
return maxUpdateRetries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of times to retry a cache update on failure
|
||||
*
|
||||
* @deprecated (4.3) use {@link Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setMaxUpdateRetries(final int maxUpdateRetries){
|
||||
this.maxUpdateRetries = maxUpdateRetries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether 303 caching is enabled.
|
||||
* @return {@code true} if it is enabled.
|
||||
*/
|
||||
public boolean is303CachingEnabled() {
|
||||
return allow303Caching;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether weak etags is allowed with PUT/DELETE methods.
|
||||
* @return {@code true} if it is allowed.
|
||||
*/
|
||||
public boolean isWeakETagOnPutDeleteAllowed() {
|
||||
return weakETagOnPutDeleteAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether heuristic caching is enabled.
|
||||
* @return {@code true} if it is enabled.
|
||||
*/
|
||||
public boolean isHeuristicCachingEnabled() {
|
||||
return heuristicCachingEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables heuristic caching.
|
||||
* @param heuristicCachingEnabled should be {@code true} to
|
||||
* permit heuristic caching, {@code false} to disable it.
|
||||
*
|
||||
* @deprecated (4.3) use {@link Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setHeuristicCachingEnabled(final boolean heuristicCachingEnabled) {
|
||||
this.heuristicCachingEnabled = heuristicCachingEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns lifetime coefficient used in heuristic freshness caching.
|
||||
*/
|
||||
public float getHeuristicCoefficient() {
|
||||
return heuristicCoefficient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets coefficient to be used in heuristic freshness caching. This is
|
||||
* interpreted as the fraction of the time between the {@code Last-Modified}
|
||||
* and {@code Date} headers of a cached response during which the cached
|
||||
* response will be considered heuristically fresh.
|
||||
* @param heuristicCoefficient should be between {@code 0.0} and
|
||||
* {@code 1.0}.
|
||||
*
|
||||
* @deprecated (4.3) use {@link Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setHeuristicCoefficient(final float heuristicCoefficient) {
|
||||
this.heuristicCoefficient = heuristicCoefficient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default lifetime to be used if heuristic freshness calculation is
|
||||
* not possible.
|
||||
*/
|
||||
public long getHeuristicDefaultLifetime() {
|
||||
return heuristicDefaultLifetime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default lifetime in seconds to be used if heuristic freshness
|
||||
* calculation is not possible. Explicit cache control directives on
|
||||
* either the request or origin response will override this, as will
|
||||
* the heuristic {@code Last-Modified} freshness calculation if it is
|
||||
* available.
|
||||
* @param heuristicDefaultLifetimeSecs is the number of seconds to
|
||||
* consider a cache-eligible response fresh in the absence of other
|
||||
* information. Set this to {@code 0} to disable this style of
|
||||
* heuristic caching.
|
||||
*
|
||||
* @deprecated (4.3) use {@link Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setHeuristicDefaultLifetime(final long heuristicDefaultLifetimeSecs) {
|
||||
this.heuristicDefaultLifetime = heuristicDefaultLifetimeSecs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the cache will behave as a shared cache or not.
|
||||
* @return {@code true} for a shared cache, {@code false} for a non-
|
||||
* shared (private) cache
|
||||
*/
|
||||
public boolean isSharedCache() {
|
||||
return isSharedCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the cache should behave as a shared cache or not.
|
||||
* @param isSharedCache true to behave as a shared cache, false to
|
||||
* behave as a non-shared (private) cache. To have the cache
|
||||
* behave like a browser cache, you want to set this to {@code false}.
|
||||
*
|
||||
* @deprecated (4.3) use {@link Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setSharedCache(final boolean isSharedCache) {
|
||||
this.isSharedCache = isSharedCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of threads to allow for background
|
||||
* revalidations due to the {@code stale-while-revalidate} directive. A
|
||||
* value of 0 means background revalidations are disabled.
|
||||
*/
|
||||
public int getAsynchronousWorkersMax() {
|
||||
return asynchronousWorkersMax;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of threads to allow for background
|
||||
* revalidations due to the {@code stale-while-revalidate} directive.
|
||||
* @param max number of threads; a value of 0 disables background
|
||||
* revalidations.
|
||||
*
|
||||
* @deprecated (4.3) use {@link Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setAsynchronousWorkersMax(final int max) {
|
||||
this.asynchronousWorkersMax = max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum number of threads to keep alive for background
|
||||
* revalidations due to the {@code stale-while-revalidate} directive.
|
||||
*/
|
||||
public int getAsynchronousWorkersCore() {
|
||||
return asynchronousWorkersCore;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum number of threads to keep alive for background
|
||||
* revalidations due to the {@code stale-while-revalidate} directive.
|
||||
* @param min should be greater than zero and less than or equal
|
||||
* to <code>getAsynchronousWorkersMax()</code>
|
||||
*
|
||||
* @deprecated (4.3) use {@link Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setAsynchronousWorkersCore(final int min) {
|
||||
this.asynchronousWorkersCore = min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current maximum idle lifetime in seconds for a
|
||||
* background revalidation worker thread. If a worker thread is idle
|
||||
* for this long, and there are more than the core number of worker
|
||||
* threads alive, the worker will be reclaimed.
|
||||
*/
|
||||
public int getAsynchronousWorkerIdleLifetimeSecs() {
|
||||
return asynchronousWorkerIdleLifetimeSecs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current maximum idle lifetime in seconds for a
|
||||
* background revalidation worker thread. If a worker thread is idle
|
||||
* for this long, and there are more than the core number of worker
|
||||
* threads alive, the worker will be reclaimed.
|
||||
* @param secs idle lifetime in seconds
|
||||
*
|
||||
* @deprecated (4.3) use {@link Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setAsynchronousWorkerIdleLifetimeSecs(final int secs) {
|
||||
this.asynchronousWorkerIdleLifetimeSecs = secs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current maximum queue size for background revalidations.
|
||||
*/
|
||||
public int getRevalidationQueueSize() {
|
||||
return revalidationQueueSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current maximum queue size for background revalidations.
|
||||
*
|
||||
* @deprecated (4.3) use {@link Builder}.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setRevalidationQueueSize(final int size) {
|
||||
this.revalidationQueueSize = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CacheConfig clone() throws CloneNotSupportedException {
|
||||
return (CacheConfig) super.clone();
|
||||
}
|
||||
|
||||
public static Builder custom() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static Builder copy(final CacheConfig config) {
|
||||
Args.notNull(config, "Cache config");
|
||||
return new Builder()
|
||||
.setMaxObjectSize(config.getMaxObjectSize())
|
||||
.setMaxCacheEntries(config.getMaxCacheEntries())
|
||||
.setMaxUpdateRetries(config.getMaxUpdateRetries())
|
||||
.setHeuristicCachingEnabled(config.isHeuristicCachingEnabled())
|
||||
.setHeuristicCoefficient(config.getHeuristicCoefficient())
|
||||
.setHeuristicDefaultLifetime(config.getHeuristicDefaultLifetime())
|
||||
.setSharedCache(config.isSharedCache())
|
||||
.setAsynchronousWorkersMax(config.getAsynchronousWorkersMax())
|
||||
.setAsynchronousWorkersCore(config.getAsynchronousWorkersCore())
|
||||
.setAsynchronousWorkerIdleLifetimeSecs(config.getAsynchronousWorkerIdleLifetimeSecs())
|
||||
.setRevalidationQueueSize(config.getRevalidationQueueSize())
|
||||
.setNeverCacheHTTP10ResponsesWithQueryString(config.isNeverCacheHTTP10ResponsesWithQuery());
|
||||
}
|
||||
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private long maxObjectSize;
|
||||
private int maxCacheEntries;
|
||||
private int maxUpdateRetries;
|
||||
private boolean allow303Caching;
|
||||
private boolean weakETagOnPutDeleteAllowed;
|
||||
private boolean heuristicCachingEnabled;
|
||||
private float heuristicCoefficient;
|
||||
private long heuristicDefaultLifetime;
|
||||
private boolean isSharedCache;
|
||||
private int asynchronousWorkersMax;
|
||||
private int asynchronousWorkersCore;
|
||||
private int asynchronousWorkerIdleLifetimeSecs;
|
||||
private int revalidationQueueSize;
|
||||
private boolean neverCacheHTTP10ResponsesWithQuery;
|
||||
|
||||
Builder() {
|
||||
this.maxObjectSize = DEFAULT_MAX_OBJECT_SIZE_BYTES;
|
||||
this.maxCacheEntries = DEFAULT_MAX_CACHE_ENTRIES;
|
||||
this.maxUpdateRetries = DEFAULT_MAX_UPDATE_RETRIES;
|
||||
this.allow303Caching = DEFAULT_303_CACHING_ENABLED;
|
||||
this.weakETagOnPutDeleteAllowed = DEFAULT_WEAK_ETAG_ON_PUTDELETE_ALLOWED;
|
||||
this.heuristicCachingEnabled = false;
|
||||
this.heuristicCoefficient = DEFAULT_HEURISTIC_COEFFICIENT;
|
||||
this.heuristicDefaultLifetime = DEFAULT_HEURISTIC_LIFETIME;
|
||||
this.isSharedCache = true;
|
||||
this.asynchronousWorkersMax = DEFAULT_ASYNCHRONOUS_WORKERS_MAX;
|
||||
this.asynchronousWorkersCore = DEFAULT_ASYNCHRONOUS_WORKERS_CORE;
|
||||
this.asynchronousWorkerIdleLifetimeSecs = DEFAULT_ASYNCHRONOUS_WORKER_IDLE_LIFETIME_SECS;
|
||||
this.revalidationQueueSize = DEFAULT_REVALIDATION_QUEUE_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the maximum response body size that will be eligible for caching.
|
||||
* @param maxObjectSize size in bytes
|
||||
*/
|
||||
public Builder setMaxObjectSize(final long maxObjectSize) {
|
||||
this.maxObjectSize = maxObjectSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of cache entries the cache will retain.
|
||||
*/
|
||||
public Builder setMaxCacheEntries(final int maxCacheEntries) {
|
||||
this.maxCacheEntries = maxCacheEntries;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of times to retry a cache update on failure
|
||||
*/
|
||||
public Builder setMaxUpdateRetries(final int maxUpdateRetries) {
|
||||
this.maxUpdateRetries = maxUpdateRetries;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables 303 caching.
|
||||
* @param allow303Caching should be {@code true} to
|
||||
* permit 303 caching, {@code false} to disable it.
|
||||
*/
|
||||
public Builder setAllow303Caching(final boolean allow303Caching) {
|
||||
this.allow303Caching = allow303Caching;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows or disallows weak etags to be used with PUT/DELETE If-Match requests.
|
||||
* @param weakETagOnPutDeleteAllowed should be {@code true} to
|
||||
* permit weak etags, {@code false} to reject them.
|
||||
*/
|
||||
public Builder setWeakETagOnPutDeleteAllowed(final boolean weakETagOnPutDeleteAllowed) {
|
||||
this.weakETagOnPutDeleteAllowed = weakETagOnPutDeleteAllowed;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables heuristic caching.
|
||||
* @param heuristicCachingEnabled should be {@code true} to
|
||||
* permit heuristic caching, {@code false} to enable it.
|
||||
*/
|
||||
public Builder setHeuristicCachingEnabled(final boolean heuristicCachingEnabled) {
|
||||
this.heuristicCachingEnabled = heuristicCachingEnabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets coefficient to be used in heuristic freshness caching. This is
|
||||
* interpreted as the fraction of the time between the {@code Last-Modified}
|
||||
* and {@code Date} headers of a cached response during which the cached
|
||||
* response will be considered heuristically fresh.
|
||||
* @param heuristicCoefficient should be between {@code 0.0} and
|
||||
* {@code 1.0}.
|
||||
*/
|
||||
public Builder setHeuristicCoefficient(final float heuristicCoefficient) {
|
||||
this.heuristicCoefficient = heuristicCoefficient;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets default lifetime in seconds to be used if heuristic freshness
|
||||
* calculation is not possible. Explicit cache control directives on
|
||||
* either the request or origin response will override this, as will
|
||||
* the heuristic {@code Last-Modified} freshness calculation if it is
|
||||
* available.
|
||||
* @param heuristicDefaultLifetime is the number of seconds to
|
||||
* consider a cache-eligible response fresh in the absence of other
|
||||
* information. Set this to {@code 0} to disable this style of
|
||||
* heuristic caching.
|
||||
*/
|
||||
public Builder setHeuristicDefaultLifetime(final long heuristicDefaultLifetime) {
|
||||
this.heuristicDefaultLifetime = heuristicDefaultLifetime;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the cache should behave as a shared cache or not.
|
||||
* @param isSharedCache true to behave as a shared cache, false to
|
||||
* behave as a non-shared (private) cache. To have the cache
|
||||
* behave like a browser cache, you want to set this to {@code false}.
|
||||
*/
|
||||
public Builder setSharedCache(final boolean isSharedCache) {
|
||||
this.isSharedCache = isSharedCache;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of threads to allow for background
|
||||
* revalidations due to the {@code stale-while-revalidate} directive.
|
||||
* @param asynchronousWorkersMax number of threads; a value of 0 disables background
|
||||
* revalidations.
|
||||
*/
|
||||
public Builder setAsynchronousWorkersMax(final int asynchronousWorkersMax) {
|
||||
this.asynchronousWorkersMax = asynchronousWorkersMax;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum number of threads to keep alive for background
|
||||
* revalidations due to the {@code stale-while-revalidate} directive.
|
||||
* @param asynchronousWorkersCore should be greater than zero and less than or equal
|
||||
* to <code>getAsynchronousWorkersMax()</code>
|
||||
*/
|
||||
public Builder setAsynchronousWorkersCore(final int asynchronousWorkersCore) {
|
||||
this.asynchronousWorkersCore = asynchronousWorkersCore;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current maximum idle lifetime in seconds for a
|
||||
* background revalidation worker thread. If a worker thread is idle
|
||||
* for this long, and there are more than the core number of worker
|
||||
* threads alive, the worker will be reclaimed.
|
||||
* @param asynchronousWorkerIdleLifetimeSecs idle lifetime in seconds
|
||||
*/
|
||||
public Builder setAsynchronousWorkerIdleLifetimeSecs(final int asynchronousWorkerIdleLifetimeSecs) {
|
||||
this.asynchronousWorkerIdleLifetimeSecs = asynchronousWorkerIdleLifetimeSecs;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current maximum queue size for background revalidations.
|
||||
*/
|
||||
public Builder setRevalidationQueueSize(final int revalidationQueueSize) {
|
||||
this.revalidationQueueSize = revalidationQueueSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the cache should never cache HTTP 1.0 responses with a query string or not.
|
||||
* @param neverCacheHTTP10ResponsesWithQuery true to never cache responses with a query
|
||||
* string, false to cache if explicit cache headers are found. Set this to {@code true}
|
||||
* to better emulate IE, which also never caches responses, regardless of what caching
|
||||
* headers may be present.
|
||||
*/
|
||||
public Builder setNeverCacheHTTP10ResponsesWithQueryString(
|
||||
final boolean neverCacheHTTP10ResponsesWithQuery) {
|
||||
this.neverCacheHTTP10ResponsesWithQuery = neverCacheHTTP10ResponsesWithQuery;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CacheConfig build() {
|
||||
return new CacheConfig(
|
||||
maxObjectSize,
|
||||
maxCacheEntries,
|
||||
maxUpdateRetries,
|
||||
allow303Caching,
|
||||
weakETagOnPutDeleteAllowed,
|
||||
heuristicCachingEnabled,
|
||||
heuristicCoefficient,
|
||||
heuristicDefaultLifetime,
|
||||
isSharedCache,
|
||||
asynchronousWorkersMax,
|
||||
asynchronousWorkersCore,
|
||||
asynchronousWorkerIdleLifetimeSecs,
|
||||
revalidationQueueSize,
|
||||
neverCacheHTTP10ResponsesWithQuery);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
builder.append("[maxObjectSize=").append(this.maxObjectSize)
|
||||
.append(", maxCacheEntries=").append(this.maxCacheEntries)
|
||||
.append(", maxUpdateRetries=").append(this.maxUpdateRetries)
|
||||
.append(", 303CachingEnabled=").append(this.allow303Caching)
|
||||
.append(", weakETagOnPutDeleteAllowed=").append(this.weakETagOnPutDeleteAllowed)
|
||||
.append(", heuristicCachingEnabled=").append(this.heuristicCachingEnabled)
|
||||
.append(", heuristicCoefficient=").append(this.heuristicCoefficient)
|
||||
.append(", heuristicDefaultLifetime=").append(this.heuristicDefaultLifetime)
|
||||
.append(", isSharedCache=").append(this.isSharedCache)
|
||||
.append(", asynchronousWorkersMax=").append(this.asynchronousWorkersMax)
|
||||
.append(", asynchronousWorkersCore=").append(this.asynchronousWorkersCore)
|
||||
.append(", asynchronousWorkerIdleLifetimeSecs=").append(this.asynchronousWorkerIdleLifetimeSecs)
|
||||
.append(", revalidationQueueSize=").append(this.revalidationQueueSize)
|
||||
.append(", neverCacheHTTP10ResponsesWithQuery=").append(this.neverCacheHTTP10ResponsesWithQuery)
|
||||
.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.HttpEntity;
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
import ch.boye.httpclientandroidlib.protocol.HTTP;
|
||||
import ch.boye.httpclientandroidlib.util.Args;
|
||||
|
||||
@Immutable
|
||||
class CacheEntity implements HttpEntity, Serializable {
|
||||
|
||||
private static final long serialVersionUID = -3467082284120936233L;
|
||||
|
||||
private final HttpCacheEntry cacheEntry;
|
||||
|
||||
public CacheEntity(final HttpCacheEntry cacheEntry) {
|
||||
super();
|
||||
this.cacheEntry = cacheEntry;
|
||||
}
|
||||
|
||||
public Header getContentType() {
|
||||
return this.cacheEntry.getFirstHeader(HTTP.CONTENT_TYPE);
|
||||
}
|
||||
|
||||
public Header getContentEncoding() {
|
||||
return this.cacheEntry.getFirstHeader(HTTP.CONTENT_ENCODING);
|
||||
}
|
||||
|
||||
public boolean isChunked() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isRepeatable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public long getContentLength() {
|
||||
return this.cacheEntry.getResource().length();
|
||||
}
|
||||
|
||||
public InputStream getContent() throws IOException {
|
||||
return this.cacheEntry.getResource().getInputStream();
|
||||
}
|
||||
|
||||
public void writeTo(final OutputStream outstream) throws IOException {
|
||||
Args.notNull(outstream, "Output stream");
|
||||
final InputStream instream = this.cacheEntry.getResource().getInputStream();
|
||||
try {
|
||||
IOUtils.copy(instream, outstream);
|
||||
} finally {
|
||||
instream.close();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isStreaming() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void consumeContent() throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() throws CloneNotSupportedException {
|
||||
return super.clone();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.HttpStatus;
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HeaderConstants;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
import ch.boye.httpclientandroidlib.client.cache.Resource;
|
||||
import ch.boye.httpclientandroidlib.client.cache.ResourceFactory;
|
||||
import ch.boye.httpclientandroidlib.client.utils.DateUtils;
|
||||
import ch.boye.httpclientandroidlib.protocol.HTTP;
|
||||
import ch.boye.httpclientandroidlib.util.Args;
|
||||
|
||||
/**
|
||||
* Update a {@link HttpCacheEntry} with new or updated information based on the latest
|
||||
* 304 status response from the Server. Use the {@link HttpResponse} to perform
|
||||
* the update.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
@Immutable
|
||||
class CacheEntryUpdater {
|
||||
|
||||
private final ResourceFactory resourceFactory;
|
||||
|
||||
CacheEntryUpdater() {
|
||||
this(new HeapResourceFactory());
|
||||
}
|
||||
|
||||
CacheEntryUpdater(final ResourceFactory resourceFactory) {
|
||||
super();
|
||||
this.resourceFactory = resourceFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the entry with the new information from the response. Should only be used for
|
||||
* 304 responses.
|
||||
*
|
||||
* @param requestId
|
||||
* @param entry The cache Entry to be updated
|
||||
* @param requestDate When the request was performed
|
||||
* @param responseDate When the response was gotten
|
||||
* @param response The HttpResponse from the backend server call
|
||||
* @return HttpCacheEntry an updated version of the cache entry
|
||||
* @throws java.io.IOException if something bad happens while trying to read the body from the original entry
|
||||
*/
|
||||
public HttpCacheEntry updateCacheEntry(
|
||||
final String requestId,
|
||||
final HttpCacheEntry entry,
|
||||
final Date requestDate,
|
||||
final Date responseDate,
|
||||
final HttpResponse response) throws IOException {
|
||||
Args.check(response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED,
|
||||
"Response must have 304 status code");
|
||||
final Header[] mergedHeaders = mergeHeaders(entry, response);
|
||||
Resource resource = null;
|
||||
if (entry.getResource() != null) {
|
||||
resource = resourceFactory.copy(requestId, entry.getResource());
|
||||
}
|
||||
return new HttpCacheEntry(
|
||||
requestDate,
|
||||
responseDate,
|
||||
entry.getStatusLine(),
|
||||
mergedHeaders,
|
||||
resource);
|
||||
}
|
||||
|
||||
protected Header[] mergeHeaders(final HttpCacheEntry entry, final HttpResponse response) {
|
||||
|
||||
if (entryAndResponseHaveDateHeader(entry, response)
|
||||
&& entryDateHeaderNewerThenResponse(entry, response)) {
|
||||
// Don't merge headers, keep the entry's headers as they are newer.
|
||||
return entry.getAllHeaders();
|
||||
}
|
||||
|
||||
final List<Header> cacheEntryHeaderList = new ArrayList<Header>(Arrays.asList(entry
|
||||
.getAllHeaders()));
|
||||
removeCacheHeadersThatMatchResponse(cacheEntryHeaderList, response);
|
||||
removeCacheEntry1xxWarnings(cacheEntryHeaderList, entry);
|
||||
cacheEntryHeaderList.addAll(Arrays.asList(response.getAllHeaders()));
|
||||
|
||||
return cacheEntryHeaderList.toArray(new Header[cacheEntryHeaderList.size()]);
|
||||
}
|
||||
|
||||
private void removeCacheHeadersThatMatchResponse(final List<Header> cacheEntryHeaderList,
|
||||
final HttpResponse response) {
|
||||
for (final Header responseHeader : response.getAllHeaders()) {
|
||||
final ListIterator<Header> cacheEntryHeaderListIter = cacheEntryHeaderList.listIterator();
|
||||
|
||||
while (cacheEntryHeaderListIter.hasNext()) {
|
||||
final String cacheEntryHeaderName = cacheEntryHeaderListIter.next().getName();
|
||||
|
||||
if (cacheEntryHeaderName.equals(responseHeader.getName())) {
|
||||
cacheEntryHeaderListIter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeCacheEntry1xxWarnings(final List<Header> cacheEntryHeaderList, final HttpCacheEntry entry) {
|
||||
final ListIterator<Header> cacheEntryHeaderListIter = cacheEntryHeaderList.listIterator();
|
||||
|
||||
while (cacheEntryHeaderListIter.hasNext()) {
|
||||
final String cacheEntryHeaderName = cacheEntryHeaderListIter.next().getName();
|
||||
|
||||
if (HeaderConstants.WARNING.equals(cacheEntryHeaderName)) {
|
||||
for (final Header cacheEntryWarning : entry.getHeaders(HeaderConstants.WARNING)) {
|
||||
if (cacheEntryWarning.getValue().startsWith("1")) {
|
||||
cacheEntryHeaderListIter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean entryDateHeaderNewerThenResponse(final HttpCacheEntry entry, final HttpResponse response) {
|
||||
final Date entryDate = DateUtils.parseDate(entry.getFirstHeader(HTTP.DATE_HEADER)
|
||||
.getValue());
|
||||
final Date responseDate = DateUtils.parseDate(response.getFirstHeader(HTTP.DATE_HEADER)
|
||||
.getValue());
|
||||
if (entryDate == null || responseDate == null) {
|
||||
return false;
|
||||
}
|
||||
if (!entryDate.after(responseDate)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean entryAndResponseHaveDateHeader(final HttpCacheEntry entry, final HttpResponse response) {
|
||||
if (entry.getFirstHeader(HTTP.DATE_HEADER) != null
|
||||
&& response.getFirstHeader(HTTP.DATE_HEADER) != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,288 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Date;
|
||||
|
||||
import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog;
|
||||
/* LogFactory removed by HttpClient for Android script. */
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.HttpHost;
|
||||
import ch.boye.httpclientandroidlib.HttpRequest;
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HeaderConstants;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheInvalidator;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheStorage;
|
||||
import ch.boye.httpclientandroidlib.client.utils.DateUtils;
|
||||
import ch.boye.httpclientandroidlib.protocol.HTTP;
|
||||
|
||||
/**
|
||||
* Given a particular HttpRequest, flush any cache entries that this request
|
||||
* would invalidate.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
@Immutable
|
||||
class CacheInvalidator implements HttpCacheInvalidator {
|
||||
|
||||
private final HttpCacheStorage storage;
|
||||
private final CacheKeyGenerator cacheKeyGenerator;
|
||||
|
||||
public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass());
|
||||
|
||||
/**
|
||||
* Create a new {@link CacheInvalidator} for a given {@link HttpCache} and
|
||||
* {@link CacheKeyGenerator}.
|
||||
*
|
||||
* @param uriExtractor Provides identifiers for the keys to store cache entries
|
||||
* @param storage the cache to store items away in
|
||||
*/
|
||||
public CacheInvalidator(
|
||||
final CacheKeyGenerator uriExtractor,
|
||||
final HttpCacheStorage storage) {
|
||||
this.cacheKeyGenerator = uriExtractor;
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove cache entries from the cache that are no longer fresh or
|
||||
* have been invalidated in some way.
|
||||
*
|
||||
* @param host The backend host we are talking to
|
||||
* @param req The HttpRequest to that host
|
||||
*/
|
||||
public void flushInvalidatedCacheEntries(final HttpHost host, final HttpRequest req) {
|
||||
if (requestShouldNotBeCached(req)) {
|
||||
log.debug("Request should not be cached");
|
||||
|
||||
final String theUri = cacheKeyGenerator.getURI(host, req);
|
||||
|
||||
final HttpCacheEntry parent = getEntry(theUri);
|
||||
|
||||
log.debug("parent entry: " + parent);
|
||||
|
||||
if (parent != null) {
|
||||
for (final String variantURI : parent.getVariantMap().values()) {
|
||||
flushEntry(variantURI);
|
||||
}
|
||||
flushEntry(theUri);
|
||||
}
|
||||
final URL reqURL = getAbsoluteURL(theUri);
|
||||
if (reqURL == null) {
|
||||
log.error("Couldn't transform request into valid URL");
|
||||
return;
|
||||
}
|
||||
final Header clHdr = req.getFirstHeader("Content-Location");
|
||||
if (clHdr != null) {
|
||||
final String contentLocation = clHdr.getValue();
|
||||
if (!flushAbsoluteUriFromSameHost(reqURL, contentLocation)) {
|
||||
flushRelativeUriFromSameHost(reqURL, contentLocation);
|
||||
}
|
||||
}
|
||||
final Header lHdr = req.getFirstHeader("Location");
|
||||
if (lHdr != null) {
|
||||
flushAbsoluteUriFromSameHost(reqURL, lHdr.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void flushEntry(final String uri) {
|
||||
try {
|
||||
storage.removeEntry(uri);
|
||||
} catch (final IOException ioe) {
|
||||
log.warn("unable to flush cache entry", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private HttpCacheEntry getEntry(final String theUri) {
|
||||
try {
|
||||
return storage.getEntry(theUri);
|
||||
} catch (final IOException ioe) {
|
||||
log.warn("could not retrieve entry from storage", ioe);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void flushUriIfSameHost(final URL requestURL, final URL targetURL) {
|
||||
final URL canonicalTarget = getAbsoluteURL(cacheKeyGenerator.canonicalizeUri(targetURL.toString()));
|
||||
if (canonicalTarget == null) {
|
||||
return;
|
||||
}
|
||||
if (canonicalTarget.getAuthority().equalsIgnoreCase(requestURL.getAuthority())) {
|
||||
flushEntry(canonicalTarget.toString());
|
||||
}
|
||||
}
|
||||
|
||||
protected void flushRelativeUriFromSameHost(final URL reqURL, final String relUri) {
|
||||
final URL relURL = getRelativeURL(reqURL, relUri);
|
||||
if (relURL == null) {
|
||||
return;
|
||||
}
|
||||
flushUriIfSameHost(reqURL, relURL);
|
||||
}
|
||||
|
||||
|
||||
protected boolean flushAbsoluteUriFromSameHost(final URL reqURL, final String uri) {
|
||||
final URL absURL = getAbsoluteURL(uri);
|
||||
if (absURL == null) {
|
||||
return false;
|
||||
}
|
||||
flushUriIfSameHost(reqURL,absURL);
|
||||
return true;
|
||||
}
|
||||
|
||||
private URL getAbsoluteURL(final String uri) {
|
||||
URL absURL = null;
|
||||
try {
|
||||
absURL = new URL(uri);
|
||||
} catch (final MalformedURLException mue) {
|
||||
// nop
|
||||
}
|
||||
return absURL;
|
||||
}
|
||||
|
||||
private URL getRelativeURL(final URL reqURL, final String relUri) {
|
||||
URL relURL = null;
|
||||
try {
|
||||
relURL = new URL(reqURL,relUri);
|
||||
} catch (final MalformedURLException e) {
|
||||
// nop
|
||||
}
|
||||
return relURL;
|
||||
}
|
||||
|
||||
protected boolean requestShouldNotBeCached(final HttpRequest req) {
|
||||
final String method = req.getRequestLine().getMethod();
|
||||
return notGetOrHeadRequest(method);
|
||||
}
|
||||
|
||||
private boolean notGetOrHeadRequest(final String method) {
|
||||
return !(HeaderConstants.GET_METHOD.equals(method) || HeaderConstants.HEAD_METHOD
|
||||
.equals(method));
|
||||
}
|
||||
|
||||
/** Flushes entries that were invalidated by the given response
|
||||
* received for the given host/request pair.
|
||||
*/
|
||||
public void flushInvalidatedCacheEntries(final HttpHost host,
|
||||
final HttpRequest request, final HttpResponse response) {
|
||||
final int status = response.getStatusLine().getStatusCode();
|
||||
if (status < 200 || status > 299) {
|
||||
return;
|
||||
}
|
||||
final URL reqURL = getAbsoluteURL(cacheKeyGenerator.getURI(host, request));
|
||||
if (reqURL == null) {
|
||||
return;
|
||||
}
|
||||
final URL contentLocation = getContentLocationURL(reqURL, response);
|
||||
if (contentLocation != null) {
|
||||
flushLocationCacheEntry(reqURL, response, contentLocation);
|
||||
}
|
||||
final URL location = getLocationURL(reqURL, response);
|
||||
if (location != null) {
|
||||
flushLocationCacheEntry(reqURL, response, location);
|
||||
}
|
||||
}
|
||||
|
||||
private void flushLocationCacheEntry(final URL reqURL,
|
||||
final HttpResponse response, final URL location) {
|
||||
final String cacheKey = cacheKeyGenerator.canonicalizeUri(location.toString());
|
||||
final HttpCacheEntry entry = getEntry(cacheKey);
|
||||
if (entry == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// do not invalidate if response is strictly older than entry
|
||||
// or if the etags match
|
||||
|
||||
if (responseDateOlderThanEntryDate(response, entry)) {
|
||||
return;
|
||||
}
|
||||
if (!responseAndEntryEtagsDiffer(response, entry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
flushUriIfSameHost(reqURL, location);
|
||||
}
|
||||
|
||||
private URL getContentLocationURL(final URL reqURL, final HttpResponse response) {
|
||||
final Header clHeader = response.getFirstHeader("Content-Location");
|
||||
if (clHeader == null) {
|
||||
return null;
|
||||
}
|
||||
final String contentLocation = clHeader.getValue();
|
||||
final URL canonURL = getAbsoluteURL(contentLocation);
|
||||
if (canonURL != null) {
|
||||
return canonURL;
|
||||
}
|
||||
return getRelativeURL(reqURL, contentLocation);
|
||||
}
|
||||
|
||||
private URL getLocationURL(final URL reqURL, final HttpResponse response) {
|
||||
final Header clHeader = response.getFirstHeader("Location");
|
||||
if (clHeader == null) {
|
||||
return null;
|
||||
}
|
||||
final String location = clHeader.getValue();
|
||||
final URL canonURL = getAbsoluteURL(location);
|
||||
if (canonURL != null) {
|
||||
return canonURL;
|
||||
}
|
||||
return getRelativeURL(reqURL, location);
|
||||
}
|
||||
|
||||
private boolean responseAndEntryEtagsDiffer(final HttpResponse response,
|
||||
final HttpCacheEntry entry) {
|
||||
final Header entryEtag = entry.getFirstHeader(HeaderConstants.ETAG);
|
||||
final Header responseEtag = response.getFirstHeader(HeaderConstants.ETAG);
|
||||
if (entryEtag == null || responseEtag == null) {
|
||||
return false;
|
||||
}
|
||||
return (!entryEtag.getValue().equals(responseEtag.getValue()));
|
||||
}
|
||||
|
||||
private boolean responseDateOlderThanEntryDate(final HttpResponse response,
|
||||
final HttpCacheEntry entry) {
|
||||
final Header entryDateHeader = entry.getFirstHeader(HTTP.DATE_HEADER);
|
||||
final Header responseDateHeader = response.getFirstHeader(HTTP.DATE_HEADER);
|
||||
if (entryDateHeader == null || responseDateHeader == null) {
|
||||
/* be conservative; should probably flush */
|
||||
return false;
|
||||
}
|
||||
final Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
|
||||
final Date responseDate = DateUtils.parseDate(responseDateHeader.getValue());
|
||||
if (entryDate == null || responseDate == null) {
|
||||
return false;
|
||||
}
|
||||
return responseDate.before(entryDate);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,178 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import ch.boye.httpclientandroidlib.Consts;
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.HeaderElement;
|
||||
import ch.boye.httpclientandroidlib.HttpHost;
|
||||
import ch.boye.httpclientandroidlib.HttpRequest;
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HeaderConstants;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
import ch.boye.httpclientandroidlib.client.utils.URIUtils;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
@Immutable
|
||||
class CacheKeyGenerator {
|
||||
|
||||
private static final URI BASE_URI = URI.create("http://example.com/");
|
||||
|
||||
/**
|
||||
* For a given {@link HttpHost} and {@link HttpRequest} get a URI from the
|
||||
* pair that I can use as an identifier KEY into my HttpCache
|
||||
*
|
||||
* @param host The host for this request
|
||||
* @param req the {@link HttpRequest}
|
||||
* @return String the extracted URI
|
||||
*/
|
||||
public String getURI(final HttpHost host, final HttpRequest req) {
|
||||
if (isRelativeRequest(req)) {
|
||||
return canonicalizeUri(String.format("%s%s", host.toString(), req.getRequestLine().getUri()));
|
||||
}
|
||||
return canonicalizeUri(req.getRequestLine().getUri());
|
||||
}
|
||||
|
||||
public String canonicalizeUri(final String uri) {
|
||||
try {
|
||||
final URI normalized = URIUtils.resolve(BASE_URI, uri);
|
||||
final URL u = new URL(normalized.toASCIIString());
|
||||
final String protocol = u.getProtocol();
|
||||
final String hostname = u.getHost();
|
||||
final int port = canonicalizePort(u.getPort(), protocol);
|
||||
final String path = u.getPath();
|
||||
final String query = u.getQuery();
|
||||
final String file = (query != null) ? (path + "?" + query) : path;
|
||||
final URL out = new URL(protocol, hostname, port, file);
|
||||
return out.toString();
|
||||
} catch (final IllegalArgumentException e) {
|
||||
return uri;
|
||||
} catch (final MalformedURLException e) {
|
||||
return uri;
|
||||
}
|
||||
}
|
||||
|
||||
private int canonicalizePort(final int port, final String protocol) {
|
||||
if (port == -1 && "http".equalsIgnoreCase(protocol)) {
|
||||
return 80;
|
||||
} else if (port == -1 && "https".equalsIgnoreCase(protocol)) {
|
||||
return 443;
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
private boolean isRelativeRequest(final HttpRequest req) {
|
||||
final String requestUri = req.getRequestLine().getUri();
|
||||
return ("*".equals(requestUri) || requestUri.startsWith("/"));
|
||||
}
|
||||
|
||||
protected String getFullHeaderValue(final Header[] headers) {
|
||||
if (headers == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
final StringBuilder buf = new StringBuilder("");
|
||||
boolean first = true;
|
||||
for (final Header hdr : headers) {
|
||||
if (!first) {
|
||||
buf.append(", ");
|
||||
}
|
||||
buf.append(hdr.getValue().trim());
|
||||
first = false;
|
||||
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given {@link HttpHost} and {@link HttpRequest} if the request has a
|
||||
* VARY header - I need to get an additional URI from the pair of host and
|
||||
* request so that I can also store the variant into my HttpCache.
|
||||
*
|
||||
* @param host The host for this request
|
||||
* @param req the {@link HttpRequest}
|
||||
* @param entry the parent entry used to track the variants
|
||||
* @return String the extracted variant URI
|
||||
*/
|
||||
public String getVariantURI(final HttpHost host, final HttpRequest req, final HttpCacheEntry entry) {
|
||||
if (!entry.hasVariants()) {
|
||||
return getURI(host, req);
|
||||
}
|
||||
return getVariantKey(req, entry) + getURI(host, req);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a "variant key" from the headers of a given request that are
|
||||
* covered by the Vary header of a given cache entry. Any request whose
|
||||
* varying headers match those of this request should have the same
|
||||
* variant key.
|
||||
* @param req originating request
|
||||
* @param entry cache entry in question that has variants
|
||||
* @return a <code>String</code> variant key
|
||||
*/
|
||||
public String getVariantKey(final HttpRequest req, final HttpCacheEntry entry) {
|
||||
final List<String> variantHeaderNames = new ArrayList<String>();
|
||||
for (final Header varyHdr : entry.getHeaders(HeaderConstants.VARY)) {
|
||||
for (final HeaderElement elt : varyHdr.getElements()) {
|
||||
variantHeaderNames.add(elt.getName());
|
||||
}
|
||||
}
|
||||
Collections.sort(variantHeaderNames);
|
||||
|
||||
StringBuilder buf;
|
||||
try {
|
||||
buf = new StringBuilder("{");
|
||||
boolean first = true;
|
||||
for (final String headerName : variantHeaderNames) {
|
||||
if (!first) {
|
||||
buf.append("&");
|
||||
}
|
||||
buf.append(URLEncoder.encode(headerName, Consts.UTF_8.name()));
|
||||
buf.append("=");
|
||||
buf.append(URLEncoder.encode(getFullHeaderValue(req.getHeaders(headerName)),
|
||||
Consts.UTF_8.name()));
|
||||
first = false;
|
||||
}
|
||||
buf.append("}");
|
||||
} catch (final UnsupportedEncodingException uee) {
|
||||
throw new RuntimeException("couldn't encode to UTF-8", uee);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
|
||||
final class CacheMap extends LinkedHashMap<String, HttpCacheEntry> {
|
||||
|
||||
private static final long serialVersionUID = -7750025207539768511L;
|
||||
|
||||
private final int maxEntries;
|
||||
|
||||
CacheMap(final int maxEntries) {
|
||||
super(20, 0.75f, true);
|
||||
this.maxEntries = maxEntries;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(final Map.Entry<String, HttpCacheEntry> eldest) {
|
||||
return size() > this.maxEntries;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,320 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.HeaderElement;
|
||||
import ch.boye.httpclientandroidlib.HttpRequest;
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HeaderConstants;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
import ch.boye.httpclientandroidlib.client.utils.DateUtils;
|
||||
import ch.boye.httpclientandroidlib.protocol.HTTP;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
@Immutable
|
||||
class CacheValidityPolicy {
|
||||
|
||||
public static final long MAX_AGE = 2147483648L;
|
||||
|
||||
CacheValidityPolicy() {
|
||||
super();
|
||||
}
|
||||
|
||||
public long getCurrentAgeSecs(final HttpCacheEntry entry, final Date now) {
|
||||
return getCorrectedInitialAgeSecs(entry) + getResidentTimeSecs(entry, now);
|
||||
}
|
||||
|
||||
public long getFreshnessLifetimeSecs(final HttpCacheEntry entry) {
|
||||
final long maxage = getMaxAge(entry);
|
||||
if (maxage > -1) {
|
||||
return maxage;
|
||||
}
|
||||
|
||||
final Date dateValue = entry.getDate();
|
||||
if (dateValue == null) {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
final Date expiry = getExpirationDate(entry);
|
||||
if (expiry == null) {
|
||||
return 0;
|
||||
}
|
||||
final long diff = expiry.getTime() - dateValue.getTime();
|
||||
return (diff / 1000);
|
||||
}
|
||||
|
||||
public boolean isResponseFresh(final HttpCacheEntry entry, final Date now) {
|
||||
return (getCurrentAgeSecs(entry, now) < getFreshnessLifetimeSecs(entry));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides if this response is fresh enough based Last-Modified and Date, if available.
|
||||
* This entry is meant to be used when isResponseFresh returns false. The algorithm is as follows:
|
||||
*
|
||||
* if last-modified and date are defined, freshness lifetime is coefficient*(date-lastModified),
|
||||
* else freshness lifetime is defaultLifetime
|
||||
*
|
||||
* @param entry the cache entry
|
||||
* @param now what time is it currently (When is right NOW)
|
||||
* @param coefficient Part of the heuristic for cache entry freshness
|
||||
* @param defaultLifetime How long can I assume a cache entry is default TTL
|
||||
* @return {@code true} if the response is fresh
|
||||
*/
|
||||
public boolean isResponseHeuristicallyFresh(final HttpCacheEntry entry,
|
||||
final Date now, final float coefficient, final long defaultLifetime) {
|
||||
return (getCurrentAgeSecs(entry, now) < getHeuristicFreshnessLifetimeSecs(entry, coefficient, defaultLifetime));
|
||||
}
|
||||
|
||||
public long getHeuristicFreshnessLifetimeSecs(final HttpCacheEntry entry,
|
||||
final float coefficient, final long defaultLifetime) {
|
||||
final Date dateValue = entry.getDate();
|
||||
final Date lastModifiedValue = getLastModifiedValue(entry);
|
||||
|
||||
if (dateValue != null && lastModifiedValue != null) {
|
||||
final long diff = dateValue.getTime() - lastModifiedValue.getTime();
|
||||
if (diff < 0) {
|
||||
return 0;
|
||||
}
|
||||
return (long)(coefficient * (diff / 1000));
|
||||
}
|
||||
|
||||
return defaultLifetime;
|
||||
}
|
||||
|
||||
public boolean isRevalidatable(final HttpCacheEntry entry) {
|
||||
return entry.getFirstHeader(HeaderConstants.ETAG) != null
|
||||
|| entry.getFirstHeader(HeaderConstants.LAST_MODIFIED) != null;
|
||||
}
|
||||
|
||||
public boolean mustRevalidate(final HttpCacheEntry entry) {
|
||||
return hasCacheControlDirective(entry, HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE);
|
||||
}
|
||||
|
||||
public boolean proxyRevalidate(final HttpCacheEntry entry) {
|
||||
return hasCacheControlDirective(entry, HeaderConstants.CACHE_CONTROL_PROXY_REVALIDATE);
|
||||
}
|
||||
|
||||
public boolean mayReturnStaleWhileRevalidating(final HttpCacheEntry entry, final Date now) {
|
||||
for (final Header h : entry.getHeaders(HeaderConstants.CACHE_CONTROL)) {
|
||||
for(final HeaderElement elt : h.getElements()) {
|
||||
if (HeaderConstants.STALE_WHILE_REVALIDATE.equalsIgnoreCase(elt.getName())) {
|
||||
try {
|
||||
final int allowedStalenessLifetime = Integer.parseInt(elt.getValue());
|
||||
if (getStalenessSecs(entry, now) <= allowedStalenessLifetime) {
|
||||
return true;
|
||||
}
|
||||
} catch (final NumberFormatException nfe) {
|
||||
// skip malformed directive
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean mayReturnStaleIfError(final HttpRequest request,
|
||||
final HttpCacheEntry entry, final Date now) {
|
||||
final long stalenessSecs = getStalenessSecs(entry, now);
|
||||
return mayReturnStaleIfError(request.getHeaders(HeaderConstants.CACHE_CONTROL),
|
||||
stalenessSecs)
|
||||
|| mayReturnStaleIfError(entry.getHeaders(HeaderConstants.CACHE_CONTROL),
|
||||
stalenessSecs);
|
||||
}
|
||||
|
||||
private boolean mayReturnStaleIfError(final Header[] headers, final long stalenessSecs) {
|
||||
boolean result = false;
|
||||
for(final Header h : headers) {
|
||||
for(final HeaderElement elt : h.getElements()) {
|
||||
if (HeaderConstants.STALE_IF_ERROR.equals(elt.getName())) {
|
||||
try {
|
||||
final int staleIfErrorSecs = Integer.parseInt(elt.getValue());
|
||||
if (stalenessSecs <= staleIfErrorSecs) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
} catch (final NumberFormatException nfe) {
|
||||
// skip malformed directive
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated (4.3) use {@link HttpCacheEntry#getDate()}.
|
||||
* @param entry
|
||||
* @return the Date of the entry
|
||||
*/
|
||||
@Deprecated
|
||||
protected Date getDateValue(final HttpCacheEntry entry) {
|
||||
return entry.getDate();
|
||||
}
|
||||
|
||||
protected Date getLastModifiedValue(final HttpCacheEntry entry) {
|
||||
final Header dateHdr = entry.getFirstHeader(HeaderConstants.LAST_MODIFIED);
|
||||
if (dateHdr == null) {
|
||||
return null;
|
||||
}
|
||||
return DateUtils.parseDate(dateHdr.getValue());
|
||||
}
|
||||
|
||||
protected long getContentLengthValue(final HttpCacheEntry entry) {
|
||||
final Header cl = entry.getFirstHeader(HTTP.CONTENT_LEN);
|
||||
if (cl == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
return Long.parseLong(cl.getValue());
|
||||
} catch (final NumberFormatException ex) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean hasContentLengthHeader(final HttpCacheEntry entry) {
|
||||
return null != entry.getFirstHeader(HTTP.CONTENT_LEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* This matters for deciding whether the cache entry is valid to serve as a
|
||||
* response. If these values do not match, we might have a partial response
|
||||
*
|
||||
* @param entry The cache entry we are currently working with
|
||||
* @return boolean indicating whether actual length matches Content-Length
|
||||
*/
|
||||
protected boolean contentLengthHeaderMatchesActualLength(final HttpCacheEntry entry) {
|
||||
return !hasContentLengthHeader(entry) || getContentLengthValue(entry) == entry.getResource().length();
|
||||
}
|
||||
|
||||
protected long getApparentAgeSecs(final HttpCacheEntry entry) {
|
||||
final Date dateValue = entry.getDate();
|
||||
if (dateValue == null) {
|
||||
return MAX_AGE;
|
||||
}
|
||||
final long diff = entry.getResponseDate().getTime() - dateValue.getTime();
|
||||
if (diff < 0L) {
|
||||
return 0;
|
||||
}
|
||||
return (diff / 1000);
|
||||
}
|
||||
|
||||
protected long getAgeValue(final HttpCacheEntry entry) {
|
||||
long ageValue = 0;
|
||||
for (final Header hdr : entry.getHeaders(HeaderConstants.AGE)) {
|
||||
long hdrAge;
|
||||
try {
|
||||
hdrAge = Long.parseLong(hdr.getValue());
|
||||
if (hdrAge < 0) {
|
||||
hdrAge = MAX_AGE;
|
||||
}
|
||||
} catch (final NumberFormatException nfe) {
|
||||
hdrAge = MAX_AGE;
|
||||
}
|
||||
ageValue = (hdrAge > ageValue) ? hdrAge : ageValue;
|
||||
}
|
||||
return ageValue;
|
||||
}
|
||||
|
||||
protected long getCorrectedReceivedAgeSecs(final HttpCacheEntry entry) {
|
||||
final long apparentAge = getApparentAgeSecs(entry);
|
||||
final long ageValue = getAgeValue(entry);
|
||||
return (apparentAge > ageValue) ? apparentAge : ageValue;
|
||||
}
|
||||
|
||||
protected long getResponseDelaySecs(final HttpCacheEntry entry) {
|
||||
final long diff = entry.getResponseDate().getTime() - entry.getRequestDate().getTime();
|
||||
return (diff / 1000L);
|
||||
}
|
||||
|
||||
protected long getCorrectedInitialAgeSecs(final HttpCacheEntry entry) {
|
||||
return getCorrectedReceivedAgeSecs(entry) + getResponseDelaySecs(entry);
|
||||
}
|
||||
|
||||
protected long getResidentTimeSecs(final HttpCacheEntry entry, final Date now) {
|
||||
final long diff = now.getTime() - entry.getResponseDate().getTime();
|
||||
return (diff / 1000L);
|
||||
}
|
||||
|
||||
protected long getMaxAge(final HttpCacheEntry entry) {
|
||||
long maxage = -1;
|
||||
for (final Header hdr : entry.getHeaders(HeaderConstants.CACHE_CONTROL)) {
|
||||
for (final HeaderElement elt : hdr.getElements()) {
|
||||
if (HeaderConstants.CACHE_CONTROL_MAX_AGE.equals(elt.getName())
|
||||
|| "s-maxage".equals(elt.getName())) {
|
||||
try {
|
||||
final long currMaxAge = Long.parseLong(elt.getValue());
|
||||
if (maxage == -1 || currMaxAge < maxage) {
|
||||
maxage = currMaxAge;
|
||||
}
|
||||
} catch (final NumberFormatException nfe) {
|
||||
// be conservative if can't parse
|
||||
maxage = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxage;
|
||||
}
|
||||
|
||||
protected Date getExpirationDate(final HttpCacheEntry entry) {
|
||||
final Header expiresHeader = entry.getFirstHeader(HeaderConstants.EXPIRES);
|
||||
if (expiresHeader == null) {
|
||||
return null;
|
||||
}
|
||||
return DateUtils.parseDate(expiresHeader.getValue());
|
||||
}
|
||||
|
||||
public boolean hasCacheControlDirective(final HttpCacheEntry entry,
|
||||
final String directive) {
|
||||
for (final Header h : entry.getHeaders(HeaderConstants.CACHE_CONTROL)) {
|
||||
for(final HeaderElement elt : h.getElements()) {
|
||||
if (directive.equalsIgnoreCase(elt.getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public long getStalenessSecs(final HttpCacheEntry entry, final Date now) {
|
||||
final long age = getCurrentAgeSecs(entry, now);
|
||||
final long freshness = getFreshnessLifetimeSecs(entry);
|
||||
if (age <= freshness) {
|
||||
return 0L;
|
||||
}
|
||||
return (age - freshness);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog;
|
||||
/* LogFactory removed by HttpClient for Android script. */
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.HeaderElement;
|
||||
import ch.boye.httpclientandroidlib.HttpRequest;
|
||||
import ch.boye.httpclientandroidlib.HttpVersion;
|
||||
import ch.boye.httpclientandroidlib.ProtocolVersion;
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HeaderConstants;
|
||||
|
||||
/**
|
||||
* Determines if an HttpRequest is allowed to be served from the cache.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
@Immutable
|
||||
class CacheableRequestPolicy {
|
||||
|
||||
public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass());
|
||||
|
||||
/**
|
||||
* Determines if an HttpRequest can be served from the cache.
|
||||
*
|
||||
* @param request
|
||||
* an HttpRequest
|
||||
* @return boolean Is it possible to serve this request from cache
|
||||
*/
|
||||
public boolean isServableFromCache(final HttpRequest request) {
|
||||
final String method = request.getRequestLine().getMethod();
|
||||
|
||||
final ProtocolVersion pv = request.getRequestLine().getProtocolVersion();
|
||||
if (HttpVersion.HTTP_1_1.compareToVersion(pv) != 0) {
|
||||
log.trace("non-HTTP/1.1 request was not serveable from cache");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!method.equals(HeaderConstants.GET_METHOD)) {
|
||||
log.trace("non-GET request was not serveable from cache");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.getHeaders(HeaderConstants.PRAGMA).length > 0) {
|
||||
log.trace("request with Pragma header was not serveable from cache");
|
||||
return false;
|
||||
}
|
||||
|
||||
final Header[] cacheControlHeaders = request.getHeaders(HeaderConstants.CACHE_CONTROL);
|
||||
for (final Header cacheControl : cacheControlHeaders) {
|
||||
for (final HeaderElement cacheControlElement : cacheControl.getElements()) {
|
||||
if (HeaderConstants.CACHE_CONTROL_NO_STORE.equalsIgnoreCase(cacheControlElement
|
||||
.getName())) {
|
||||
log.trace("Request with no-store was not serveable from cache");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equalsIgnoreCase(cacheControlElement
|
||||
.getName())) {
|
||||
log.trace("Request with no-cache was not serveable from cache");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.trace("Request was serveable from cache");
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.HttpEntity;
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.HttpStatus;
|
||||
import ch.boye.httpclientandroidlib.HttpVersion;
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HeaderConstants;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse;
|
||||
import ch.boye.httpclientandroidlib.client.utils.DateUtils;
|
||||
import ch.boye.httpclientandroidlib.message.BasicHeader;
|
||||
import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
|
||||
import ch.boye.httpclientandroidlib.protocol.HTTP;
|
||||
|
||||
/**
|
||||
* Rebuilds an {@link HttpResponse} from a {@link net.sf.ehcache.CacheEntry}
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
@Immutable
|
||||
class CachedHttpResponseGenerator {
|
||||
|
||||
private final CacheValidityPolicy validityStrategy;
|
||||
|
||||
CachedHttpResponseGenerator(final CacheValidityPolicy validityStrategy) {
|
||||
super();
|
||||
this.validityStrategy = validityStrategy;
|
||||
}
|
||||
|
||||
CachedHttpResponseGenerator() {
|
||||
this(new CacheValidityPolicy());
|
||||
}
|
||||
|
||||
/**
|
||||
* If I was able to use a {@link CacheEntity} to response to the {@link ch.boye.httpclientandroidlib.HttpRequest} then
|
||||
* generate an {@link HttpResponse} based on the cache entry.
|
||||
* @param entry
|
||||
* {@link CacheEntity} to transform into an {@link HttpResponse}
|
||||
* @return {@link HttpResponse} that was constructed
|
||||
*/
|
||||
CloseableHttpResponse generateResponse(final HttpCacheEntry entry) {
|
||||
|
||||
final Date now = new Date();
|
||||
final HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, entry
|
||||
.getStatusCode(), entry.getReasonPhrase());
|
||||
|
||||
response.setHeaders(entry.getAllHeaders());
|
||||
|
||||
if (entry.getResource() != null) {
|
||||
final HttpEntity entity = new CacheEntity(entry);
|
||||
addMissingContentLengthHeader(response, entity);
|
||||
response.setEntity(entity);
|
||||
}
|
||||
|
||||
final long age = this.validityStrategy.getCurrentAgeSecs(entry, now);
|
||||
if (age > 0) {
|
||||
if (age >= Integer.MAX_VALUE) {
|
||||
response.setHeader(HeaderConstants.AGE, "2147483648");
|
||||
} else {
|
||||
response.setHeader(HeaderConstants.AGE, "" + ((int) age));
|
||||
}
|
||||
}
|
||||
|
||||
return Proxies.enhanceResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a 304 - Not Modified response from a {@link CacheEntity}. This should be
|
||||
* used to respond to conditional requests, when the entry exists or has been re-validated.
|
||||
*/
|
||||
CloseableHttpResponse generateNotModifiedResponse(final HttpCacheEntry entry) {
|
||||
|
||||
final HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
|
||||
HttpStatus.SC_NOT_MODIFIED, "Not Modified");
|
||||
|
||||
// The response MUST include the following headers
|
||||
// (http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
|
||||
|
||||
// - Date, unless its omission is required by section 14.8.1
|
||||
Header dateHeader = entry.getFirstHeader(HTTP.DATE_HEADER);
|
||||
if (dateHeader == null) {
|
||||
dateHeader = new BasicHeader(HTTP.DATE_HEADER, DateUtils.formatDate(new Date()));
|
||||
}
|
||||
response.addHeader(dateHeader);
|
||||
|
||||
// - ETag and/or Content-Location, if the header would have been sent
|
||||
// in a 200 response to the same request
|
||||
final Header etagHeader = entry.getFirstHeader(HeaderConstants.ETAG);
|
||||
if (etagHeader != null) {
|
||||
response.addHeader(etagHeader);
|
||||
}
|
||||
|
||||
final Header contentLocationHeader = entry.getFirstHeader("Content-Location");
|
||||
if (contentLocationHeader != null) {
|
||||
response.addHeader(contentLocationHeader);
|
||||
}
|
||||
|
||||
// - Expires, Cache-Control, and/or Vary, if the field-value might
|
||||
// differ from that sent in any previous response for the same
|
||||
// variant
|
||||
final Header expiresHeader = entry.getFirstHeader(HeaderConstants.EXPIRES);
|
||||
if (expiresHeader != null) {
|
||||
response.addHeader(expiresHeader);
|
||||
}
|
||||
|
||||
final Header cacheControlHeader = entry.getFirstHeader(HeaderConstants.CACHE_CONTROL);
|
||||
if (cacheControlHeader != null) {
|
||||
response.addHeader(cacheControlHeader);
|
||||
}
|
||||
|
||||
final Header varyHeader = entry.getFirstHeader(HeaderConstants.VARY);
|
||||
if (varyHeader != null) {
|
||||
response.addHeader(varyHeader);
|
||||
}
|
||||
|
||||
return Proxies.enhanceResponse(response);
|
||||
}
|
||||
|
||||
private void addMissingContentLengthHeader(final HttpResponse response, final HttpEntity entity) {
|
||||
if (transferEncodingIsPresent(response)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Header contentLength = response.getFirstHeader(HTTP.CONTENT_LEN);
|
||||
if (contentLength == null) {
|
||||
contentLength = new BasicHeader(HTTP.CONTENT_LEN, Long.toString(entity
|
||||
.getContentLength()));
|
||||
response.setHeader(contentLength);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean transferEncodingIsPresent(final HttpResponse response) {
|
||||
final Header hdr = response.getFirstHeader(HTTP.TRANSFER_ENCODING);
|
||||
return hdr != null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,346 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog;
|
||||
/* LogFactory removed by HttpClient for Android script. */
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.HeaderElement;
|
||||
import ch.boye.httpclientandroidlib.HttpHost;
|
||||
import ch.boye.httpclientandroidlib.HttpRequest;
|
||||
import ch.boye.httpclientandroidlib.HttpStatus;
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HeaderConstants;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
import ch.boye.httpclientandroidlib.client.utils.DateUtils;
|
||||
|
||||
/**
|
||||
* Determines whether a given {@link HttpCacheEntry} is suitable to be
|
||||
* used as a response for a given {@link HttpRequest}.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
@Immutable
|
||||
class CachedResponseSuitabilityChecker {
|
||||
|
||||
public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass());
|
||||
|
||||
private final boolean sharedCache;
|
||||
private final boolean useHeuristicCaching;
|
||||
private final float heuristicCoefficient;
|
||||
private final long heuristicDefaultLifetime;
|
||||
private final CacheValidityPolicy validityStrategy;
|
||||
|
||||
CachedResponseSuitabilityChecker(final CacheValidityPolicy validityStrategy,
|
||||
final CacheConfig config) {
|
||||
super();
|
||||
this.validityStrategy = validityStrategy;
|
||||
this.sharedCache = config.isSharedCache();
|
||||
this.useHeuristicCaching = config.isHeuristicCachingEnabled();
|
||||
this.heuristicCoefficient = config.getHeuristicCoefficient();
|
||||
this.heuristicDefaultLifetime = config.getHeuristicDefaultLifetime();
|
||||
}
|
||||
|
||||
CachedResponseSuitabilityChecker(final CacheConfig config) {
|
||||
this(new CacheValidityPolicy(), config);
|
||||
}
|
||||
|
||||
private boolean isFreshEnough(final HttpCacheEntry entry, final HttpRequest request, final Date now) {
|
||||
if (validityStrategy.isResponseFresh(entry, now)) {
|
||||
return true;
|
||||
}
|
||||
if (useHeuristicCaching &&
|
||||
validityStrategy.isResponseHeuristicallyFresh(entry, now, heuristicCoefficient, heuristicDefaultLifetime)) {
|
||||
return true;
|
||||
}
|
||||
if (originInsistsOnFreshness(entry)) {
|
||||
return false;
|
||||
}
|
||||
final long maxstale = getMaxStale(request);
|
||||
if (maxstale == -1) {
|
||||
return false;
|
||||
}
|
||||
return (maxstale > validityStrategy.getStalenessSecs(entry, now));
|
||||
}
|
||||
|
||||
private boolean originInsistsOnFreshness(final HttpCacheEntry entry) {
|
||||
if (validityStrategy.mustRevalidate(entry)) {
|
||||
return true;
|
||||
}
|
||||
if (!sharedCache) {
|
||||
return false;
|
||||
}
|
||||
return validityStrategy.proxyRevalidate(entry) ||
|
||||
validityStrategy.hasCacheControlDirective(entry, "s-maxage");
|
||||
}
|
||||
|
||||
private long getMaxStale(final HttpRequest request) {
|
||||
long maxstale = -1;
|
||||
for(final Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
|
||||
for(final HeaderElement elt : h.getElements()) {
|
||||
if (HeaderConstants.CACHE_CONTROL_MAX_STALE.equals(elt.getName())) {
|
||||
if ((elt.getValue() == null || "".equals(elt.getValue().trim()))
|
||||
&& maxstale == -1) {
|
||||
maxstale = Long.MAX_VALUE;
|
||||
} else {
|
||||
try {
|
||||
long val = Long.parseLong(elt.getValue());
|
||||
if (val < 0) {
|
||||
val = 0;
|
||||
}
|
||||
if (maxstale == -1 || val < maxstale) {
|
||||
maxstale = val;
|
||||
}
|
||||
} catch (final NumberFormatException nfe) {
|
||||
// err on the side of preserving semantic transparency
|
||||
maxstale = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxstale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if I can utilize a {@link HttpCacheEntry} to respond to the given
|
||||
* {@link HttpRequest}
|
||||
*
|
||||
* @param host
|
||||
* {@link HttpHost}
|
||||
* @param request
|
||||
* {@link HttpRequest}
|
||||
* @param entry
|
||||
* {@link HttpCacheEntry}
|
||||
* @param now
|
||||
* Right now in time
|
||||
* @return boolean yes/no answer
|
||||
*/
|
||||
public boolean canCachedResponseBeUsed(final HttpHost host, final HttpRequest request, final HttpCacheEntry entry, final Date now) {
|
||||
|
||||
if (!isFreshEnough(entry, request, now)) {
|
||||
log.trace("Cache entry was not fresh enough");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!validityStrategy.contentLengthHeaderMatchesActualLength(entry)) {
|
||||
log.debug("Cache entry Content-Length and header information do not match");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasUnsupportedConditionalHeaders(request)) {
|
||||
log.debug("Request contained conditional headers we don't handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isConditional(request) && entry.getStatusCode() == HttpStatus.SC_NOT_MODIFIED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isConditional(request) && !allConditionalsMatch(request, entry, now)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (final Header ccHdr : request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
|
||||
for (final HeaderElement elt : ccHdr.getElements()) {
|
||||
if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elt.getName())) {
|
||||
log.trace("Response contained NO CACHE directive, cache was not suitable");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HeaderConstants.CACHE_CONTROL_NO_STORE.equals(elt.getName())) {
|
||||
log.trace("Response contained NO STORE directive, cache was not suitable");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HeaderConstants.CACHE_CONTROL_MAX_AGE.equals(elt.getName())) {
|
||||
try {
|
||||
final int maxage = Integer.parseInt(elt.getValue());
|
||||
if (validityStrategy.getCurrentAgeSecs(entry, now) > maxage) {
|
||||
log.trace("Response from cache was NOT suitable due to max age");
|
||||
return false;
|
||||
}
|
||||
} catch (final NumberFormatException ex) {
|
||||
// err conservatively
|
||||
log.debug("Response from cache was malformed" + ex.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (HeaderConstants.CACHE_CONTROL_MAX_STALE.equals(elt.getName())) {
|
||||
try {
|
||||
final int maxstale = Integer.parseInt(elt.getValue());
|
||||
if (validityStrategy.getFreshnessLifetimeSecs(entry) > maxstale) {
|
||||
log.trace("Response from cache was not suitable due to Max stale freshness");
|
||||
return false;
|
||||
}
|
||||
} catch (final NumberFormatException ex) {
|
||||
// err conservatively
|
||||
log.debug("Response from cache was malformed: " + ex.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (HeaderConstants.CACHE_CONTROL_MIN_FRESH.equals(elt.getName())) {
|
||||
try {
|
||||
final long minfresh = Long.parseLong(elt.getValue());
|
||||
if (minfresh < 0L) {
|
||||
return false;
|
||||
}
|
||||
final long age = validityStrategy.getCurrentAgeSecs(entry, now);
|
||||
final long freshness = validityStrategy.getFreshnessLifetimeSecs(entry);
|
||||
if (freshness - age < minfresh) {
|
||||
log.trace("Response from cache was not suitable due to min fresh " +
|
||||
"freshness requirement");
|
||||
return false;
|
||||
}
|
||||
} catch (final NumberFormatException ex) {
|
||||
// err conservatively
|
||||
log.debug("Response from cache was malformed: " + ex.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.trace("Response from cache was suitable");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this request the type of conditional request we support?
|
||||
* @param request The current httpRequest being made
|
||||
* @return {@code true} if the request is supported
|
||||
*/
|
||||
public boolean isConditional(final HttpRequest request) {
|
||||
return hasSupportedEtagValidator(request) || hasSupportedLastModifiedValidator(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that conditionals that are part of this request match
|
||||
* @param request The current httpRequest being made
|
||||
* @param entry the cache entry
|
||||
* @param now right NOW in time
|
||||
* @return {@code true} if the request matches all conditionals
|
||||
*/
|
||||
public boolean allConditionalsMatch(final HttpRequest request, final HttpCacheEntry entry, final Date now) {
|
||||
final boolean hasEtagValidator = hasSupportedEtagValidator(request);
|
||||
final boolean hasLastModifiedValidator = hasSupportedLastModifiedValidator(request);
|
||||
|
||||
final boolean etagValidatorMatches = (hasEtagValidator) && etagValidatorMatches(request, entry);
|
||||
final boolean lastModifiedValidatorMatches = (hasLastModifiedValidator) && lastModifiedValidatorMatches(request, entry, now);
|
||||
|
||||
if ((hasEtagValidator && hasLastModifiedValidator)
|
||||
&& !(etagValidatorMatches && lastModifiedValidatorMatches)) {
|
||||
return false;
|
||||
} else if (hasEtagValidator && !etagValidatorMatches) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasLastModifiedValidator && !lastModifiedValidatorMatches) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean hasUnsupportedConditionalHeaders(final HttpRequest request) {
|
||||
return (request.getFirstHeader(HeaderConstants.IF_RANGE) != null
|
||||
|| request.getFirstHeader(HeaderConstants.IF_MATCH) != null
|
||||
|| hasValidDateField(request, HeaderConstants.IF_UNMODIFIED_SINCE));
|
||||
}
|
||||
|
||||
private boolean hasSupportedEtagValidator(final HttpRequest request) {
|
||||
return request.containsHeader(HeaderConstants.IF_NONE_MATCH);
|
||||
}
|
||||
|
||||
private boolean hasSupportedLastModifiedValidator(final HttpRequest request) {
|
||||
return hasValidDateField(request, HeaderConstants.IF_MODIFIED_SINCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check entry against If-None-Match
|
||||
* @param request The current httpRequest being made
|
||||
* @param entry the cache entry
|
||||
* @return boolean does the etag validator match
|
||||
*/
|
||||
private boolean etagValidatorMatches(final HttpRequest request, final HttpCacheEntry entry) {
|
||||
final Header etagHeader = entry.getFirstHeader(HeaderConstants.ETAG);
|
||||
final String etag = (etagHeader != null) ? etagHeader.getValue() : null;
|
||||
final Header[] ifNoneMatch = request.getHeaders(HeaderConstants.IF_NONE_MATCH);
|
||||
if (ifNoneMatch != null) {
|
||||
for (final Header h : ifNoneMatch) {
|
||||
for (final HeaderElement elt : h.getElements()) {
|
||||
final String reqEtag = elt.toString();
|
||||
if (("*".equals(reqEtag) && etag != null)
|
||||
|| reqEtag.equals(etag)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check entry against If-Modified-Since, if If-Modified-Since is in the future it is invalid as per
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
||||
* @param request The current httpRequest being made
|
||||
* @param entry the cache entry
|
||||
* @param now right NOW in time
|
||||
* @return boolean Does the last modified header match
|
||||
*/
|
||||
private boolean lastModifiedValidatorMatches(final HttpRequest request, final HttpCacheEntry entry, final Date now) {
|
||||
final Header lastModifiedHeader = entry.getFirstHeader(HeaderConstants.LAST_MODIFIED);
|
||||
Date lastModified = null;
|
||||
if (lastModifiedHeader != null) {
|
||||
lastModified = DateUtils.parseDate(lastModifiedHeader.getValue());
|
||||
}
|
||||
if (lastModified == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (final Header h : request.getHeaders(HeaderConstants.IF_MODIFIED_SINCE)) {
|
||||
final Date ifModifiedSince = DateUtils.parseDate(h.getValue());
|
||||
if (ifModifiedSince != null) {
|
||||
if (ifModifiedSince.after(now) || lastModified.after(ifModifiedSince)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean hasValidDateField(final HttpRequest request, final String headerName) {
|
||||
for(final Header h : request.getHeaders(headerName)) {
|
||||
final Date date = DateUtils.parseDate(h.getValue());
|
||||
return date != null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,870 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog;
|
||||
/* LogFactory removed by HttpClient for Android script. */
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.HeaderElement;
|
||||
import ch.boye.httpclientandroidlib.HttpException;
|
||||
import ch.boye.httpclientandroidlib.HttpHost;
|
||||
import ch.boye.httpclientandroidlib.HttpMessage;
|
||||
import ch.boye.httpclientandroidlib.HttpRequest;
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.HttpStatus;
|
||||
import ch.boye.httpclientandroidlib.HttpVersion;
|
||||
import ch.boye.httpclientandroidlib.ProtocolVersion;
|
||||
import ch.boye.httpclientandroidlib.RequestLine;
|
||||
import ch.boye.httpclientandroidlib.annotation.ThreadSafe;
|
||||
import ch.boye.httpclientandroidlib.client.cache.CacheResponseStatus;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HeaderConstants;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheContext;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheStorage;
|
||||
import ch.boye.httpclientandroidlib.client.cache.ResourceFactory;
|
||||
import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse;
|
||||
import ch.boye.httpclientandroidlib.client.methods.HttpExecutionAware;
|
||||
import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper;
|
||||
import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext;
|
||||
import ch.boye.httpclientandroidlib.client.utils.DateUtils;
|
||||
import ch.boye.httpclientandroidlib.conn.routing.HttpRoute;
|
||||
import ch.boye.httpclientandroidlib.impl.execchain.ClientExecChain;
|
||||
import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
|
||||
import ch.boye.httpclientandroidlib.protocol.HTTP;
|
||||
import ch.boye.httpclientandroidlib.protocol.HttpContext;
|
||||
import ch.boye.httpclientandroidlib.util.Args;
|
||||
import ch.boye.httpclientandroidlib.util.VersionInfo;
|
||||
|
||||
/**
|
||||
* Request executor in the request execution chain that is responsible for
|
||||
* transparent client-side caching. The current implementation is conditionally
|
||||
* compliant with HTTP/1.1 (meaning all the MUST and MUST NOTs are obeyed),
|
||||
* although quite a lot, though not all, of the SHOULDs and SHOULD NOTs
|
||||
* are obeyed too.
|
||||
* <p/>
|
||||
* Folks that would like to experiment with alternative storage backends
|
||||
* should look at the {@link HttpCacheStorage} interface and the related
|
||||
* package documentation there. You may also be interested in the provided
|
||||
* {@link ch.boye.httpclientandroidlib.impl.client.cache.ehcache.EhcacheHttpCacheStorage
|
||||
* EhCache} and {@link
|
||||
* ch.boye.httpclientandroidlib.impl.client.cache.memcached.MemcachedHttpCacheStorage
|
||||
* memcached} storage backends.
|
||||
* <p/>
|
||||
* Further responsibilities such as communication with the opposite
|
||||
* endpoint is delegated to the next executor in the request execution
|
||||
* chain.
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
@ThreadSafe // So long as the responseCache implementation is threadsafe
|
||||
public class CachingExec implements ClientExecChain {
|
||||
|
||||
private final static boolean SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS = false;
|
||||
|
||||
private final AtomicLong cacheHits = new AtomicLong();
|
||||
private final AtomicLong cacheMisses = new AtomicLong();
|
||||
private final AtomicLong cacheUpdates = new AtomicLong();
|
||||
|
||||
private final Map<ProtocolVersion, String> viaHeaders = new HashMap<ProtocolVersion, String>(4);
|
||||
|
||||
private final CacheConfig cacheConfig;
|
||||
private final ClientExecChain backend;
|
||||
private final HttpCache responseCache;
|
||||
private final CacheValidityPolicy validityPolicy;
|
||||
private final CachedHttpResponseGenerator responseGenerator;
|
||||
private final CacheableRequestPolicy cacheableRequestPolicy;
|
||||
private final CachedResponseSuitabilityChecker suitabilityChecker;
|
||||
private final ConditionalRequestBuilder conditionalRequestBuilder;
|
||||
private final ResponseProtocolCompliance responseCompliance;
|
||||
private final RequestProtocolCompliance requestCompliance;
|
||||
private final ResponseCachingPolicy responseCachingPolicy;
|
||||
|
||||
private final AsynchronousValidator asynchRevalidator;
|
||||
|
||||
public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass());
|
||||
|
||||
public CachingExec(
|
||||
final ClientExecChain backend,
|
||||
final HttpCache cache,
|
||||
final CacheConfig config) {
|
||||
this(backend, cache, config, null);
|
||||
}
|
||||
|
||||
public CachingExec(
|
||||
final ClientExecChain backend,
|
||||
final HttpCache cache,
|
||||
final CacheConfig config,
|
||||
final AsynchronousValidator asynchRevalidator) {
|
||||
super();
|
||||
Args.notNull(backend, "HTTP backend");
|
||||
Args.notNull(cache, "HttpCache");
|
||||
this.cacheConfig = config != null ? config : CacheConfig.DEFAULT;
|
||||
this.backend = backend;
|
||||
this.responseCache = cache;
|
||||
this.validityPolicy = new CacheValidityPolicy();
|
||||
this.responseGenerator = new CachedHttpResponseGenerator(this.validityPolicy);
|
||||
this.cacheableRequestPolicy = new CacheableRequestPolicy();
|
||||
this.suitabilityChecker = new CachedResponseSuitabilityChecker(this.validityPolicy, this.cacheConfig);
|
||||
this.conditionalRequestBuilder = new ConditionalRequestBuilder();
|
||||
this.responseCompliance = new ResponseProtocolCompliance();
|
||||
this.requestCompliance = new RequestProtocolCompliance(this.cacheConfig.isWeakETagOnPutDeleteAllowed());
|
||||
this.responseCachingPolicy = new ResponseCachingPolicy(
|
||||
this.cacheConfig.getMaxObjectSize(), this.cacheConfig.isSharedCache(),
|
||||
this.cacheConfig.isNeverCacheHTTP10ResponsesWithQuery(), this.cacheConfig.is303CachingEnabled());
|
||||
this.asynchRevalidator = asynchRevalidator;
|
||||
}
|
||||
|
||||
public CachingExec(
|
||||
final ClientExecChain backend,
|
||||
final ResourceFactory resourceFactory,
|
||||
final HttpCacheStorage storage,
|
||||
final CacheConfig config) {
|
||||
this(backend, new BasicHttpCache(resourceFactory, storage, config), config);
|
||||
}
|
||||
|
||||
public CachingExec(final ClientExecChain backend) {
|
||||
this(backend, new BasicHttpCache(), CacheConfig.DEFAULT);
|
||||
}
|
||||
|
||||
CachingExec(
|
||||
final ClientExecChain backend,
|
||||
final HttpCache responseCache,
|
||||
final CacheValidityPolicy validityPolicy,
|
||||
final ResponseCachingPolicy responseCachingPolicy,
|
||||
final CachedHttpResponseGenerator responseGenerator,
|
||||
final CacheableRequestPolicy cacheableRequestPolicy,
|
||||
final CachedResponseSuitabilityChecker suitabilityChecker,
|
||||
final ConditionalRequestBuilder conditionalRequestBuilder,
|
||||
final ResponseProtocolCompliance responseCompliance,
|
||||
final RequestProtocolCompliance requestCompliance,
|
||||
final CacheConfig config,
|
||||
final AsynchronousValidator asynchRevalidator) {
|
||||
this.cacheConfig = config != null ? config : CacheConfig.DEFAULT;
|
||||
this.backend = backend;
|
||||
this.responseCache = responseCache;
|
||||
this.validityPolicy = validityPolicy;
|
||||
this.responseCachingPolicy = responseCachingPolicy;
|
||||
this.responseGenerator = responseGenerator;
|
||||
this.cacheableRequestPolicy = cacheableRequestPolicy;
|
||||
this.suitabilityChecker = suitabilityChecker;
|
||||
this.conditionalRequestBuilder = conditionalRequestBuilder;
|
||||
this.responseCompliance = responseCompliance;
|
||||
this.requestCompliance = requestCompliance;
|
||||
this.asynchRevalidator = asynchRevalidator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the number of times that the cache successfully responded
|
||||
* to an {@link HttpRequest} without contacting the origin server.
|
||||
* @return the number of cache hits
|
||||
*/
|
||||
public long getCacheHits() {
|
||||
return cacheHits.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the number of times that the cache contacted the origin
|
||||
* server because it had no appropriate response cached.
|
||||
* @return the number of cache misses
|
||||
*/
|
||||
public long getCacheMisses() {
|
||||
return cacheMisses.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports the number of times that the cache was able to satisfy
|
||||
* a response by revalidating an existing but stale cache entry.
|
||||
* @return the number of cache revalidations
|
||||
*/
|
||||
public long getCacheUpdates() {
|
||||
return cacheUpdates.get();
|
||||
}
|
||||
|
||||
public CloseableHttpResponse execute(
|
||||
final HttpRoute route,
|
||||
final HttpRequestWrapper request) throws IOException, HttpException {
|
||||
return execute(route, request, HttpClientContext.create(), null);
|
||||
}
|
||||
|
||||
public CloseableHttpResponse execute(
|
||||
final HttpRoute route,
|
||||
final HttpRequestWrapper request,
|
||||
final HttpClientContext context) throws IOException, HttpException {
|
||||
return execute(route, request, context, null);
|
||||
}
|
||||
|
||||
public CloseableHttpResponse execute(
|
||||
final HttpRoute route,
|
||||
final HttpRequestWrapper request,
|
||||
final HttpClientContext context,
|
||||
final HttpExecutionAware execAware) throws IOException, HttpException {
|
||||
|
||||
final HttpHost target = context.getTargetHost();
|
||||
final String via = generateViaHeader(request.getOriginal());
|
||||
|
||||
// default response context
|
||||
setResponseStatus(context, CacheResponseStatus.CACHE_MISS);
|
||||
|
||||
if (clientRequestsOurOptions(request)) {
|
||||
setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
|
||||
return Proxies.enhanceResponse(new OptionsHttp11Response());
|
||||
}
|
||||
|
||||
final HttpResponse fatalErrorResponse = getFatallyNoncompliantResponse(request, context);
|
||||
if (fatalErrorResponse != null) {
|
||||
return Proxies.enhanceResponse(fatalErrorResponse);
|
||||
}
|
||||
|
||||
requestCompliance.makeRequestCompliant(request);
|
||||
request.addHeader("Via",via);
|
||||
|
||||
flushEntriesInvalidatedByRequest(context.getTargetHost(), request);
|
||||
|
||||
if (!cacheableRequestPolicy.isServableFromCache(request)) {
|
||||
log.debug("Request is not servable from cache");
|
||||
return callBackend(route, request, context, execAware);
|
||||
}
|
||||
|
||||
final HttpCacheEntry entry = satisfyFromCache(target, request);
|
||||
if (entry == null) {
|
||||
log.debug("Cache miss");
|
||||
return handleCacheMiss(route, request, context, execAware);
|
||||
} else {
|
||||
return handleCacheHit(route, request, context, execAware, entry);
|
||||
}
|
||||
}
|
||||
|
||||
private CloseableHttpResponse handleCacheHit(
|
||||
final HttpRoute route,
|
||||
final HttpRequestWrapper request,
|
||||
final HttpClientContext context,
|
||||
final HttpExecutionAware execAware,
|
||||
final HttpCacheEntry entry) throws IOException, HttpException {
|
||||
final HttpHost target = context.getTargetHost();
|
||||
recordCacheHit(target, request);
|
||||
CloseableHttpResponse out = null;
|
||||
final Date now = getCurrentDate();
|
||||
if (suitabilityChecker.canCachedResponseBeUsed(target, request, entry, now)) {
|
||||
log.debug("Cache hit");
|
||||
out = generateCachedResponse(request, context, entry, now);
|
||||
} else if (!mayCallBackend(request)) {
|
||||
log.debug("Cache entry not suitable but only-if-cached requested");
|
||||
out = generateGatewayTimeout(context);
|
||||
} else if (!(entry.getStatusCode() == HttpStatus.SC_NOT_MODIFIED
|
||||
&& !suitabilityChecker.isConditional(request))) {
|
||||
log.debug("Revalidating cache entry");
|
||||
return revalidateCacheEntry(route, request, context, execAware, entry, now);
|
||||
} else {
|
||||
log.debug("Cache entry not usable; calling backend");
|
||||
return callBackend(route, request, context, execAware);
|
||||
}
|
||||
context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
|
||||
context.setAttribute(HttpClientContext.HTTP_TARGET_HOST, target);
|
||||
context.setAttribute(HttpClientContext.HTTP_REQUEST, request);
|
||||
context.setAttribute(HttpClientContext.HTTP_RESPONSE, out);
|
||||
context.setAttribute(HttpClientContext.HTTP_REQ_SENT, Boolean.TRUE);
|
||||
return out;
|
||||
}
|
||||
|
||||
private CloseableHttpResponse revalidateCacheEntry(
|
||||
final HttpRoute route,
|
||||
final HttpRequestWrapper request,
|
||||
final HttpClientContext context,
|
||||
final HttpExecutionAware execAware,
|
||||
final HttpCacheEntry entry,
|
||||
final Date now) throws HttpException {
|
||||
|
||||
try {
|
||||
if (asynchRevalidator != null
|
||||
&& !staleResponseNotAllowed(request, entry, now)
|
||||
&& validityPolicy.mayReturnStaleWhileRevalidating(entry, now)) {
|
||||
log.trace("Serving stale with asynchronous revalidation");
|
||||
final CloseableHttpResponse resp = generateCachedResponse(request, context, entry, now);
|
||||
asynchRevalidator.revalidateCacheEntry(this, route, request, context, execAware, entry);
|
||||
return resp;
|
||||
}
|
||||
return revalidateCacheEntry(route, request, context, execAware, entry);
|
||||
} catch (final IOException ioex) {
|
||||
return handleRevalidationFailure(request, context, entry, now);
|
||||
}
|
||||
}
|
||||
|
||||
private CloseableHttpResponse handleCacheMiss(
|
||||
final HttpRoute route,
|
||||
final HttpRequestWrapper request,
|
||||
final HttpClientContext context,
|
||||
final HttpExecutionAware execAware) throws IOException, HttpException {
|
||||
final HttpHost target = context.getTargetHost();
|
||||
recordCacheMiss(target, request);
|
||||
|
||||
if (!mayCallBackend(request)) {
|
||||
return Proxies.enhanceResponse(
|
||||
new BasicHttpResponse(
|
||||
HttpVersion.HTTP_1_1, HttpStatus.SC_GATEWAY_TIMEOUT, "Gateway Timeout"));
|
||||
}
|
||||
|
||||
final Map<String, Variant> variants = getExistingCacheVariants(target, request);
|
||||
if (variants != null && variants.size() > 0) {
|
||||
return negotiateResponseFromVariants(route, request, context,
|
||||
execAware, variants);
|
||||
}
|
||||
|
||||
return callBackend(route, request, context, execAware);
|
||||
}
|
||||
|
||||
private HttpCacheEntry satisfyFromCache(
|
||||
final HttpHost target, final HttpRequestWrapper request) {
|
||||
HttpCacheEntry entry = null;
|
||||
try {
|
||||
entry = responseCache.getCacheEntry(target, request);
|
||||
} catch (final IOException ioe) {
|
||||
log.warn("Unable to retrieve entries from cache", ioe);
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
private HttpResponse getFatallyNoncompliantResponse(
|
||||
final HttpRequestWrapper request,
|
||||
final HttpContext context) {
|
||||
HttpResponse fatalErrorResponse = null;
|
||||
final List<RequestProtocolError> fatalError = requestCompliance.requestIsFatallyNonCompliant(request);
|
||||
|
||||
for (final RequestProtocolError error : fatalError) {
|
||||
setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
|
||||
fatalErrorResponse = requestCompliance.getErrorForRequest(error);
|
||||
}
|
||||
return fatalErrorResponse;
|
||||
}
|
||||
|
||||
private Map<String, Variant> getExistingCacheVariants(
|
||||
final HttpHost target,
|
||||
final HttpRequestWrapper request) {
|
||||
Map<String,Variant> variants = null;
|
||||
try {
|
||||
variants = responseCache.getVariantCacheEntriesWithEtags(target, request);
|
||||
} catch (final IOException ioe) {
|
||||
log.warn("Unable to retrieve variant entries from cache", ioe);
|
||||
}
|
||||
return variants;
|
||||
}
|
||||
|
||||
private void recordCacheMiss(final HttpHost target, final HttpRequestWrapper request) {
|
||||
cacheMisses.getAndIncrement();
|
||||
if (log.isTraceEnabled()) {
|
||||
final RequestLine rl = request.getRequestLine();
|
||||
log.trace("Cache miss [host: " + target + "; uri: " + rl.getUri() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private void recordCacheHit(final HttpHost target, final HttpRequestWrapper request) {
|
||||
cacheHits.getAndIncrement();
|
||||
if (log.isTraceEnabled()) {
|
||||
final RequestLine rl = request.getRequestLine();
|
||||
log.trace("Cache hit [host: " + target + "; uri: " + rl.getUri() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private void recordCacheUpdate(final HttpContext context) {
|
||||
cacheUpdates.getAndIncrement();
|
||||
setResponseStatus(context, CacheResponseStatus.VALIDATED);
|
||||
}
|
||||
|
||||
private void flushEntriesInvalidatedByRequest(
|
||||
final HttpHost target,
|
||||
final HttpRequestWrapper request) {
|
||||
try {
|
||||
responseCache.flushInvalidatedCacheEntriesFor(target, request);
|
||||
} catch (final IOException ioe) {
|
||||
log.warn("Unable to flush invalidated entries from cache", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private CloseableHttpResponse generateCachedResponse(final HttpRequestWrapper request,
|
||||
final HttpContext context, final HttpCacheEntry entry, final Date now) {
|
||||
final CloseableHttpResponse cachedResponse;
|
||||
if (request.containsHeader(HeaderConstants.IF_NONE_MATCH)
|
||||
|| request.containsHeader(HeaderConstants.IF_MODIFIED_SINCE)) {
|
||||
cachedResponse = responseGenerator.generateNotModifiedResponse(entry);
|
||||
} else {
|
||||
cachedResponse = responseGenerator.generateResponse(entry);
|
||||
}
|
||||
setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
|
||||
if (validityPolicy.getStalenessSecs(entry, now) > 0L) {
|
||||
cachedResponse.addHeader(HeaderConstants.WARNING,"110 localhost \"Response is stale\"");
|
||||
}
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
private CloseableHttpResponse handleRevalidationFailure(
|
||||
final HttpRequestWrapper request,
|
||||
final HttpContext context,
|
||||
final HttpCacheEntry entry,
|
||||
final Date now) {
|
||||
if (staleResponseNotAllowed(request, entry, now)) {
|
||||
return generateGatewayTimeout(context);
|
||||
} else {
|
||||
return unvalidatedCacheHit(context, entry);
|
||||
}
|
||||
}
|
||||
|
||||
private CloseableHttpResponse generateGatewayTimeout(
|
||||
final HttpContext context) {
|
||||
setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
|
||||
return Proxies.enhanceResponse(new BasicHttpResponse(
|
||||
HttpVersion.HTTP_1_1, HttpStatus.SC_GATEWAY_TIMEOUT,
|
||||
"Gateway Timeout"));
|
||||
}
|
||||
|
||||
private CloseableHttpResponse unvalidatedCacheHit(
|
||||
final HttpContext context, final HttpCacheEntry entry) {
|
||||
final CloseableHttpResponse cachedResponse = responseGenerator.generateResponse(entry);
|
||||
setResponseStatus(context, CacheResponseStatus.CACHE_HIT);
|
||||
cachedResponse.addHeader(HeaderConstants.WARNING, "111 localhost \"Revalidation failed\"");
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
private boolean staleResponseNotAllowed(
|
||||
final HttpRequestWrapper request,
|
||||
final HttpCacheEntry entry,
|
||||
final Date now) {
|
||||
return validityPolicy.mustRevalidate(entry)
|
||||
|| (cacheConfig.isSharedCache() && validityPolicy.proxyRevalidate(entry))
|
||||
|| explicitFreshnessRequest(request, entry, now);
|
||||
}
|
||||
|
||||
private boolean mayCallBackend(final HttpRequestWrapper request) {
|
||||
for (final Header h: request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
|
||||
for (final HeaderElement elt : h.getElements()) {
|
||||
if ("only-if-cached".equals(elt.getName())) {
|
||||
log.trace("Request marked only-if-cached");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean explicitFreshnessRequest(
|
||||
final HttpRequestWrapper request,
|
||||
final HttpCacheEntry entry,
|
||||
final Date now) {
|
||||
for(final Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
|
||||
for(final HeaderElement elt : h.getElements()) {
|
||||
if (HeaderConstants.CACHE_CONTROL_MAX_STALE.equals(elt.getName())) {
|
||||
try {
|
||||
final int maxstale = Integer.parseInt(elt.getValue());
|
||||
final long age = validityPolicy.getCurrentAgeSecs(entry, now);
|
||||
final long lifetime = validityPolicy.getFreshnessLifetimeSecs(entry);
|
||||
if (age - lifetime > maxstale) {
|
||||
return true;
|
||||
}
|
||||
} catch (final NumberFormatException nfe) {
|
||||
return true;
|
||||
}
|
||||
} else if (HeaderConstants.CACHE_CONTROL_MIN_FRESH.equals(elt.getName())
|
||||
|| HeaderConstants.CACHE_CONTROL_MAX_AGE.equals(elt.getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String generateViaHeader(final HttpMessage msg) {
|
||||
|
||||
final ProtocolVersion pv = msg.getProtocolVersion();
|
||||
final String existingEntry = viaHeaders.get(pv);
|
||||
if (existingEntry != null) {
|
||||
return existingEntry;
|
||||
}
|
||||
|
||||
final VersionInfo vi = VersionInfo.loadVersionInfo("ch.boye.httpclientandroidlib.client", getClass().getClassLoader());
|
||||
final String release = (vi != null) ? vi.getRelease() : VersionInfo.UNAVAILABLE;
|
||||
|
||||
String value;
|
||||
if ("http".equalsIgnoreCase(pv.getProtocol())) {
|
||||
value = String.format("%d.%d localhost (Apache-HttpClient/%s (cache))", pv.getMajor(), pv.getMinor(),
|
||||
release);
|
||||
} else {
|
||||
value = String.format("%s/%d.%d localhost (Apache-HttpClient/%s (cache))", pv.getProtocol(), pv.getMajor(),
|
||||
pv.getMinor(), release);
|
||||
}
|
||||
viaHeaders.put(pv, value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private void setResponseStatus(final HttpContext context, final CacheResponseStatus value) {
|
||||
if (context != null) {
|
||||
context.setAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports whether this {@code CachingHttpClient} implementation
|
||||
* supports byte-range requests as specified by the {@code Range}
|
||||
* and {@code Content-Range} headers.
|
||||
* @return {@code true} if byte-range requests are supported
|
||||
*/
|
||||
public boolean supportsRangeAndContentRangeHeaders() {
|
||||
return SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS;
|
||||
}
|
||||
|
||||
Date getCurrentDate() {
|
||||
return new Date();
|
||||
}
|
||||
|
||||
boolean clientRequestsOurOptions(final HttpRequest request) {
|
||||
final RequestLine line = request.getRequestLine();
|
||||
|
||||
if (!HeaderConstants.OPTIONS_METHOD.equals(line.getMethod())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!"*".equals(line.getUri())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!"0".equals(request.getFirstHeader(HeaderConstants.MAX_FORWARDS).getValue())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CloseableHttpResponse callBackend(
|
||||
final HttpRoute route,
|
||||
final HttpRequestWrapper request,
|
||||
final HttpClientContext context,
|
||||
final HttpExecutionAware execAware) throws IOException, HttpException {
|
||||
|
||||
final Date requestDate = getCurrentDate();
|
||||
|
||||
log.trace("Calling the backend");
|
||||
final CloseableHttpResponse backendResponse = backend.execute(route, request, context, execAware);
|
||||
try {
|
||||
backendResponse.addHeader("Via", generateViaHeader(backendResponse));
|
||||
return handleBackendResponse(route, request, context, execAware,
|
||||
requestDate, getCurrentDate(), backendResponse);
|
||||
} catch (final IOException ex) {
|
||||
backendResponse.close();
|
||||
throw ex;
|
||||
} catch (final RuntimeException ex) {
|
||||
backendResponse.close();
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean revalidationResponseIsTooOld(final HttpResponse backendResponse,
|
||||
final HttpCacheEntry cacheEntry) {
|
||||
final Header entryDateHeader = cacheEntry.getFirstHeader(HTTP.DATE_HEADER);
|
||||
final Header responseDateHeader = backendResponse.getFirstHeader(HTTP.DATE_HEADER);
|
||||
if (entryDateHeader != null && responseDateHeader != null) {
|
||||
final Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
|
||||
final Date respDate = DateUtils.parseDate(responseDateHeader.getValue());
|
||||
if (entryDate == null || respDate == null) {
|
||||
// either backend response or cached entry did not have a valid
|
||||
// Date header, so we can't tell if they are out of order
|
||||
// according to the origin clock; thus we can skip the
|
||||
// unconditional retry recommended in 13.2.6 of RFC 2616.
|
||||
return false;
|
||||
}
|
||||
if (respDate.before(entryDate)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CloseableHttpResponse negotiateResponseFromVariants(
|
||||
final HttpRoute route,
|
||||
final HttpRequestWrapper request,
|
||||
final HttpClientContext context,
|
||||
final HttpExecutionAware execAware,
|
||||
final Map<String, Variant> variants) throws IOException, HttpException {
|
||||
final HttpRequestWrapper conditionalRequest = conditionalRequestBuilder
|
||||
.buildConditionalRequestFromVariants(request, variants);
|
||||
|
||||
final Date requestDate = getCurrentDate();
|
||||
final CloseableHttpResponse backendResponse = backend.execute(
|
||||
route, conditionalRequest, context, execAware);
|
||||
try {
|
||||
final Date responseDate = getCurrentDate();
|
||||
|
||||
backendResponse.addHeader("Via", generateViaHeader(backendResponse));
|
||||
|
||||
if (backendResponse.getStatusLine().getStatusCode() != HttpStatus.SC_NOT_MODIFIED) {
|
||||
return handleBackendResponse(
|
||||
route, request, context, execAware,
|
||||
requestDate, responseDate, backendResponse);
|
||||
}
|
||||
|
||||
final Header resultEtagHeader = backendResponse.getFirstHeader(HeaderConstants.ETAG);
|
||||
if (resultEtagHeader == null) {
|
||||
log.warn("304 response did not contain ETag");
|
||||
IOUtils.consume(backendResponse.getEntity());
|
||||
backendResponse.close();
|
||||
return callBackend(route, request, context, execAware);
|
||||
}
|
||||
|
||||
final String resultEtag = resultEtagHeader.getValue();
|
||||
final Variant matchingVariant = variants.get(resultEtag);
|
||||
if (matchingVariant == null) {
|
||||
log.debug("304 response did not contain ETag matching one sent in If-None-Match");
|
||||
IOUtils.consume(backendResponse.getEntity());
|
||||
backendResponse.close();
|
||||
return callBackend(route, request, context, execAware);
|
||||
}
|
||||
|
||||
final HttpCacheEntry matchedEntry = matchingVariant.getEntry();
|
||||
|
||||
if (revalidationResponseIsTooOld(backendResponse, matchedEntry)) {
|
||||
IOUtils.consume(backendResponse.getEntity());
|
||||
backendResponse.close();
|
||||
return retryRequestUnconditionally(route, request, context, execAware, matchedEntry);
|
||||
}
|
||||
|
||||
recordCacheUpdate(context);
|
||||
|
||||
final HttpCacheEntry responseEntry = getUpdatedVariantEntry(
|
||||
context.getTargetHost(), conditionalRequest, requestDate, responseDate,
|
||||
backendResponse, matchingVariant, matchedEntry);
|
||||
backendResponse.close();
|
||||
|
||||
final CloseableHttpResponse resp = responseGenerator.generateResponse(responseEntry);
|
||||
tryToUpdateVariantMap(context.getTargetHost(), request, matchingVariant);
|
||||
|
||||
if (shouldSendNotModifiedResponse(request, responseEntry)) {
|
||||
return responseGenerator.generateNotModifiedResponse(responseEntry);
|
||||
}
|
||||
return resp;
|
||||
} catch (final IOException ex) {
|
||||
backendResponse.close();
|
||||
throw ex;
|
||||
} catch (final RuntimeException ex) {
|
||||
backendResponse.close();
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
private CloseableHttpResponse retryRequestUnconditionally(
|
||||
final HttpRoute route,
|
||||
final HttpRequestWrapper request,
|
||||
final HttpClientContext context,
|
||||
final HttpExecutionAware execAware,
|
||||
final HttpCacheEntry matchedEntry) throws IOException, HttpException {
|
||||
final HttpRequestWrapper unconditional = conditionalRequestBuilder
|
||||
.buildUnconditionalRequest(request, matchedEntry);
|
||||
return callBackend(route, unconditional, context, execAware);
|
||||
}
|
||||
|
||||
private HttpCacheEntry getUpdatedVariantEntry(
|
||||
final HttpHost target,
|
||||
final HttpRequestWrapper conditionalRequest,
|
||||
final Date requestDate,
|
||||
final Date responseDate,
|
||||
final CloseableHttpResponse backendResponse,
|
||||
final Variant matchingVariant,
|
||||
final HttpCacheEntry matchedEntry) throws IOException {
|
||||
HttpCacheEntry responseEntry = matchedEntry;
|
||||
try {
|
||||
responseEntry = responseCache.updateVariantCacheEntry(target, conditionalRequest,
|
||||
matchedEntry, backendResponse, requestDate, responseDate, matchingVariant.getCacheKey());
|
||||
} catch (final IOException ioe) {
|
||||
log.warn("Could not update cache entry", ioe);
|
||||
} finally {
|
||||
backendResponse.close();
|
||||
}
|
||||
return responseEntry;
|
||||
}
|
||||
|
||||
private void tryToUpdateVariantMap(
|
||||
final HttpHost target,
|
||||
final HttpRequestWrapper request,
|
||||
final Variant matchingVariant) {
|
||||
try {
|
||||
responseCache.reuseVariantEntryFor(target, request, matchingVariant);
|
||||
} catch (final IOException ioe) {
|
||||
log.warn("Could not update cache entry to reuse variant", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldSendNotModifiedResponse(
|
||||
final HttpRequestWrapper request,
|
||||
final HttpCacheEntry responseEntry) {
|
||||
return (suitabilityChecker.isConditional(request)
|
||||
&& suitabilityChecker.allConditionalsMatch(request, responseEntry, new Date()));
|
||||
}
|
||||
|
||||
CloseableHttpResponse revalidateCacheEntry(
|
||||
final HttpRoute route,
|
||||
final HttpRequestWrapper request,
|
||||
final HttpClientContext context,
|
||||
final HttpExecutionAware execAware,
|
||||
final HttpCacheEntry cacheEntry) throws IOException, HttpException {
|
||||
|
||||
final HttpRequestWrapper conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(request, cacheEntry);
|
||||
|
||||
Date requestDate = getCurrentDate();
|
||||
CloseableHttpResponse backendResponse = backend.execute(
|
||||
route, conditionalRequest, context, execAware);
|
||||
Date responseDate = getCurrentDate();
|
||||
|
||||
if (revalidationResponseIsTooOld(backendResponse, cacheEntry)) {
|
||||
backendResponse.close();
|
||||
final HttpRequestWrapper unconditional = conditionalRequestBuilder
|
||||
.buildUnconditionalRequest(request, cacheEntry);
|
||||
requestDate = getCurrentDate();
|
||||
backendResponse = backend.execute(route, unconditional, context, execAware);
|
||||
responseDate = getCurrentDate();
|
||||
}
|
||||
|
||||
backendResponse.addHeader(HeaderConstants.VIA, generateViaHeader(backendResponse));
|
||||
|
||||
final int statusCode = backendResponse.getStatusLine().getStatusCode();
|
||||
if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
|
||||
recordCacheUpdate(context);
|
||||
}
|
||||
|
||||
if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
|
||||
final HttpCacheEntry updatedEntry = responseCache.updateCacheEntry(
|
||||
context.getTargetHost(), request, cacheEntry,
|
||||
backendResponse, requestDate, responseDate);
|
||||
if (suitabilityChecker.isConditional(request)
|
||||
&& suitabilityChecker.allConditionalsMatch(request, updatedEntry, new Date())) {
|
||||
return responseGenerator
|
||||
.generateNotModifiedResponse(updatedEntry);
|
||||
}
|
||||
return responseGenerator.generateResponse(updatedEntry);
|
||||
}
|
||||
|
||||
if (staleIfErrorAppliesTo(statusCode)
|
||||
&& !staleResponseNotAllowed(request, cacheEntry, getCurrentDate())
|
||||
&& validityPolicy.mayReturnStaleIfError(request, cacheEntry, responseDate)) {
|
||||
try {
|
||||
final CloseableHttpResponse cachedResponse = responseGenerator.generateResponse(cacheEntry);
|
||||
cachedResponse.addHeader(HeaderConstants.WARNING, "110 localhost \"Response is stale\"");
|
||||
return cachedResponse;
|
||||
} finally {
|
||||
backendResponse.close();
|
||||
}
|
||||
}
|
||||
return handleBackendResponse(
|
||||
route, conditionalRequest, context, execAware,
|
||||
requestDate, responseDate, backendResponse);
|
||||
}
|
||||
|
||||
private boolean staleIfErrorAppliesTo(final int statusCode) {
|
||||
return statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR
|
||||
|| statusCode == HttpStatus.SC_BAD_GATEWAY
|
||||
|| statusCode == HttpStatus.SC_SERVICE_UNAVAILABLE
|
||||
|| statusCode == HttpStatus.SC_GATEWAY_TIMEOUT;
|
||||
}
|
||||
|
||||
CloseableHttpResponse handleBackendResponse(
|
||||
final HttpRoute route,
|
||||
final HttpRequestWrapper request,
|
||||
final HttpClientContext context,
|
||||
final HttpExecutionAware execAware,
|
||||
final Date requestDate,
|
||||
final Date responseDate,
|
||||
final CloseableHttpResponse backendResponse) throws IOException {
|
||||
|
||||
log.trace("Handling Backend response");
|
||||
responseCompliance.ensureProtocolCompliance(request, backendResponse);
|
||||
|
||||
final HttpHost target = context.getTargetHost();
|
||||
final boolean cacheable = responseCachingPolicy.isResponseCacheable(request, backendResponse);
|
||||
responseCache.flushInvalidatedCacheEntriesFor(target, request, backendResponse);
|
||||
if (cacheable && !alreadyHaveNewerCacheEntry(target, request, backendResponse)) {
|
||||
storeRequestIfModifiedSinceFor304Response(request, backendResponse);
|
||||
return responseCache.cacheAndReturnResponse(target, request,
|
||||
backendResponse, requestDate, responseDate);
|
||||
}
|
||||
if (!cacheable) {
|
||||
try {
|
||||
responseCache.flushCacheEntriesFor(target, request);
|
||||
} catch (final IOException ioe) {
|
||||
log.warn("Unable to flush invalid cache entries", ioe);
|
||||
}
|
||||
}
|
||||
return backendResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* For 304 Not modified responses, adds a "Last-Modified" header with the
|
||||
* value of the "If-Modified-Since" header passed in the request. This
|
||||
* header is required to be able to reuse match the cache entry for
|
||||
* subsequent requests but as defined in http specifications it is not
|
||||
* included in 304 responses by backend servers. This header will not be
|
||||
* included in the resulting response.
|
||||
*/
|
||||
private void storeRequestIfModifiedSinceFor304Response(
|
||||
final HttpRequest request, final HttpResponse backendResponse) {
|
||||
if (backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED) {
|
||||
final Header h = request.getFirstHeader("If-Modified-Since");
|
||||
if (h != null) {
|
||||
backendResponse.addHeader("Last-Modified", h.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean alreadyHaveNewerCacheEntry(final HttpHost target, final HttpRequestWrapper request,
|
||||
final HttpResponse backendResponse) {
|
||||
HttpCacheEntry existing = null;
|
||||
try {
|
||||
existing = responseCache.getCacheEntry(target, request);
|
||||
} catch (final IOException ioe) {
|
||||
// nop
|
||||
}
|
||||
if (existing == null) {
|
||||
return false;
|
||||
}
|
||||
final Header entryDateHeader = existing.getFirstHeader(HTTP.DATE_HEADER);
|
||||
if (entryDateHeader == null) {
|
||||
return false;
|
||||
}
|
||||
final Header responseDateHeader = backendResponse.getFirstHeader(HTTP.DATE_HEADER);
|
||||
if (responseDateHeader == null) {
|
||||
return false;
|
||||
}
|
||||
final Date entryDate = DateUtils.parseDate(entryDateHeader.getValue());
|
||||
final Date responseDate = DateUtils.parseDate(responseDateHeader.getValue());
|
||||
if (entryDate == null || responseDate == null) {
|
||||
return false;
|
||||
}
|
||||
return responseDate.before(entryDate);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheInvalidator;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheStorage;
|
||||
import ch.boye.httpclientandroidlib.client.cache.ResourceFactory;
|
||||
import ch.boye.httpclientandroidlib.impl.client.HttpClientBuilder;
|
||||
import ch.boye.httpclientandroidlib.impl.execchain.ClientExecChain;
|
||||
|
||||
/**
|
||||
* Builder for {@link ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient}
|
||||
* instances capable of client-side caching.
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
public class CachingHttpClientBuilder extends HttpClientBuilder {
|
||||
|
||||
private ResourceFactory resourceFactory;
|
||||
private HttpCacheStorage storage;
|
||||
private File cacheDir;
|
||||
private CacheConfig cacheConfig;
|
||||
private SchedulingStrategy schedulingStrategy;
|
||||
private HttpCacheInvalidator httpCacheInvalidator;
|
||||
|
||||
public static CachingHttpClientBuilder create() {
|
||||
return new CachingHttpClientBuilder();
|
||||
}
|
||||
|
||||
protected CachingHttpClientBuilder() {
|
||||
super();
|
||||
}
|
||||
|
||||
public final CachingHttpClientBuilder setResourceFactory(
|
||||
final ResourceFactory resourceFactory) {
|
||||
this.resourceFactory = resourceFactory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public final CachingHttpClientBuilder setHttpCacheStorage(
|
||||
final HttpCacheStorage storage) {
|
||||
this.storage = storage;
|
||||
return this;
|
||||
}
|
||||
|
||||
public final CachingHttpClientBuilder setCacheDir(
|
||||
final File cacheDir) {
|
||||
this.cacheDir = cacheDir;
|
||||
return this;
|
||||
}
|
||||
|
||||
public final CachingHttpClientBuilder setCacheConfig(
|
||||
final CacheConfig cacheConfig) {
|
||||
this.cacheConfig = cacheConfig;
|
||||
return this;
|
||||
}
|
||||
|
||||
public final CachingHttpClientBuilder setSchedulingStrategy(
|
||||
final SchedulingStrategy schedulingStrategy) {
|
||||
this.schedulingStrategy = schedulingStrategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public final CachingHttpClientBuilder setHttpCacheInvalidator(
|
||||
final HttpCacheInvalidator cacheInvalidator) {
|
||||
this.httpCacheInvalidator = cacheInvalidator;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClientExecChain decorateMainExec(final ClientExecChain mainExec) {
|
||||
final CacheConfig config = this.cacheConfig != null ? this.cacheConfig : CacheConfig.DEFAULT;
|
||||
ResourceFactory resourceFactory = this.resourceFactory;
|
||||
if (resourceFactory == null) {
|
||||
if (this.cacheDir == null) {
|
||||
resourceFactory = new HeapResourceFactory();
|
||||
} else {
|
||||
resourceFactory = new FileResourceFactory(cacheDir);
|
||||
}
|
||||
}
|
||||
HttpCacheStorage storage = this.storage;
|
||||
if (storage == null) {
|
||||
if (this.cacheDir == null) {
|
||||
storage = new BasicHttpCacheStorage(config);
|
||||
} else {
|
||||
final ManagedHttpCacheStorage managedStorage = new ManagedHttpCacheStorage(config);
|
||||
addCloseable(managedStorage);
|
||||
storage = managedStorage;
|
||||
}
|
||||
}
|
||||
final AsynchronousValidator revalidator = createAsynchronousRevalidator(config);
|
||||
final CacheKeyGenerator uriExtractor = new CacheKeyGenerator();
|
||||
|
||||
HttpCacheInvalidator cacheInvalidator = this.httpCacheInvalidator;
|
||||
if (cacheInvalidator == null) {
|
||||
cacheInvalidator = new CacheInvalidator(uriExtractor, storage);
|
||||
}
|
||||
|
||||
return new CachingExec(mainExec,
|
||||
new BasicHttpCache(
|
||||
resourceFactory,
|
||||
storage, config,
|
||||
uriExtractor,
|
||||
cacheInvalidator), config, revalidator);
|
||||
}
|
||||
|
||||
private AsynchronousValidator createAsynchronousRevalidator(final CacheConfig config) {
|
||||
if (config.getAsynchronousWorkersMax() > 0) {
|
||||
final SchedulingStrategy configuredSchedulingStrategy = createSchedulingStrategy(config);
|
||||
final AsynchronousValidator revalidator = new AsynchronousValidator(
|
||||
configuredSchedulingStrategy);
|
||||
addCloseable(revalidator);
|
||||
return revalidator;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
private SchedulingStrategy createSchedulingStrategy(final CacheConfig config) {
|
||||
return schedulingStrategy != null ? schedulingStrategy : new ImmediateSchedulingStrategy(config);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
|
||||
|
||||
/**
|
||||
* Factory methods for {@link CloseableHttpClient} instances
|
||||
* capable of client-side caching.
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
@Immutable
|
||||
public class CachingHttpClients {
|
||||
|
||||
private CachingHttpClients() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates builder object for construction of custom
|
||||
* {@link CloseableHttpClient} instances.
|
||||
*/
|
||||
public static CachingHttpClientBuilder custom() {
|
||||
return CachingHttpClientBuilder.create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link CloseableHttpClient} instance that uses a memory bound
|
||||
* response cache.
|
||||
*/
|
||||
public static CloseableHttpClient createMemoryBound() {
|
||||
return CachingHttpClientBuilder.create().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link CloseableHttpClient} instance that uses a file system
|
||||
* bound response cache.
|
||||
*
|
||||
* @param cacheDir location of response cache.
|
||||
*/
|
||||
public static CloseableHttpClient createFileBound(final File cacheDir) {
|
||||
return CachingHttpClientBuilder.create().setCacheDir(cacheDir).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.SequenceInputStream;
|
||||
|
||||
import ch.boye.httpclientandroidlib.annotation.NotThreadSafe;
|
||||
import ch.boye.httpclientandroidlib.client.cache.Resource;
|
||||
import ch.boye.httpclientandroidlib.entity.AbstractHttpEntity;
|
||||
import ch.boye.httpclientandroidlib.util.Args;
|
||||
|
||||
@NotThreadSafe
|
||||
class CombinedEntity extends AbstractHttpEntity {
|
||||
|
||||
private final Resource resource;
|
||||
private final InputStream combinedStream;
|
||||
|
||||
CombinedEntity(final Resource resource, final InputStream instream) throws IOException {
|
||||
super();
|
||||
this.resource = resource;
|
||||
this.combinedStream = new SequenceInputStream(
|
||||
new ResourceStream(resource.getInputStream()), instream);
|
||||
}
|
||||
|
||||
public long getContentLength() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean isRepeatable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isStreaming() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public InputStream getContent() throws IOException, IllegalStateException {
|
||||
return this.combinedStream;
|
||||
}
|
||||
|
||||
public void writeTo(final OutputStream outstream) throws IOException {
|
||||
Args.notNull(outstream, "Output stream");
|
||||
final InputStream instream = getContent();
|
||||
try {
|
||||
int l;
|
||||
final byte[] tmp = new byte[2048];
|
||||
while ((l = instream.read(tmp)) != -1) {
|
||||
outstream.write(tmp, 0, l);
|
||||
}
|
||||
} finally {
|
||||
instream.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void dispose() {
|
||||
this.resource.dispose();
|
||||
}
|
||||
|
||||
class ResourceStream extends FilterInputStream {
|
||||
|
||||
protected ResourceStream(final InputStream in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
super.close();
|
||||
} finally {
|
||||
dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.HeaderElement;
|
||||
import ch.boye.httpclientandroidlib.ProtocolException;
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HeaderConstants;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
@Immutable
|
||||
class ConditionalRequestBuilder {
|
||||
|
||||
/**
|
||||
* When a {@link HttpCacheEntry} is stale but 'might' be used as a response
|
||||
* to an {@link ch.boye.httpclientandroidlib.HttpRequest} we will attempt to revalidate
|
||||
* the entry with the origin. Build the origin {@link ch.boye.httpclientandroidlib.HttpRequest}
|
||||
* here and return it.
|
||||
*
|
||||
* @param request the original request from the caller
|
||||
* @param cacheEntry the entry that needs to be re-validated
|
||||
* @return the wrapped request
|
||||
* @throws ProtocolException when I am unable to build a new origin request.
|
||||
*/
|
||||
public HttpRequestWrapper buildConditionalRequest(final HttpRequestWrapper request, final HttpCacheEntry cacheEntry)
|
||||
throws ProtocolException {
|
||||
final HttpRequestWrapper newRequest = HttpRequestWrapper.wrap(request.getOriginal());
|
||||
newRequest.setHeaders(request.getAllHeaders());
|
||||
final Header eTag = cacheEntry.getFirstHeader(HeaderConstants.ETAG);
|
||||
if (eTag != null) {
|
||||
newRequest.setHeader(HeaderConstants.IF_NONE_MATCH, eTag.getValue());
|
||||
}
|
||||
final Header lastModified = cacheEntry.getFirstHeader(HeaderConstants.LAST_MODIFIED);
|
||||
if (lastModified != null) {
|
||||
newRequest.setHeader(HeaderConstants.IF_MODIFIED_SINCE, lastModified.getValue());
|
||||
}
|
||||
boolean mustRevalidate = false;
|
||||
for(final Header h : cacheEntry.getHeaders(HeaderConstants.CACHE_CONTROL)) {
|
||||
for(final HeaderElement elt : h.getElements()) {
|
||||
if (HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE.equalsIgnoreCase(elt.getName())
|
||||
|| HeaderConstants.CACHE_CONTROL_PROXY_REVALIDATE.equalsIgnoreCase(elt.getName())) {
|
||||
mustRevalidate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mustRevalidate) {
|
||||
newRequest.addHeader(HeaderConstants.CACHE_CONTROL, HeaderConstants.CACHE_CONTROL_MAX_AGE + "=0");
|
||||
}
|
||||
return newRequest;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* When a {@link HttpCacheEntry} does not exist for a specific
|
||||
* {@link ch.boye.httpclientandroidlib.HttpRequest} we attempt to see if an existing
|
||||
* {@link HttpCacheEntry} is appropriate by building a conditional
|
||||
* {@link ch.boye.httpclientandroidlib.HttpRequest} using the variants' ETag values.
|
||||
* If no such values exist, the request is unmodified
|
||||
*
|
||||
* @param request the original request from the caller
|
||||
* @param variants
|
||||
* @return the wrapped request
|
||||
*/
|
||||
public HttpRequestWrapper buildConditionalRequestFromVariants(final HttpRequestWrapper request,
|
||||
final Map<String, Variant> variants) {
|
||||
final HttpRequestWrapper newRequest = HttpRequestWrapper.wrap(request.getOriginal());
|
||||
newRequest.setHeaders(request.getAllHeaders());
|
||||
|
||||
// we do not support partial content so all etags are used
|
||||
final StringBuilder etags = new StringBuilder();
|
||||
boolean first = true;
|
||||
for(final String etag : variants.keySet()) {
|
||||
if (!first) {
|
||||
etags.append(",");
|
||||
}
|
||||
first = false;
|
||||
etags.append(etag);
|
||||
}
|
||||
|
||||
newRequest.setHeader(HeaderConstants.IF_NONE_MATCH, etags.toString());
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a request to unconditionally validate a cache entry with
|
||||
* the origin. In certain cases (due to multiple intervening caches)
|
||||
* our cache may actually receive a response to a normal conditional
|
||||
* validation where the Date header is actually older than that of
|
||||
* our current cache entry. In this case, the protocol recommendation
|
||||
* is to retry the validation and force syncup with the origin.
|
||||
* @param request client request we are trying to satisfy
|
||||
* @param entry existing cache entry we are trying to validate
|
||||
* @return an unconditional validation request
|
||||
*/
|
||||
public HttpRequestWrapper buildUnconditionalRequest(final HttpRequestWrapper request, final HttpCacheEntry entry) {
|
||||
final HttpRequestWrapper newRequest = HttpRequestWrapper.wrap(request.getOriginal());
|
||||
newRequest.setHeaders(request.getAllHeaders());
|
||||
newRequest.addHeader(HeaderConstants.CACHE_CONTROL,HeaderConstants.CACHE_CONTROL_NO_CACHE);
|
||||
newRequest.addHeader(HeaderConstants.PRAGMA,HeaderConstants.CACHE_CONTROL_NO_CACHE);
|
||||
newRequest.removeHeaders(HeaderConstants.IF_RANGE);
|
||||
newRequest.removeHeaders(HeaderConstants.IF_MATCH);
|
||||
newRequest.removeHeaders(HeaderConstants.IF_NONE_MATCH);
|
||||
newRequest.removeHeaders(HeaderConstants.IF_UNMODIFIED_SINCE);
|
||||
newRequest.removeHeaders(HeaderConstants.IF_MODIFIED_SINCE);
|
||||
return newRequest;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import ch.boye.httpclientandroidlib.annotation.ThreadSafe;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* Implements a bounded failure cache. The oldest entries are discarded when
|
||||
* the maximum size is exceeded.
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
@ThreadSafe
|
||||
public class DefaultFailureCache implements FailureCache {
|
||||
|
||||
static final int DEFAULT_MAX_SIZE = 1000;
|
||||
static final int MAX_UPDATE_TRIES = 10;
|
||||
|
||||
private final int maxSize;
|
||||
private final ConcurrentMap<String, FailureCacheValue> storage;
|
||||
|
||||
/**
|
||||
* Create a new failure cache with the maximum size of
|
||||
* {@link #DEFAULT_MAX_SIZE}.
|
||||
*/
|
||||
public DefaultFailureCache() {
|
||||
this(DEFAULT_MAX_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new failure cache with the specified maximum size.
|
||||
* @param maxSize the maximum number of entries the cache should store
|
||||
*/
|
||||
public DefaultFailureCache(final int maxSize) {
|
||||
this.maxSize = maxSize;
|
||||
this.storage = new ConcurrentHashMap<String, FailureCacheValue>();
|
||||
}
|
||||
|
||||
public int getErrorCount(final String identifier) {
|
||||
if (identifier == null) {
|
||||
throw new IllegalArgumentException("identifier may not be null");
|
||||
}
|
||||
final FailureCacheValue storedErrorCode = storage.get(identifier);
|
||||
return storedErrorCode != null ? storedErrorCode.getErrorCount() : 0;
|
||||
}
|
||||
|
||||
public void resetErrorCount(final String identifier) {
|
||||
if (identifier == null) {
|
||||
throw new IllegalArgumentException("identifier may not be null");
|
||||
}
|
||||
storage.remove(identifier);
|
||||
}
|
||||
|
||||
public void increaseErrorCount(final String identifier) {
|
||||
if (identifier == null) {
|
||||
throw new IllegalArgumentException("identifier may not be null");
|
||||
}
|
||||
updateValue(identifier);
|
||||
removeOldestEntryIfMapSizeExceeded();
|
||||
}
|
||||
|
||||
private void updateValue(final String identifier) {
|
||||
/**
|
||||
* Due to concurrency it is possible that someone else is modifying an
|
||||
* entry before we could write back our updated value. So we keep
|
||||
* trying until it is our turn.
|
||||
*
|
||||
* In case there is a lot of contention on that identifier, a thread
|
||||
* might starve. Thus it gives up after a certain number of failed
|
||||
* update tries.
|
||||
*/
|
||||
for (int i = 0; i < MAX_UPDATE_TRIES; i++) {
|
||||
final FailureCacheValue oldValue = storage.get(identifier);
|
||||
if (oldValue == null) {
|
||||
final FailureCacheValue newValue = new FailureCacheValue(identifier, 1);
|
||||
if (storage.putIfAbsent(identifier, newValue) == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
final int errorCount = oldValue.getErrorCount();
|
||||
if (errorCount == Integer.MAX_VALUE) {
|
||||
return;
|
||||
}
|
||||
final FailureCacheValue newValue = new FailureCacheValue(identifier, errorCount + 1);
|
||||
if (storage.replace(identifier, oldValue, newValue)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeOldestEntryIfMapSizeExceeded() {
|
||||
if (storage.size() > maxSize) {
|
||||
final FailureCacheValue valueWithOldestTimestamp = findValueWithOldestTimestamp();
|
||||
if (valueWithOldestTimestamp != null) {
|
||||
storage.remove(valueWithOldestTimestamp.getKey(), valueWithOldestTimestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private FailureCacheValue findValueWithOldestTimestamp() {
|
||||
long oldestTimestamp = Long.MAX_VALUE;
|
||||
FailureCacheValue oldestValue = null;
|
||||
for (final Map.Entry<String, FailureCacheValue> storageEntry : storage.entrySet()) {
|
||||
final FailureCacheValue value = storageEntry.getValue();
|
||||
final long creationTimeInNanos = value.getCreationTimeInNanos();
|
||||
if (creationTimeInNanos < oldestTimestamp) {
|
||||
oldestTimestamp = creationTimeInNanos;
|
||||
oldestValue = storageEntry.getValue();
|
||||
}
|
||||
}
|
||||
return oldestValue;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntrySerializationException;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntrySerializer;
|
||||
|
||||
/**
|
||||
* {@link HttpCacheEntrySerializer} implementation that uses the default (native)
|
||||
* serialization.
|
||||
*
|
||||
* @see java.io.Serializable
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
@Immutable
|
||||
public class DefaultHttpCacheEntrySerializer implements HttpCacheEntrySerializer {
|
||||
|
||||
public void writeTo(final HttpCacheEntry cacheEntry, final OutputStream os) throws IOException {
|
||||
final ObjectOutputStream oos = new ObjectOutputStream(os);
|
||||
try {
|
||||
oos.writeObject(cacheEntry);
|
||||
} finally {
|
||||
oos.close();
|
||||
}
|
||||
}
|
||||
|
||||
public HttpCacheEntry readFrom(final InputStream is) throws IOException {
|
||||
final ObjectInputStream ois = new ObjectInputStream(is);
|
||||
try {
|
||||
return (HttpCacheEntry) ois.readObject();
|
||||
} catch (final ClassNotFoundException ex) {
|
||||
throw new HttpCacheEntrySerializationException("Class not found: " + ex.getMessage(), ex);
|
||||
} finally {
|
||||
ois.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import ch.boye.httpclientandroidlib.annotation.ThreadSafe;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* An implementation that backs off exponentially based on the number of
|
||||
* consecutive failed attempts stored in the
|
||||
* {@link AsynchronousValidationRequest}. It uses the following defaults:
|
||||
* <pre>
|
||||
* no delay in case it was never tried or didn't fail so far
|
||||
* 6 secs delay for one failed attempt (= {@link #getInitialExpiryInMillis()})
|
||||
* 60 secs delay for two failed attempts
|
||||
* 10 mins delay for three failed attempts
|
||||
* 100 mins delay for four failed attempts
|
||||
* ~16 hours delay for five failed attempts
|
||||
* 24 hours delay for six or more failed attempts (= {@link #getMaxExpiryInMillis()})
|
||||
* </pre>
|
||||
*
|
||||
* The following equation is used to calculate the delay for a specific revalidation request:
|
||||
* <pre>
|
||||
* delay = {@link #getInitialExpiryInMillis()} * Math.pow({@link #getBackOffRate()}, {@link AsynchronousValidationRequest#getConsecutiveFailedAttempts()} - 1))
|
||||
* </pre>
|
||||
* The resulting delay won't exceed {@link #getMaxExpiryInMillis()}.
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
@ThreadSafe
|
||||
public class ExponentialBackOffSchedulingStrategy implements SchedulingStrategy {
|
||||
|
||||
public static final long DEFAULT_BACK_OFF_RATE = 10;
|
||||
public static final long DEFAULT_INITIAL_EXPIRY_IN_MILLIS = TimeUnit.SECONDS.toMillis(6);
|
||||
public static final long DEFAULT_MAX_EXPIRY_IN_MILLIS = TimeUnit.SECONDS.toMillis(86400);
|
||||
|
||||
private final long backOffRate;
|
||||
private final long initialExpiryInMillis;
|
||||
private final long maxExpiryInMillis;
|
||||
|
||||
private final ScheduledExecutorService executor;
|
||||
|
||||
/**
|
||||
* Create a new scheduling strategy using a fixed pool of worker threads.
|
||||
* @param cacheConfig the thread pool configuration to be used; not <code>null</code>
|
||||
* @see ch.boye.httpclientandroidlib.impl.client.cache.CacheConfig#getAsynchronousWorkersMax()
|
||||
* @see #DEFAULT_BACK_OFF_RATE
|
||||
* @see #DEFAULT_INITIAL_EXPIRY_IN_MILLIS
|
||||
* @see #DEFAULT_MAX_EXPIRY_IN_MILLIS
|
||||
*/
|
||||
public ExponentialBackOffSchedulingStrategy(final CacheConfig cacheConfig) {
|
||||
this(cacheConfig,
|
||||
DEFAULT_BACK_OFF_RATE,
|
||||
DEFAULT_INITIAL_EXPIRY_IN_MILLIS,
|
||||
DEFAULT_MAX_EXPIRY_IN_MILLIS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new scheduling strategy by using a fixed pool of worker threads and the
|
||||
* given parameters to calculated the delay.
|
||||
*
|
||||
* @param cacheConfig the thread pool configuration to be used; not <code>null</code>
|
||||
* @param backOffRate the back off rate to be used; not negative
|
||||
* @param initialExpiryInMillis the initial expiry in milli seconds; not negative
|
||||
* @param maxExpiryInMillis the upper limit of the delay in milli seconds; not negative
|
||||
* @see ch.boye.httpclientandroidlib.impl.client.cache.CacheConfig#getAsynchronousWorkersMax()
|
||||
* @see ExponentialBackOffSchedulingStrategy
|
||||
*/
|
||||
public ExponentialBackOffSchedulingStrategy(
|
||||
final CacheConfig cacheConfig,
|
||||
final long backOffRate,
|
||||
final long initialExpiryInMillis,
|
||||
final long maxExpiryInMillis) {
|
||||
this(createThreadPoolFromCacheConfig(cacheConfig),
|
||||
backOffRate,
|
||||
initialExpiryInMillis,
|
||||
maxExpiryInMillis);
|
||||
}
|
||||
|
||||
private static ScheduledThreadPoolExecutor createThreadPoolFromCacheConfig(
|
||||
final CacheConfig cacheConfig) {
|
||||
final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(
|
||||
cacheConfig.getAsynchronousWorkersMax());
|
||||
scheduledThreadPoolExecutor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
|
||||
return scheduledThreadPoolExecutor;
|
||||
}
|
||||
|
||||
ExponentialBackOffSchedulingStrategy(
|
||||
final ScheduledExecutorService executor,
|
||||
final long backOffRate,
|
||||
final long initialExpiryInMillis,
|
||||
final long maxExpiryInMillis) {
|
||||
this.executor = checkNotNull("executor", executor);
|
||||
this.backOffRate = checkNotNegative("backOffRate", backOffRate);
|
||||
this.initialExpiryInMillis = checkNotNegative("initialExpiryInMillis", initialExpiryInMillis);
|
||||
this.maxExpiryInMillis = checkNotNegative("maxExpiryInMillis", maxExpiryInMillis);
|
||||
}
|
||||
|
||||
public void schedule(
|
||||
final AsynchronousValidationRequest revalidationRequest) {
|
||||
checkNotNull("revalidationRequest", revalidationRequest);
|
||||
final int consecutiveFailedAttempts = revalidationRequest.getConsecutiveFailedAttempts();
|
||||
final long delayInMillis = calculateDelayInMillis(consecutiveFailedAttempts);
|
||||
executor.schedule(revalidationRequest, delayInMillis, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
public long getBackOffRate() {
|
||||
return backOffRate;
|
||||
}
|
||||
|
||||
public long getInitialExpiryInMillis() {
|
||||
return initialExpiryInMillis;
|
||||
}
|
||||
|
||||
public long getMaxExpiryInMillis() {
|
||||
return maxExpiryInMillis;
|
||||
}
|
||||
|
||||
protected long calculateDelayInMillis(final int consecutiveFailedAttempts) {
|
||||
if (consecutiveFailedAttempts > 0) {
|
||||
final long delayInSeconds = (long) (initialExpiryInMillis *
|
||||
Math.pow(backOffRate, consecutiveFailedAttempts - 1));
|
||||
return Math.min(delayInSeconds, maxExpiryInMillis);
|
||||
}
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected static <T> T checkNotNull(final String parameterName, final T value) {
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException(parameterName + " may not be null");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
protected static long checkNotNegative(final String parameterName, final long value) {
|
||||
if (value < 0) {
|
||||
throw new IllegalArgumentException(parameterName + " may not be negative");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
/**
|
||||
* Increase and reset the number of errors associated with a specific
|
||||
* identifier.
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
public interface FailureCache {
|
||||
|
||||
/**
|
||||
* Get the current error count.
|
||||
* @param identifier the identifier for which the error count is requested
|
||||
* @return the currently known error count or zero if there is no record
|
||||
*/
|
||||
int getErrorCount(String identifier);
|
||||
|
||||
/**
|
||||
* Reset the error count back to zero.
|
||||
* @param identifier the identifier for which the error count should be
|
||||
* reset
|
||||
*/
|
||||
void resetErrorCount(String identifier);
|
||||
|
||||
/**
|
||||
* Increases the error count by one.
|
||||
* @param identifier the identifier for which the error count should be
|
||||
* increased
|
||||
*/
|
||||
void increaseErrorCount(String identifier);
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
|
||||
/**
|
||||
* The error count with a creation timestamp and its associated key.
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
@Immutable
|
||||
public class FailureCacheValue {
|
||||
|
||||
private final long creationTimeInNanos;
|
||||
private final String key;
|
||||
private final int errorCount;
|
||||
|
||||
public FailureCacheValue(final String key, final int errorCount) {
|
||||
this.creationTimeInNanos = System.nanoTime();
|
||||
this.key = key;
|
||||
this.errorCount = errorCount;
|
||||
}
|
||||
|
||||
public long getCreationTimeInNanos() {
|
||||
return creationTimeInNanos;
|
||||
}
|
||||
|
||||
public String getKey()
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
public int getErrorCount() {
|
||||
return errorCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[entry creationTimeInNanos=" + creationTimeInNanos + "; " +
|
||||
"key=" + key + "; errorCount=" + errorCount + ']';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import ch.boye.httpclientandroidlib.annotation.ThreadSafe;
|
||||
import ch.boye.httpclientandroidlib.client.cache.Resource;
|
||||
|
||||
/**
|
||||
* Cache resource backed by a file.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
@ThreadSafe
|
||||
public class FileResource implements Resource {
|
||||
|
||||
private static final long serialVersionUID = 4132244415919043397L;
|
||||
|
||||
private final File file;
|
||||
|
||||
private volatile boolean disposed;
|
||||
|
||||
public FileResource(final File file) {
|
||||
super();
|
||||
this.file = file;
|
||||
this.disposed = false;
|
||||
}
|
||||
|
||||
synchronized File getFile() {
|
||||
return this.file;
|
||||
}
|
||||
|
||||
public synchronized InputStream getInputStream() throws IOException {
|
||||
return new FileInputStream(this.file);
|
||||
}
|
||||
|
||||
public synchronized long length() {
|
||||
return this.file.length();
|
||||
}
|
||||
|
||||
public synchronized void dispose() {
|
||||
if (this.disposed) {
|
||||
return;
|
||||
}
|
||||
this.disposed = true;
|
||||
this.file.delete();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.client.cache.InputLimit;
|
||||
import ch.boye.httpclientandroidlib.client.cache.Resource;
|
||||
import ch.boye.httpclientandroidlib.client.cache.ResourceFactory;
|
||||
|
||||
/**
|
||||
* Generates {@link Resource} instances whose body is stored in a temporary file.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
@Immutable
|
||||
public class FileResourceFactory implements ResourceFactory {
|
||||
|
||||
private final File cacheDir;
|
||||
private final BasicIdGenerator idgen;
|
||||
|
||||
public FileResourceFactory(final File cacheDir) {
|
||||
super();
|
||||
this.cacheDir = cacheDir;
|
||||
this.idgen = new BasicIdGenerator();
|
||||
}
|
||||
|
||||
private File generateUniqueCacheFile(final String requestId) {
|
||||
final StringBuilder buffer = new StringBuilder();
|
||||
this.idgen.generate(buffer);
|
||||
buffer.append('.');
|
||||
final int len = Math.min(requestId.length(), 100);
|
||||
for (int i = 0; i < len; i++) {
|
||||
final char ch = requestId.charAt(i);
|
||||
if (Character.isLetterOrDigit(ch) || ch == '.') {
|
||||
buffer.append(ch);
|
||||
} else {
|
||||
buffer.append('-');
|
||||
}
|
||||
}
|
||||
return new File(this.cacheDir, buffer.toString());
|
||||
}
|
||||
|
||||
public Resource generate(
|
||||
final String requestId,
|
||||
final InputStream instream,
|
||||
final InputLimit limit) throws IOException {
|
||||
final File file = generateUniqueCacheFile(requestId);
|
||||
final FileOutputStream outstream = new FileOutputStream(file);
|
||||
try {
|
||||
final byte[] buf = new byte[2048];
|
||||
long total = 0;
|
||||
int l;
|
||||
while ((l = instream.read(buf)) != -1) {
|
||||
outstream.write(buf, 0, l);
|
||||
total += l;
|
||||
if (limit != null && total > limit.getValue()) {
|
||||
limit.reached();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
outstream.close();
|
||||
}
|
||||
return new FileResource(file);
|
||||
}
|
||||
|
||||
public Resource copy(
|
||||
final String requestId,
|
||||
final Resource resource) throws IOException {
|
||||
final File file = generateUniqueCacheFile(requestId);
|
||||
|
||||
if (resource instanceof FileResource) {
|
||||
final File src = ((FileResource) resource).getFile();
|
||||
IOUtils.copyFile(src, file);
|
||||
} else {
|
||||
final FileOutputStream out = new FileOutputStream(file);
|
||||
IOUtils.copyAndClose(resource.getInputStream(), out);
|
||||
}
|
||||
return new FileResource(file);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.client.cache.Resource;
|
||||
|
||||
/**
|
||||
* Cache resource backed by a byte array on the heap.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
@Immutable
|
||||
public class HeapResource implements Resource {
|
||||
|
||||
private static final long serialVersionUID = -2078599905620463394L;
|
||||
|
||||
private final byte[] b;
|
||||
|
||||
public HeapResource(final byte[] b) {
|
||||
super();
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
byte[] getByteArray() {
|
||||
return this.b;
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
return new ByteArrayInputStream(this.b);
|
||||
}
|
||||
|
||||
public long length() {
|
||||
return this.b.length;
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.client.cache.InputLimit;
|
||||
import ch.boye.httpclientandroidlib.client.cache.Resource;
|
||||
import ch.boye.httpclientandroidlib.client.cache.ResourceFactory;
|
||||
|
||||
/**
|
||||
* Generates {@link Resource} instances stored entirely in heap.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
@Immutable
|
||||
public class HeapResourceFactory implements ResourceFactory {
|
||||
|
||||
public Resource generate(
|
||||
final String requestId,
|
||||
final InputStream instream,
|
||||
final InputLimit limit) throws IOException {
|
||||
final ByteArrayOutputStream outstream = new ByteArrayOutputStream();
|
||||
final byte[] buf = new byte[2048];
|
||||
long total = 0;
|
||||
int l;
|
||||
while ((l = instream.read(buf)) != -1) {
|
||||
outstream.write(buf, 0, l);
|
||||
total += l;
|
||||
if (limit != null && total > limit.getValue()) {
|
||||
limit.reached();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return createResource(outstream.toByteArray());
|
||||
}
|
||||
|
||||
public Resource copy(
|
||||
final String requestId,
|
||||
final Resource resource) throws IOException {
|
||||
byte[] body;
|
||||
if (resource instanceof HeapResource) {
|
||||
body = ((HeapResource) resource).getByteArray();
|
||||
} else {
|
||||
final ByteArrayOutputStream outstream = new ByteArrayOutputStream();
|
||||
IOUtils.copyAndClose(resource.getInputStream(), outstream);
|
||||
body = outstream.toByteArray();
|
||||
}
|
||||
return createResource(body);
|
||||
}
|
||||
|
||||
Resource createResource(final byte[] buf) {
|
||||
return new HeapResource(buf);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import ch.boye.httpclientandroidlib.HttpHost;
|
||||
import ch.boye.httpclientandroidlib.HttpRequest;
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
interface HttpCache {
|
||||
|
||||
/**
|
||||
* Clear all matching {@link HttpCacheEntry}s.
|
||||
* @param host
|
||||
* @param request
|
||||
* @throws IOException
|
||||
*/
|
||||
void flushCacheEntriesFor(HttpHost host, HttpRequest request)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Clear invalidated matching {@link HttpCacheEntry}s
|
||||
* @param host
|
||||
* @param request
|
||||
* @throws IOException
|
||||
*/
|
||||
void flushInvalidatedCacheEntriesFor(HttpHost host, HttpRequest request)
|
||||
throws IOException;
|
||||
|
||||
/** Clear any entries that may be invalidated by the given response to
|
||||
* a particular request.
|
||||
* @param host
|
||||
* @param request
|
||||
* @param response
|
||||
*/
|
||||
void flushInvalidatedCacheEntriesFor(HttpHost host, HttpRequest request,
|
||||
HttpResponse response);
|
||||
|
||||
/**
|
||||
* Retrieve matching {@link HttpCacheEntry} from the cache if it exists
|
||||
* @param host
|
||||
* @param request
|
||||
* @return the matching {@link HttpCacheEntry} or {@code null}
|
||||
* @throws IOException
|
||||
*/
|
||||
HttpCacheEntry getCacheEntry(HttpHost host, HttpRequest request)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Retrieve all variants from the cache, if there are no variants then an empty
|
||||
* {@link Map} is returned
|
||||
* @param host
|
||||
* @param request
|
||||
* @return a <code>Map</code> mapping Etags to variant cache entries
|
||||
* @throws IOException
|
||||
*/
|
||||
Map<String,Variant> getVariantCacheEntriesWithEtags(HttpHost host, HttpRequest request)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Store a {@link HttpResponse} in the cache if possible, and return
|
||||
* @param host
|
||||
* @param request
|
||||
* @param originResponse
|
||||
* @param requestSent
|
||||
* @param responseReceived
|
||||
* @return the {@link HttpResponse}
|
||||
* @throws IOException
|
||||
*/
|
||||
HttpResponse cacheAndReturnResponse(
|
||||
HttpHost host, HttpRequest request, HttpResponse originResponse,
|
||||
Date requestSent, Date responseReceived)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Store a {@link HttpResponse} in the cache if possible, and return
|
||||
* @param host
|
||||
* @param request
|
||||
* @param originResponse
|
||||
* @param requestSent
|
||||
* @param responseReceived
|
||||
* @return the {@link HttpResponse}
|
||||
* @throws IOException
|
||||
*/
|
||||
CloseableHttpResponse cacheAndReturnResponse(HttpHost host,
|
||||
HttpRequest request, CloseableHttpResponse originResponse,
|
||||
Date requestSent, Date responseReceived)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Update a {@link HttpCacheEntry} using a 304 {@link HttpResponse}.
|
||||
* @param target
|
||||
* @param request
|
||||
* @param stale
|
||||
* @param originResponse
|
||||
* @param requestSent
|
||||
* @param responseReceived
|
||||
* @return the updated {@link HttpCacheEntry}
|
||||
* @throws IOException
|
||||
*/
|
||||
HttpCacheEntry updateCacheEntry(
|
||||
HttpHost target, HttpRequest request, HttpCacheEntry stale, HttpResponse originResponse,
|
||||
Date requestSent, Date responseReceived)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Update a specific {@link HttpCacheEntry} representing a cached variant
|
||||
* using a 304 {@link HttpResponse}.
|
||||
* @param target host for client request
|
||||
* @param request actual request from upstream client
|
||||
* @param stale current variant cache entry
|
||||
* @param originResponse 304 response received from origin
|
||||
* @param requestSent when the validating request was sent
|
||||
* @param responseReceived when the validating response was received
|
||||
* @param cacheKey where in the cache this entry is currently stored
|
||||
* @return the updated {@link HttpCacheEntry}
|
||||
* @throws IOException
|
||||
*/
|
||||
HttpCacheEntry updateVariantCacheEntry(HttpHost target, HttpRequest request,
|
||||
HttpCacheEntry stale, HttpResponse originResponse, Date requestSent,
|
||||
Date responseReceived, String cacheKey)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Specifies cache should reuse the given cached variant to satisfy
|
||||
* requests whose varying headers match those of the given client request.
|
||||
* @param target host of the upstream client request
|
||||
* @param req request sent by upstream client
|
||||
* @param variant variant cache entry to reuse
|
||||
* @throws IOException may be thrown during cache update
|
||||
*/
|
||||
void reuseVariantEntryFor(HttpHost target, final HttpRequest req,
|
||||
final Variant variant) throws IOException;
|
||||
}
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
import ch.boye.httpclientandroidlib.HttpEntity;
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
|
||||
@Immutable
|
||||
class IOUtils {
|
||||
|
||||
static void consume(final HttpEntity entity) throws IOException {
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
if (entity.isStreaming()) {
|
||||
final InputStream instream = entity.getContent();
|
||||
if (instream != null) {
|
||||
instream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void copy(final InputStream in, final OutputStream out) throws IOException {
|
||||
final byte[] buf = new byte[2048];
|
||||
int len;
|
||||
while ((len = in.read(buf)) != -1) {
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
static void closeSilently(final Closeable closable) {
|
||||
try {
|
||||
closable.close();
|
||||
} catch (final IOException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
static void copyAndClose(final InputStream in, final OutputStream out) throws IOException {
|
||||
try {
|
||||
copy(in, out);
|
||||
in.close();
|
||||
out.close();
|
||||
} catch (final IOException ex) {
|
||||
closeSilently(in);
|
||||
closeSilently(out);
|
||||
// Propagate the original exception
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
static void copyFile(final File in, final File out) throws IOException {
|
||||
final RandomAccessFile f1 = new RandomAccessFile(in, "r");
|
||||
final RandomAccessFile f2 = new RandomAccessFile(out, "rw");
|
||||
try {
|
||||
final FileChannel c1 = f1.getChannel();
|
||||
final FileChannel c2 = f2.getChannel();
|
||||
try {
|
||||
c1.transferTo(0, f1.length(), c2);
|
||||
c1.close();
|
||||
c2.close();
|
||||
} catch (final IOException ex) {
|
||||
closeSilently(c1);
|
||||
closeSilently(c2);
|
||||
// Propagate the original exception
|
||||
throw ex;
|
||||
}
|
||||
f1.close();
|
||||
f2.close();
|
||||
} catch (final IOException ex) {
|
||||
closeSilently(f1);
|
||||
closeSilently(f2);
|
||||
// Propagate the original exception
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import ch.boye.httpclientandroidlib.annotation.ThreadSafe;
|
||||
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Immediately schedules any incoming validation request. Relies on
|
||||
* {@link CacheConfig} to configure the used {@link java.util.concurrent.ThreadPoolExecutor}.
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
@ThreadSafe
|
||||
public class ImmediateSchedulingStrategy implements SchedulingStrategy {
|
||||
|
||||
private final ExecutorService executor;
|
||||
|
||||
/**
|
||||
* Uses a {@link java.util.concurrent.ThreadPoolExecutor} which is configured according to the
|
||||
* given {@link CacheConfig}.
|
||||
* @param cacheConfig specifies thread pool settings. See
|
||||
* {@link CacheConfig#getAsynchronousWorkersMax()},
|
||||
* {@link CacheConfig#getAsynchronousWorkersCore()},
|
||||
* {@link CacheConfig#getAsynchronousWorkerIdleLifetimeSecs()},
|
||||
* and {@link CacheConfig#getRevalidationQueueSize()}.
|
||||
*/
|
||||
public ImmediateSchedulingStrategy(final CacheConfig cacheConfig) {
|
||||
this(new ThreadPoolExecutor(
|
||||
cacheConfig.getAsynchronousWorkersCore(),
|
||||
cacheConfig.getAsynchronousWorkersMax(),
|
||||
cacheConfig.getAsynchronousWorkerIdleLifetimeSecs(),
|
||||
TimeUnit.SECONDS,
|
||||
new ArrayBlockingQueue<Runnable>(cacheConfig.getRevalidationQueueSize()))
|
||||
);
|
||||
}
|
||||
|
||||
ImmediateSchedulingStrategy(final ExecutorService executor) {
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
public void schedule(final AsynchronousValidationRequest revalidationRequest) {
|
||||
if (revalidationRequest == null) {
|
||||
throw new IllegalArgumentException("AsynchronousValidationRequest may not be null");
|
||||
}
|
||||
|
||||
executor.execute(revalidationRequest);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
executor.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Visible for testing.
|
||||
*/
|
||||
void awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException {
|
||||
executor.awaitTermination(timeout, unit);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import ch.boye.httpclientandroidlib.annotation.ThreadSafe;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheStorage;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheUpdateCallback;
|
||||
import ch.boye.httpclientandroidlib.client.cache.Resource;
|
||||
import ch.boye.httpclientandroidlib.util.Args;
|
||||
|
||||
/**
|
||||
* {@link HttpCacheStorage} implementation capable of deallocating resources associated with
|
||||
* the cache entries. This cache keeps track of cache entries using
|
||||
* {@link java.lang.ref.PhantomReference} and maintains a collection of all resources that
|
||||
* are no longer in use. The cache, however, does not automatically deallocates associated
|
||||
* resources by invoking {@link Resource#dispose()} method. The consumer MUST periodically
|
||||
* call {@link #cleanResources()} method to trigger resource deallocation. The cache can be
|
||||
* permanently shut down using {@link #shutdown()} method. All resources associated with
|
||||
* the entries used by the cache will be deallocated.
|
||||
*
|
||||
* This {@link HttpCacheStorage} implementation is intended for use with {@link FileResource}
|
||||
* and similar.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
@ThreadSafe
|
||||
public class ManagedHttpCacheStorage implements HttpCacheStorage, Closeable {
|
||||
|
||||
private final CacheMap entries;
|
||||
private final ReferenceQueue<HttpCacheEntry> morque;
|
||||
private final Set<ResourceReference> resources;
|
||||
private final AtomicBoolean active;
|
||||
|
||||
public ManagedHttpCacheStorage(final CacheConfig config) {
|
||||
super();
|
||||
this.entries = new CacheMap(config.getMaxCacheEntries());
|
||||
this.morque = new ReferenceQueue<HttpCacheEntry>();
|
||||
this.resources = new HashSet<ResourceReference>();
|
||||
this.active = new AtomicBoolean(true);
|
||||
}
|
||||
|
||||
private void ensureValidState() throws IllegalStateException {
|
||||
if (!this.active.get()) {
|
||||
throw new IllegalStateException("Cache has been shut down");
|
||||
}
|
||||
}
|
||||
|
||||
private void keepResourceReference(final HttpCacheEntry entry) {
|
||||
final Resource resource = entry.getResource();
|
||||
if (resource != null) {
|
||||
// Must deallocate the resource when the entry is no longer in used
|
||||
final ResourceReference ref = new ResourceReference(entry, this.morque);
|
||||
this.resources.add(ref);
|
||||
}
|
||||
}
|
||||
|
||||
public void putEntry(final String url, final HttpCacheEntry entry) throws IOException {
|
||||
Args.notNull(url, "URL");
|
||||
Args.notNull(entry, "Cache entry");
|
||||
ensureValidState();
|
||||
synchronized (this) {
|
||||
this.entries.put(url, entry);
|
||||
keepResourceReference(entry);
|
||||
}
|
||||
}
|
||||
|
||||
public HttpCacheEntry getEntry(final String url) throws IOException {
|
||||
Args.notNull(url, "URL");
|
||||
ensureValidState();
|
||||
synchronized (this) {
|
||||
return this.entries.get(url);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeEntry(final String url) throws IOException {
|
||||
Args.notNull(url, "URL");
|
||||
ensureValidState();
|
||||
synchronized (this) {
|
||||
// Cannot deallocate the associated resources immediately as the
|
||||
// cache entry may still be in use
|
||||
this.entries.remove(url);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateEntry(
|
||||
final String url,
|
||||
final HttpCacheUpdateCallback callback) throws IOException {
|
||||
Args.notNull(url, "URL");
|
||||
Args.notNull(callback, "Callback");
|
||||
ensureValidState();
|
||||
synchronized (this) {
|
||||
final HttpCacheEntry existing = this.entries.get(url);
|
||||
final HttpCacheEntry updated = callback.update(existing);
|
||||
this.entries.put(url, updated);
|
||||
if (existing != updated) {
|
||||
keepResourceReference(updated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanResources() {
|
||||
if (this.active.get()) {
|
||||
ResourceReference ref;
|
||||
while ((ref = (ResourceReference) this.morque.poll()) != null) {
|
||||
synchronized (this) {
|
||||
this.resources.remove(ref);
|
||||
}
|
||||
ref.getResource().dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (this.active.compareAndSet(true, false)) {
|
||||
synchronized (this) {
|
||||
this.entries.clear();
|
||||
for (final ResourceReference ref: this.resources) {
|
||||
ref.getResource().dispose();
|
||||
}
|
||||
this.resources.clear();
|
||||
while (this.morque.poll() != null) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,182 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.HeaderIterator;
|
||||
import ch.boye.httpclientandroidlib.HttpEntity;
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.HttpStatus;
|
||||
import ch.boye.httpclientandroidlib.HttpVersion;
|
||||
import ch.boye.httpclientandroidlib.ProtocolVersion;
|
||||
import ch.boye.httpclientandroidlib.StatusLine;
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.message.AbstractHttpMessage;
|
||||
import ch.boye.httpclientandroidlib.message.BasicStatusLine;
|
||||
import ch.boye.httpclientandroidlib.params.BasicHttpParams;
|
||||
import ch.boye.httpclientandroidlib.params.HttpParams;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@Immutable
|
||||
final class OptionsHttp11Response extends AbstractHttpMessage implements HttpResponse {
|
||||
|
||||
private final StatusLine statusLine = new BasicStatusLine(HttpVersion.HTTP_1_1,
|
||||
HttpStatus.SC_NOT_IMPLEMENTED, "");
|
||||
private final ProtocolVersion version = HttpVersion.HTTP_1_1;
|
||||
|
||||
public StatusLine getStatusLine() {
|
||||
return statusLine;
|
||||
}
|
||||
|
||||
public void setStatusLine(final StatusLine statusline) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public void setStatusLine(final ProtocolVersion ver, final int code) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public void setStatusLine(final ProtocolVersion ver, final int code, final String reason) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public void setStatusCode(final int code) throws IllegalStateException {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public void setReasonPhrase(final String reason) throws IllegalStateException {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public HttpEntity getEntity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setEntity(final HttpEntity entity) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public Locale getLocale() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setLocale(final Locale loc) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
public ProtocolVersion getProtocolVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsHeader(final String name) {
|
||||
return this.headergroup.containsHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Header[] getHeaders(final String name) {
|
||||
return this.headergroup.getHeaders(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Header getFirstHeader(final String name) {
|
||||
return this.headergroup.getFirstHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Header getLastHeader(final String name) {
|
||||
return this.headergroup.getLastHeader(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Header[] getAllHeaders() {
|
||||
return this.headergroup.getAllHeaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHeader(final Header header) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHeader(final String name, final String value) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(final Header header) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(final String name, final String value) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeaders(final Header[] headers) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeHeader(final Header header) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeHeaders(final String name) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderIterator headerIterator() {
|
||||
return this.headergroup.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderIterator headerIterator(final String name) {
|
||||
return this.headergroup.iterator(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpParams getParams() {
|
||||
if (this.params == null) {
|
||||
this.params = new BasicHttpParams();
|
||||
}
|
||||
return this.params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParams(final HttpParams params) {
|
||||
// No-op on purpose, this class is not going to be doing any work.
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.annotation.NotThreadSafe;
|
||||
import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse;
|
||||
import ch.boye.httpclientandroidlib.util.Args;
|
||||
|
||||
/**
|
||||
* Proxies for HTTP message objects.
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
@NotThreadSafe
|
||||
class Proxies {
|
||||
|
||||
public static CloseableHttpResponse enhanceResponse(final HttpResponse original) {
|
||||
Args.notNull(original, "HTTP response");
|
||||
if (original instanceof CloseableHttpResponse) {
|
||||
return (CloseableHttpResponse) original;
|
||||
} else {
|
||||
return (CloseableHttpResponse) Proxy.newProxyInstance(
|
||||
ResponseProxyHandler.class.getClassLoader(),
|
||||
new Class<?>[] { CloseableHttpResponse.class },
|
||||
new ResponseProxyHandler(original));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,376 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.HeaderElement;
|
||||
import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest;
|
||||
import ch.boye.httpclientandroidlib.HttpRequest;
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.HttpStatus;
|
||||
import ch.boye.httpclientandroidlib.HttpVersion;
|
||||
import ch.boye.httpclientandroidlib.ProtocolVersion;
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HeaderConstants;
|
||||
import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper;
|
||||
import ch.boye.httpclientandroidlib.entity.AbstractHttpEntity;
|
||||
import ch.boye.httpclientandroidlib.entity.ContentType;
|
||||
import ch.boye.httpclientandroidlib.message.BasicHeader;
|
||||
import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
|
||||
import ch.boye.httpclientandroidlib.message.BasicStatusLine;
|
||||
import ch.boye.httpclientandroidlib.protocol.HTTP;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
@Immutable
|
||||
class RequestProtocolCompliance {
|
||||
private final boolean weakETagOnPutDeleteAllowed;
|
||||
|
||||
public RequestProtocolCompliance() {
|
||||
super();
|
||||
this.weakETagOnPutDeleteAllowed = false;
|
||||
}
|
||||
|
||||
public RequestProtocolCompliance(final boolean weakETagOnPutDeleteAllowed) {
|
||||
super();
|
||||
this.weakETagOnPutDeleteAllowed = weakETagOnPutDeleteAllowed;
|
||||
}
|
||||
|
||||
private static final List<String> disallowedWithNoCache =
|
||||
Arrays.asList(HeaderConstants.CACHE_CONTROL_MIN_FRESH, HeaderConstants.CACHE_CONTROL_MAX_STALE, HeaderConstants.CACHE_CONTROL_MAX_AGE);
|
||||
|
||||
/**
|
||||
* Test to see if the {@link HttpRequest} is HTTP1.1 compliant or not
|
||||
* and if not, we can not continue.
|
||||
*
|
||||
* @param request the HttpRequest Object
|
||||
* @return list of {@link RequestProtocolError}
|
||||
*/
|
||||
public List<RequestProtocolError> requestIsFatallyNonCompliant(final HttpRequest request) {
|
||||
final List<RequestProtocolError> theErrors = new ArrayList<RequestProtocolError>();
|
||||
|
||||
RequestProtocolError anError = requestHasWeakETagAndRange(request);
|
||||
if (anError != null) {
|
||||
theErrors.add(anError);
|
||||
}
|
||||
|
||||
if (!weakETagOnPutDeleteAllowed) {
|
||||
anError = requestHasWeekETagForPUTOrDELETEIfMatch(request);
|
||||
if (anError != null) {
|
||||
theErrors.add(anError);
|
||||
}
|
||||
}
|
||||
|
||||
anError = requestContainsNoCacheDirectiveWithFieldName(request);
|
||||
if (anError != null) {
|
||||
theErrors.add(anError);
|
||||
}
|
||||
|
||||
return theErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the {@link HttpRequest} is non-compliant but 'fixable' we go ahead and
|
||||
* fix the request here.
|
||||
*
|
||||
* @param request the request to check for compliance
|
||||
* @throws ClientProtocolException when we have trouble making the request compliant
|
||||
*/
|
||||
public void makeRequestCompliant(final HttpRequestWrapper request)
|
||||
throws ClientProtocolException {
|
||||
|
||||
if (requestMustNotHaveEntity(request)) {
|
||||
((HttpEntityEnclosingRequest) request).setEntity(null);
|
||||
}
|
||||
|
||||
verifyRequestWithExpectContinueFlagHas100continueHeader(request);
|
||||
verifyOPTIONSRequestWithBodyHasContentType(request);
|
||||
decrementOPTIONSMaxForwardsIfGreaterThen0(request);
|
||||
stripOtherFreshnessDirectivesWithNoCache(request);
|
||||
|
||||
if (requestVersionIsTooLow(request)
|
||||
|| requestMinorVersionIsTooHighMajorVersionsMatch(request)) {
|
||||
request.setProtocolVersion(HttpVersion.HTTP_1_1);
|
||||
}
|
||||
}
|
||||
|
||||
private void stripOtherFreshnessDirectivesWithNoCache(final HttpRequest request) {
|
||||
final List<HeaderElement> outElts = new ArrayList<HeaderElement>();
|
||||
boolean shouldStrip = false;
|
||||
for(final Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
|
||||
for(final HeaderElement elt : h.getElements()) {
|
||||
if (!disallowedWithNoCache.contains(elt.getName())) {
|
||||
outElts.add(elt);
|
||||
}
|
||||
if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elt.getName())) {
|
||||
shouldStrip = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!shouldStrip) {
|
||||
return;
|
||||
}
|
||||
request.removeHeaders(HeaderConstants.CACHE_CONTROL);
|
||||
request.setHeader(HeaderConstants.CACHE_CONTROL, buildHeaderFromElements(outElts));
|
||||
}
|
||||
|
||||
private String buildHeaderFromElements(final List<HeaderElement> outElts) {
|
||||
final StringBuilder newHdr = new StringBuilder("");
|
||||
boolean first = true;
|
||||
for(final HeaderElement elt : outElts) {
|
||||
if (!first) {
|
||||
newHdr.append(",");
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
newHdr.append(elt.toString());
|
||||
}
|
||||
return newHdr.toString();
|
||||
}
|
||||
|
||||
private boolean requestMustNotHaveEntity(final HttpRequest request) {
|
||||
return HeaderConstants.TRACE_METHOD.equals(request.getRequestLine().getMethod())
|
||||
&& request instanceof HttpEntityEnclosingRequest;
|
||||
}
|
||||
|
||||
private void decrementOPTIONSMaxForwardsIfGreaterThen0(final HttpRequest request) {
|
||||
if (!HeaderConstants.OPTIONS_METHOD.equals(request.getRequestLine().getMethod())) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Header maxForwards = request.getFirstHeader(HeaderConstants.MAX_FORWARDS);
|
||||
if (maxForwards == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
request.removeHeaders(HeaderConstants.MAX_FORWARDS);
|
||||
final int currentMaxForwards = Integer.parseInt(maxForwards.getValue());
|
||||
|
||||
request.setHeader(HeaderConstants.MAX_FORWARDS, Integer.toString(currentMaxForwards - 1));
|
||||
}
|
||||
|
||||
private void verifyOPTIONSRequestWithBodyHasContentType(final HttpRequest request) {
|
||||
if (!HeaderConstants.OPTIONS_METHOD.equals(request.getRequestLine().getMethod())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(request instanceof HttpEntityEnclosingRequest)) {
|
||||
return;
|
||||
}
|
||||
|
||||
addContentTypeHeaderIfMissing((HttpEntityEnclosingRequest) request);
|
||||
}
|
||||
|
||||
private void addContentTypeHeaderIfMissing(final HttpEntityEnclosingRequest request) {
|
||||
if (request.getEntity().getContentType() == null) {
|
||||
((AbstractHttpEntity) request.getEntity()).setContentType(
|
||||
ContentType.APPLICATION_OCTET_STREAM.getMimeType());
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyRequestWithExpectContinueFlagHas100continueHeader(final HttpRequest request) {
|
||||
if (request instanceof HttpEntityEnclosingRequest) {
|
||||
|
||||
if (((HttpEntityEnclosingRequest) request).expectContinue()
|
||||
&& ((HttpEntityEnclosingRequest) request).getEntity() != null) {
|
||||
add100ContinueHeaderIfMissing(request);
|
||||
} else {
|
||||
remove100ContinueHeaderIfExists(request);
|
||||
}
|
||||
} else {
|
||||
remove100ContinueHeaderIfExists(request);
|
||||
}
|
||||
}
|
||||
|
||||
private void remove100ContinueHeaderIfExists(final HttpRequest request) {
|
||||
boolean hasHeader = false;
|
||||
|
||||
final Header[] expectHeaders = request.getHeaders(HTTP.EXPECT_DIRECTIVE);
|
||||
List<HeaderElement> expectElementsThatAreNot100Continue = new ArrayList<HeaderElement>();
|
||||
|
||||
for (final Header h : expectHeaders) {
|
||||
for (final HeaderElement elt : h.getElements()) {
|
||||
if (!(HTTP.EXPECT_CONTINUE.equalsIgnoreCase(elt.getName()))) {
|
||||
expectElementsThatAreNot100Continue.add(elt);
|
||||
} else {
|
||||
hasHeader = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasHeader) {
|
||||
request.removeHeader(h);
|
||||
for (final HeaderElement elt : expectElementsThatAreNot100Continue) {
|
||||
final BasicHeader newHeader = new BasicHeader(HTTP.EXPECT_DIRECTIVE, elt.getName());
|
||||
request.addHeader(newHeader);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
expectElementsThatAreNot100Continue = new ArrayList<HeaderElement>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void add100ContinueHeaderIfMissing(final HttpRequest request) {
|
||||
boolean hasHeader = false;
|
||||
|
||||
for (final Header h : request.getHeaders(HTTP.EXPECT_DIRECTIVE)) {
|
||||
for (final HeaderElement elt : h.getElements()) {
|
||||
if (HTTP.EXPECT_CONTINUE.equalsIgnoreCase(elt.getName())) {
|
||||
hasHeader = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasHeader) {
|
||||
request.addHeader(HTTP.EXPECT_DIRECTIVE, HTTP.EXPECT_CONTINUE);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean requestMinorVersionIsTooHighMajorVersionsMatch(final HttpRequest request) {
|
||||
final ProtocolVersion requestProtocol = request.getProtocolVersion();
|
||||
if (requestProtocol.getMajor() != HttpVersion.HTTP_1_1.getMajor()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (requestProtocol.getMinor() > HttpVersion.HTTP_1_1.getMinor()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean requestVersionIsTooLow(final HttpRequest request) {
|
||||
return request.getProtocolVersion().compareToVersion(HttpVersion.HTTP_1_1) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract error information about the {@link HttpRequest} telling the 'caller'
|
||||
* that a problem occured.
|
||||
*
|
||||
* @param errorCheck What type of error should I get
|
||||
* @return The {@link HttpResponse} that is the error generated
|
||||
*/
|
||||
public HttpResponse getErrorForRequest(final RequestProtocolError errorCheck) {
|
||||
switch (errorCheck) {
|
||||
case BODY_BUT_NO_LENGTH_ERROR:
|
||||
return new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1,
|
||||
HttpStatus.SC_LENGTH_REQUIRED, ""));
|
||||
|
||||
case WEAK_ETAG_AND_RANGE_ERROR:
|
||||
return new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1,
|
||||
HttpStatus.SC_BAD_REQUEST, "Weak eTag not compatible with byte range"));
|
||||
|
||||
case WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR:
|
||||
return new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1,
|
||||
HttpStatus.SC_BAD_REQUEST,
|
||||
"Weak eTag not compatible with PUT or DELETE requests"));
|
||||
|
||||
case NO_CACHE_DIRECTIVE_WITH_FIELD_NAME:
|
||||
return new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1,
|
||||
HttpStatus.SC_BAD_REQUEST,
|
||||
"No-Cache directive MUST NOT include a field name"));
|
||||
|
||||
default:
|
||||
throw new IllegalStateException(
|
||||
"The request was compliant, therefore no error can be generated for it.");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private RequestProtocolError requestHasWeakETagAndRange(final HttpRequest request) {
|
||||
// TODO: Should these be looking at all the headers marked as Range?
|
||||
final String method = request.getRequestLine().getMethod();
|
||||
if (!(HeaderConstants.GET_METHOD.equals(method))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Header range = request.getFirstHeader(HeaderConstants.RANGE);
|
||||
if (range == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Header ifRange = request.getFirstHeader(HeaderConstants.IF_RANGE);
|
||||
if (ifRange == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String val = ifRange.getValue();
|
||||
if (val.startsWith("W/")) {
|
||||
return RequestProtocolError.WEAK_ETAG_AND_RANGE_ERROR;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private RequestProtocolError requestHasWeekETagForPUTOrDELETEIfMatch(final HttpRequest request) {
|
||||
// TODO: Should these be looking at all the headers marked as If-Match/If-None-Match?
|
||||
|
||||
final String method = request.getRequestLine().getMethod();
|
||||
if (!(HeaderConstants.PUT_METHOD.equals(method) || HeaderConstants.DELETE_METHOD
|
||||
.equals(method))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Header ifMatch = request.getFirstHeader(HeaderConstants.IF_MATCH);
|
||||
if (ifMatch != null) {
|
||||
final String val = ifMatch.getValue();
|
||||
if (val.startsWith("W/")) {
|
||||
return RequestProtocolError.WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR;
|
||||
}
|
||||
} else {
|
||||
final Header ifNoneMatch = request.getFirstHeader(HeaderConstants.IF_NONE_MATCH);
|
||||
if (ifNoneMatch == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String val2 = ifNoneMatch.getValue();
|
||||
if (val2.startsWith("W/")) {
|
||||
return RequestProtocolError.WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private RequestProtocolError requestContainsNoCacheDirectiveWithFieldName(final HttpRequest request) {
|
||||
for(final Header h : request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
|
||||
for(final HeaderElement elt : h.getElements()) {
|
||||
if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equalsIgnoreCase(elt.getName())
|
||||
&& elt.getValue() != null) {
|
||||
return RequestProtocolError.NO_CACHE_DIRECTIVE_WITH_FIELD_NAME;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
enum RequestProtocolError {
|
||||
|
||||
UNKNOWN,
|
||||
BODY_BUT_NO_LENGTH_ERROR,
|
||||
WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR,
|
||||
WEAK_ETAG_AND_RANGE_ERROR,
|
||||
NO_CACHE_DIRECTIVE_WITH_FIELD_NAME
|
||||
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.lang.ref.PhantomReference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
import ch.boye.httpclientandroidlib.client.cache.Resource;
|
||||
import ch.boye.httpclientandroidlib.util.Args;
|
||||
|
||||
@Immutable
|
||||
class ResourceReference extends PhantomReference<HttpCacheEntry> {
|
||||
|
||||
private final Resource resource;
|
||||
|
||||
public ResourceReference(final HttpCacheEntry entry, final ReferenceQueue<HttpCacheEntry> q) {
|
||||
super(entry, q);
|
||||
Args.notNull(entry.getResource(), "Resource");
|
||||
this.resource = entry.getResource();
|
||||
}
|
||||
|
||||
public Resource getResource() {
|
||||
return this.resource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.resource.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
return this.resource.equals(obj);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,311 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog;
|
||||
/* LogFactory removed by HttpClient for Android script. */
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.HeaderElement;
|
||||
import ch.boye.httpclientandroidlib.HttpMessage;
|
||||
import ch.boye.httpclientandroidlib.HttpRequest;
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.HttpStatus;
|
||||
import ch.boye.httpclientandroidlib.HttpVersion;
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HeaderConstants;
|
||||
import ch.boye.httpclientandroidlib.client.utils.DateUtils;
|
||||
import ch.boye.httpclientandroidlib.protocol.HTTP;
|
||||
|
||||
/**
|
||||
* Determines if an HttpResponse can be cached.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
@Immutable
|
||||
class ResponseCachingPolicy {
|
||||
|
||||
private static final String[] AUTH_CACHEABLE_PARAMS = {
|
||||
"s-maxage", HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE, HeaderConstants.PUBLIC
|
||||
};
|
||||
private final long maxObjectSizeBytes;
|
||||
private final boolean sharedCache;
|
||||
private final boolean neverCache1_0ResponsesWithQueryString;
|
||||
public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass());
|
||||
private static final Set<Integer> cacheableStatuses =
|
||||
new HashSet<Integer>(Arrays.asList(HttpStatus.SC_OK,
|
||||
HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION,
|
||||
HttpStatus.SC_MULTIPLE_CHOICES,
|
||||
HttpStatus.SC_MOVED_PERMANENTLY,
|
||||
HttpStatus.SC_GONE));
|
||||
private final Set<Integer> uncacheableStatuses;
|
||||
/**
|
||||
* Define a cache policy that limits the size of things that should be stored
|
||||
* in the cache to a maximum of {@link HttpResponse} bytes in size.
|
||||
*
|
||||
* @param maxObjectSizeBytes the size to limit items into the cache
|
||||
* @param sharedCache whether to behave as a shared cache (true) or a
|
||||
* non-shared/private cache (false)
|
||||
* @param neverCache1_0ResponsesWithQueryString true to never cache HTTP 1.0 responses with a query string, false
|
||||
* to cache if explicit cache headers are found.
|
||||
* @param allow303Caching if this policy is permitted to cache 303 response
|
||||
*/
|
||||
public ResponseCachingPolicy(final long maxObjectSizeBytes,
|
||||
final boolean sharedCache,
|
||||
final boolean neverCache1_0ResponsesWithQueryString,
|
||||
final boolean allow303Caching) {
|
||||
this.maxObjectSizeBytes = maxObjectSizeBytes;
|
||||
this.sharedCache = sharedCache;
|
||||
this.neverCache1_0ResponsesWithQueryString = neverCache1_0ResponsesWithQueryString;
|
||||
if (allow303Caching) {
|
||||
uncacheableStatuses = new HashSet<Integer>(
|
||||
Arrays.asList(HttpStatus.SC_PARTIAL_CONTENT));
|
||||
} else {
|
||||
uncacheableStatuses = new HashSet<Integer>(Arrays.asList(
|
||||
HttpStatus.SC_PARTIAL_CONTENT, HttpStatus.SC_SEE_OTHER));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an HttpResponse can be cached.
|
||||
*
|
||||
* @param httpMethod What type of request was this, a GET, PUT, other?
|
||||
* @param response The origin response
|
||||
* @return <code>true</code> if response is cacheable
|
||||
*/
|
||||
public boolean isResponseCacheable(final String httpMethod, final HttpResponse response) {
|
||||
boolean cacheable = false;
|
||||
|
||||
if (!HeaderConstants.GET_METHOD.equals(httpMethod)) {
|
||||
log.debug("Response was not cacheable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
final int status = response.getStatusLine().getStatusCode();
|
||||
if (cacheableStatuses.contains(status)) {
|
||||
// these response codes MAY be cached
|
||||
cacheable = true;
|
||||
} else if (uncacheableStatuses.contains(status)) {
|
||||
return false;
|
||||
} else if (unknownStatusCode(status)) {
|
||||
// a response with an unknown status code MUST NOT be
|
||||
// cached
|
||||
return false;
|
||||
}
|
||||
|
||||
final Header contentLength = response.getFirstHeader(HTTP.CONTENT_LEN);
|
||||
if (contentLength != null) {
|
||||
final int contentLengthValue = Integer.parseInt(contentLength.getValue());
|
||||
if (contentLengthValue > this.maxObjectSizeBytes) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final Header[] ageHeaders = response.getHeaders(HeaderConstants.AGE);
|
||||
|
||||
if (ageHeaders.length > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Header[] expiresHeaders = response.getHeaders(HeaderConstants.EXPIRES);
|
||||
|
||||
if (expiresHeaders.length > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Header[] dateHeaders = response.getHeaders(HTTP.DATE_HEADER);
|
||||
|
||||
if (dateHeaders.length != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Date date = DateUtils.parseDate(dateHeaders[0].getValue());
|
||||
if (date == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (final Header varyHdr : response.getHeaders(HeaderConstants.VARY)) {
|
||||
for (final HeaderElement elem : varyHdr.getElements()) {
|
||||
if ("*".equals(elem.getName())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isExplicitlyNonCacheable(response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (cacheable || isExplicitlyCacheable(response));
|
||||
}
|
||||
|
||||
private boolean unknownStatusCode(final int status) {
|
||||
if (status >= 100 && status <= 101) {
|
||||
return false;
|
||||
}
|
||||
if (status >= 200 && status <= 206) {
|
||||
return false;
|
||||
}
|
||||
if (status >= 300 && status <= 307) {
|
||||
return false;
|
||||
}
|
||||
if (status >= 400 && status <= 417) {
|
||||
return false;
|
||||
}
|
||||
if (status >= 500 && status <= 505) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean isExplicitlyNonCacheable(final HttpResponse response) {
|
||||
final Header[] cacheControlHeaders = response.getHeaders(HeaderConstants.CACHE_CONTROL);
|
||||
for (final Header header : cacheControlHeaders) {
|
||||
for (final HeaderElement elem : header.getElements()) {
|
||||
if (HeaderConstants.CACHE_CONTROL_NO_STORE.equals(elem.getName())
|
||||
|| HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elem.getName())
|
||||
|| (sharedCache && HeaderConstants.PRIVATE.equals(elem.getName()))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean hasCacheControlParameterFrom(final HttpMessage msg, final String[] params) {
|
||||
final Header[] cacheControlHeaders = msg.getHeaders(HeaderConstants.CACHE_CONTROL);
|
||||
for (final Header header : cacheControlHeaders) {
|
||||
for (final HeaderElement elem : header.getElements()) {
|
||||
for (final String param : params) {
|
||||
if (param.equalsIgnoreCase(elem.getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean isExplicitlyCacheable(final HttpResponse response) {
|
||||
if (response.getFirstHeader(HeaderConstants.EXPIRES) != null) {
|
||||
return true;
|
||||
}
|
||||
final String[] cacheableParams = { HeaderConstants.CACHE_CONTROL_MAX_AGE, "s-maxage",
|
||||
HeaderConstants.CACHE_CONTROL_MUST_REVALIDATE,
|
||||
HeaderConstants.CACHE_CONTROL_PROXY_REVALIDATE,
|
||||
HeaderConstants.PUBLIC
|
||||
};
|
||||
return hasCacheControlParameterFrom(response, cacheableParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the {@link HttpResponse} gotten from the origin is a
|
||||
* cacheable response.
|
||||
*
|
||||
* @param request the {@link HttpRequest} that generated an origin hit
|
||||
* @param response the {@link HttpResponse} from the origin
|
||||
* @return <code>true</code> if response is cacheable
|
||||
*/
|
||||
public boolean isResponseCacheable(final HttpRequest request, final HttpResponse response) {
|
||||
if (requestProtocolGreaterThanAccepted(request)) {
|
||||
log.debug("Response was not cacheable.");
|
||||
return false;
|
||||
}
|
||||
|
||||
final String[] uncacheableRequestDirectives = { HeaderConstants.CACHE_CONTROL_NO_STORE };
|
||||
if (hasCacheControlParameterFrom(request,uncacheableRequestDirectives)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.getRequestLine().getUri().contains("?")) {
|
||||
if (neverCache1_0ResponsesWithQueryString && from1_0Origin(response)) {
|
||||
log.debug("Response was not cacheable as it had a query string.");
|
||||
return false;
|
||||
} else if (!isExplicitlyCacheable(response)) {
|
||||
log.debug("Response was not cacheable as it is missing explicit caching headers.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(response)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sharedCache) {
|
||||
final Header[] authNHeaders = request.getHeaders(HeaderConstants.AUTHORIZATION);
|
||||
if (authNHeaders != null && authNHeaders.length > 0
|
||||
&& !hasCacheControlParameterFrom(response, AUTH_CACHEABLE_PARAMS)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final String method = request.getRequestLine().getMethod();
|
||||
return isResponseCacheable(method, response);
|
||||
}
|
||||
|
||||
private boolean expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(
|
||||
final HttpResponse response) {
|
||||
if (response.getFirstHeader(HeaderConstants.CACHE_CONTROL) != null) {
|
||||
return false;
|
||||
}
|
||||
final Header expiresHdr = response.getFirstHeader(HeaderConstants.EXPIRES);
|
||||
final Header dateHdr = response.getFirstHeader(HTTP.DATE_HEADER);
|
||||
if (expiresHdr == null || dateHdr == null) {
|
||||
return false;
|
||||
}
|
||||
final Date expires = DateUtils.parseDate(expiresHdr.getValue());
|
||||
final Date date = DateUtils.parseDate(dateHdr.getValue());
|
||||
if (expires == null || date == null) {
|
||||
return false;
|
||||
}
|
||||
return expires.equals(date) || expires.before(date);
|
||||
}
|
||||
|
||||
private boolean from1_0Origin(final HttpResponse response) {
|
||||
final Header via = response.getFirstHeader(HeaderConstants.VIA);
|
||||
if (via != null) {
|
||||
for(final HeaderElement elt : via.getElements()) {
|
||||
final String proto = elt.toString().split("\\s")[0];
|
||||
if (proto.contains("/")) {
|
||||
return proto.equals("HTTP/1.0");
|
||||
} else {
|
||||
return proto.equals("1.0");
|
||||
}
|
||||
}
|
||||
}
|
||||
return HttpVersion.HTTP_1_0.equals(response.getProtocolVersion());
|
||||
}
|
||||
|
||||
private boolean requestProtocolGreaterThanAccepted(final HttpRequest req) {
|
||||
return req.getProtocolVersion().compareToVersion(HttpVersion.HTTP_1_1) > 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,251 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.HeaderElement;
|
||||
import ch.boye.httpclientandroidlib.HttpEntity;
|
||||
import ch.boye.httpclientandroidlib.HttpEntityEnclosingRequest;
|
||||
import ch.boye.httpclientandroidlib.HttpRequest;
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.HttpStatus;
|
||||
import ch.boye.httpclientandroidlib.HttpVersion;
|
||||
import ch.boye.httpclientandroidlib.annotation.Immutable;
|
||||
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
|
||||
import ch.boye.httpclientandroidlib.client.cache.HeaderConstants;
|
||||
import ch.boye.httpclientandroidlib.client.methods.HttpRequestWrapper;
|
||||
import ch.boye.httpclientandroidlib.client.utils.DateUtils;
|
||||
import ch.boye.httpclientandroidlib.message.BasicHeader;
|
||||
import ch.boye.httpclientandroidlib.protocol.HTTP;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
@Immutable
|
||||
class ResponseProtocolCompliance {
|
||||
|
||||
private static final String UNEXPECTED_100_CONTINUE = "The incoming request did not contain a "
|
||||
+ "100-continue header, but the response was a Status 100, continue.";
|
||||
private static final String UNEXPECTED_PARTIAL_CONTENT = "partial content was returned for a request that did not ask for it";
|
||||
|
||||
/**
|
||||
* When we get a response from a down stream server (Origin Server)
|
||||
* we attempt to see if it is HTTP 1.1 Compliant and if not, attempt to
|
||||
* make it so.
|
||||
*
|
||||
* @param request The {@link HttpRequest} that generated an origin hit and response
|
||||
* @param response The {@link HttpResponse} from the origin server
|
||||
* @throws IOException Bad things happened
|
||||
*/
|
||||
public void ensureProtocolCompliance(final HttpRequestWrapper request, final HttpResponse response)
|
||||
throws IOException {
|
||||
if (backendResponseMustNotHaveBody(request, response)) {
|
||||
consumeBody(response);
|
||||
response.setEntity(null);
|
||||
}
|
||||
|
||||
requestDidNotExpect100ContinueButResponseIsOne(request, response);
|
||||
|
||||
transferEncodingIsNotReturnedTo1_0Client(request, response);
|
||||
|
||||
ensurePartialContentIsNotSentToAClientThatDidNotRequestIt(request, response);
|
||||
|
||||
ensure200ForOPTIONSRequestWithNoBodyHasContentLengthZero(request, response);
|
||||
|
||||
ensure206ContainsDateHeader(response);
|
||||
|
||||
ensure304DoesNotContainExtraEntityHeaders(response);
|
||||
|
||||
identityIsNotUsedInContentEncoding(response);
|
||||
|
||||
warningsWithNonMatchingWarnDatesAreRemoved(response);
|
||||
}
|
||||
|
||||
private void consumeBody(final HttpResponse response) throws IOException {
|
||||
final HttpEntity body = response.getEntity();
|
||||
if (body != null) {
|
||||
IOUtils.consume(body);
|
||||
}
|
||||
}
|
||||
|
||||
private void warningsWithNonMatchingWarnDatesAreRemoved(
|
||||
final HttpResponse response) {
|
||||
final Date responseDate = DateUtils.parseDate(response.getFirstHeader(HTTP.DATE_HEADER).getValue());
|
||||
if (responseDate == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Header[] warningHeaders = response.getHeaders(HeaderConstants.WARNING);
|
||||
|
||||
if (warningHeaders == null || warningHeaders.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Header> newWarningHeaders = new ArrayList<Header>();
|
||||
boolean modified = false;
|
||||
for(final Header h : warningHeaders) {
|
||||
for(final WarningValue wv : WarningValue.getWarningValues(h)) {
|
||||
final Date warnDate = wv.getWarnDate();
|
||||
if (warnDate == null || warnDate.equals(responseDate)) {
|
||||
newWarningHeaders.add(new BasicHeader(HeaderConstants.WARNING,wv.toString()));
|
||||
} else {
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (modified) {
|
||||
response.removeHeaders(HeaderConstants.WARNING);
|
||||
for(final Header h : newWarningHeaders) {
|
||||
response.addHeader(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void identityIsNotUsedInContentEncoding(final HttpResponse response) {
|
||||
final Header[] hdrs = response.getHeaders(HTTP.CONTENT_ENCODING);
|
||||
if (hdrs == null || hdrs.length == 0) {
|
||||
return;
|
||||
}
|
||||
final List<Header> newHeaders = new ArrayList<Header>();
|
||||
boolean modified = false;
|
||||
for (final Header h : hdrs) {
|
||||
final StringBuilder buf = new StringBuilder();
|
||||
boolean first = true;
|
||||
for (final HeaderElement elt : h.getElements()) {
|
||||
if ("identity".equalsIgnoreCase(elt.getName())) {
|
||||
modified = true;
|
||||
} else {
|
||||
if (!first) {
|
||||
buf.append(",");
|
||||
}
|
||||
buf.append(elt.toString());
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
final String newHeaderValue = buf.toString();
|
||||
if (!"".equals(newHeaderValue)) {
|
||||
newHeaders.add(new BasicHeader(HTTP.CONTENT_ENCODING, newHeaderValue));
|
||||
}
|
||||
}
|
||||
if (!modified) {
|
||||
return;
|
||||
}
|
||||
response.removeHeaders(HTTP.CONTENT_ENCODING);
|
||||
for (final Header h : newHeaders) {
|
||||
response.addHeader(h);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensure206ContainsDateHeader(final HttpResponse response) {
|
||||
if (response.getFirstHeader(HTTP.DATE_HEADER) == null) {
|
||||
response.addHeader(HTTP.DATE_HEADER, DateUtils.formatDate(new Date()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void ensurePartialContentIsNotSentToAClientThatDidNotRequestIt(final HttpRequest request,
|
||||
final HttpResponse response) throws IOException {
|
||||
if (request.getFirstHeader(HeaderConstants.RANGE) != null
|
||||
|| response.getStatusLine().getStatusCode() != HttpStatus.SC_PARTIAL_CONTENT) {
|
||||
return;
|
||||
}
|
||||
|
||||
consumeBody(response);
|
||||
throw new ClientProtocolException(UNEXPECTED_PARTIAL_CONTENT);
|
||||
}
|
||||
|
||||
private void ensure200ForOPTIONSRequestWithNoBodyHasContentLengthZero(final HttpRequest request,
|
||||
final HttpResponse response) {
|
||||
if (!request.getRequestLine().getMethod().equalsIgnoreCase(HeaderConstants.OPTIONS_METHOD)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.getFirstHeader(HTTP.CONTENT_LEN) == null) {
|
||||
response.addHeader(HTTP.CONTENT_LEN, "0");
|
||||
}
|
||||
}
|
||||
|
||||
private void ensure304DoesNotContainExtraEntityHeaders(final HttpResponse response) {
|
||||
final String[] disallowedEntityHeaders = { HeaderConstants.ALLOW, HTTP.CONTENT_ENCODING,
|
||||
"Content-Language", HTTP.CONTENT_LEN, "Content-MD5",
|
||||
"Content-Range", HTTP.CONTENT_TYPE, HeaderConstants.LAST_MODIFIED
|
||||
};
|
||||
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED) {
|
||||
for(final String hdr : disallowedEntityHeaders) {
|
||||
response.removeHeaders(hdr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean backendResponseMustNotHaveBody(final HttpRequest request, final HttpResponse backendResponse) {
|
||||
return HeaderConstants.HEAD_METHOD.equals(request.getRequestLine().getMethod())
|
||||
|| backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NO_CONTENT
|
||||
|| backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_RESET_CONTENT
|
||||
|| backendResponse.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_MODIFIED;
|
||||
}
|
||||
|
||||
private void requestDidNotExpect100ContinueButResponseIsOne(final HttpRequestWrapper request,
|
||||
final HttpResponse response) throws IOException {
|
||||
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CONTINUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
final HttpRequest originalRequest = request.getOriginal();
|
||||
if (originalRequest instanceof HttpEntityEnclosingRequest) {
|
||||
if (((HttpEntityEnclosingRequest)originalRequest).expectContinue()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
consumeBody(response);
|
||||
throw new ClientProtocolException(UNEXPECTED_100_CONTINUE);
|
||||
}
|
||||
|
||||
private void transferEncodingIsNotReturnedTo1_0Client(final HttpRequestWrapper request,
|
||||
final HttpResponse response) {
|
||||
final HttpRequest originalRequest = request.getOriginal();
|
||||
if (originalRequest.getProtocolVersion().compareToVersion(HttpVersion.HTTP_1_1) >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
removeResponseTransferEncoding(response);
|
||||
}
|
||||
|
||||
private void removeResponseTransferEncoding(final HttpResponse response) {
|
||||
response.removeHeaders("TE");
|
||||
response.removeHeaders(HTTP.TRANSFER_ENCODING);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.annotation.NotThreadSafe;
|
||||
|
||||
/**
|
||||
* A proxy class that can enhance an arbitrary {@link HttpResponse} with
|
||||
* {@link Closeable#close()} method.
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
@NotThreadSafe
|
||||
class ResponseProxyHandler implements InvocationHandler {
|
||||
|
||||
private static final Method CLOSE_METHOD;
|
||||
|
||||
static {
|
||||
try {
|
||||
CLOSE_METHOD = Closeable.class.getMethod("close");
|
||||
} catch (final NoSuchMethodException ex) {
|
||||
throw new Error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private final HttpResponse original;
|
||||
|
||||
ResponseProxyHandler(final HttpResponse original) {
|
||||
super();
|
||||
this.original = original;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
IOUtils.consume(original.getEntity());
|
||||
}
|
||||
|
||||
public Object invoke(
|
||||
final Object proxy, final Method method, final Object[] args) throws Throwable {
|
||||
if (method.equals(CLOSE_METHOD)) {
|
||||
close();
|
||||
return null;
|
||||
} else {
|
||||
try {
|
||||
return method.invoke(this.original, args);
|
||||
} catch (final InvocationTargetException ex) {
|
||||
final Throwable cause = ex.getCause();
|
||||
if (cause != null) {
|
||||
throw cause;
|
||||
} else {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
/**
|
||||
* Specifies when revalidation requests are scheduled.
|
||||
*
|
||||
* @since 4.3
|
||||
*/
|
||||
public interface SchedulingStrategy extends Closeable
|
||||
{
|
||||
/**
|
||||
* Schedule an {@link AsynchronousValidationRequest} to be executed.
|
||||
*
|
||||
* @param revalidationRequest the request to be executed; not <code>null</code>
|
||||
* @throws java.util.concurrent.RejectedExecutionException if the request could not be scheduled for execution
|
||||
*/
|
||||
void schedule(AsynchronousValidationRequest revalidationRequest);
|
||||
}
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import ch.boye.httpclientandroidlib.HttpEntity;
|
||||
import ch.boye.httpclientandroidlib.HttpRequest;
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.annotation.NotThreadSafe;
|
||||
import ch.boye.httpclientandroidlib.client.cache.InputLimit;
|
||||
import ch.boye.httpclientandroidlib.client.cache.Resource;
|
||||
import ch.boye.httpclientandroidlib.client.cache.ResourceFactory;
|
||||
import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse;
|
||||
import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
|
||||
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
@NotThreadSafe
|
||||
class SizeLimitedResponseReader {
|
||||
|
||||
private final ResourceFactory resourceFactory;
|
||||
private final long maxResponseSizeBytes;
|
||||
private final HttpRequest request;
|
||||
private final CloseableHttpResponse response;
|
||||
|
||||
private InputStream instream;
|
||||
private InputLimit limit;
|
||||
private Resource resource;
|
||||
private boolean consumed;
|
||||
|
||||
/**
|
||||
* Create an {@link HttpResponse} that is limited in size, this allows for checking
|
||||
* the size of objects that will be stored in the cache.
|
||||
*/
|
||||
public SizeLimitedResponseReader(
|
||||
final ResourceFactory resourceFactory,
|
||||
final long maxResponseSizeBytes,
|
||||
final HttpRequest request,
|
||||
final CloseableHttpResponse response) {
|
||||
super();
|
||||
this.resourceFactory = resourceFactory;
|
||||
this.maxResponseSizeBytes = maxResponseSizeBytes;
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
protected void readResponse() throws IOException {
|
||||
if (!consumed) {
|
||||
doConsume();
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureNotConsumed() {
|
||||
if (consumed) {
|
||||
throw new IllegalStateException("Response has already been consumed");
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureConsumed() {
|
||||
if (!consumed) {
|
||||
throw new IllegalStateException("Response has not been consumed");
|
||||
}
|
||||
}
|
||||
|
||||
private void doConsume() throws IOException {
|
||||
ensureNotConsumed();
|
||||
consumed = true;
|
||||
|
||||
limit = new InputLimit(maxResponseSizeBytes);
|
||||
|
||||
final HttpEntity entity = response.getEntity();
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
final String uri = request.getRequestLine().getUri();
|
||||
instream = entity.getContent();
|
||||
try {
|
||||
resource = resourceFactory.generate(uri, instream, limit);
|
||||
} finally {
|
||||
if (!limit.isReached()) {
|
||||
instream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isLimitReached() {
|
||||
ensureConsumed();
|
||||
return limit.isReached();
|
||||
}
|
||||
|
||||
Resource getResource() {
|
||||
ensureConsumed();
|
||||
return resource;
|
||||
}
|
||||
|
||||
CloseableHttpResponse getReconstructedResponse() throws IOException {
|
||||
ensureConsumed();
|
||||
final HttpResponse reconstructed = new BasicHttpResponse(response.getStatusLine());
|
||||
reconstructed.setHeaders(response.getAllHeaders());
|
||||
|
||||
final CombinedEntity combinedEntity = new CombinedEntity(resource, instream);
|
||||
final HttpEntity entity = response.getEntity();
|
||||
if (entity != null) {
|
||||
combinedEntity.setContentType(entity.getContentType());
|
||||
combinedEntity.setContentEncoding(entity.getContentEncoding());
|
||||
combinedEntity.setChunked(entity.isChunked());
|
||||
}
|
||||
reconstructed.setEntity(combinedEntity);
|
||||
return (CloseableHttpResponse) Proxy.newProxyInstance(
|
||||
ResponseProxyHandler.class.getClassLoader(),
|
||||
new Class<?>[] { CloseableHttpResponse.class },
|
||||
new ResponseProxyHandler(reconstructed) {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
response.close();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import ch.boye.httpclientandroidlib.client.cache.HttpCacheEntry;
|
||||
|
||||
/** Records a set of information describing a cached variant. */
|
||||
class Variant {
|
||||
|
||||
private final String variantKey;
|
||||
private final String cacheKey;
|
||||
private final HttpCacheEntry entry;
|
||||
|
||||
public Variant(final String variantKey, final String cacheKey, final HttpCacheEntry entry) {
|
||||
this.variantKey = variantKey;
|
||||
this.cacheKey = cacheKey;
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
public String getVariantKey() {
|
||||
return variantKey;
|
||||
}
|
||||
|
||||
public String getCacheKey() {
|
||||
return cacheKey;
|
||||
}
|
||||
|
||||
public HttpCacheEntry getEntry() {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,370 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package ch.boye.httpclientandroidlib.impl.client.cache;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.client.utils.DateUtils;
|
||||
|
||||
/** This class provides for parsing and understanding Warning headers. As
|
||||
* the Warning header can be multi-valued, but the values can contain
|
||||
* separators like commas inside quoted strings, we cannot use the regular
|
||||
* {@link Header#getElements()} call to access the values.
|
||||
*/
|
||||
class WarningValue {
|
||||
|
||||
private int offs;
|
||||
private int init_offs;
|
||||
private final String src;
|
||||
private int warnCode;
|
||||
private String warnAgent;
|
||||
private String warnText;
|
||||
private Date warnDate;
|
||||
|
||||
WarningValue(final String s) {
|
||||
this(s, 0);
|
||||
}
|
||||
|
||||
WarningValue(final String s, final int offs) {
|
||||
this.offs = this.init_offs = offs;
|
||||
this.src = s;
|
||||
consumeWarnValue();
|
||||
}
|
||||
|
||||
/** Returns an array of the parseable warning values contained
|
||||
* in the given header value, which is assumed to be a
|
||||
* Warning header. Improperly formatted warning values will be
|
||||
* skipped, in keeping with the philosophy of "ignore what you
|
||||
* cannot understand."
|
||||
* @param h Warning {@link Header} to parse
|
||||
* @return array of <code>WarnValue</code> objects
|
||||
*/
|
||||
public static WarningValue[] getWarningValues(final Header h) {
|
||||
final List<WarningValue> out = new ArrayList<WarningValue>();
|
||||
final String src = h.getValue();
|
||||
int offs = 0;
|
||||
while(offs < src.length()) {
|
||||
try {
|
||||
final WarningValue wv = new WarningValue(src, offs);
|
||||
out.add(wv);
|
||||
offs = wv.offs;
|
||||
} catch (final IllegalArgumentException e) {
|
||||
final int nextComma = src.indexOf(',', offs);
|
||||
if (nextComma == -1) {
|
||||
break;
|
||||
}
|
||||
offs = nextComma + 1;
|
||||
}
|
||||
}
|
||||
final WarningValue[] wvs = {};
|
||||
return out.toArray(wvs);
|
||||
}
|
||||
|
||||
/*
|
||||
* LWS = [CRLF] 1*( SP | HT )
|
||||
* CRLF = CR LF
|
||||
*/
|
||||
protected void consumeLinearWhitespace() {
|
||||
while(offs < src.length()) {
|
||||
switch(src.charAt(offs)) {
|
||||
case '\r':
|
||||
if (offs+2 >= src.length()
|
||||
|| src.charAt(offs+1) != '\n'
|
||||
|| (src.charAt(offs+2) != ' '
|
||||
&& src.charAt(offs+2) != '\t')) {
|
||||
return;
|
||||
}
|
||||
offs += 2;
|
||||
break;
|
||||
case ' ':
|
||||
case '\t':
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
offs++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* CHAR = <any US-ASCII character (octets 0 - 127)>
|
||||
*/
|
||||
private boolean isChar(final char c) {
|
||||
final int i = c;
|
||||
return (i >= 0 && i <= 127);
|
||||
}
|
||||
|
||||
/*
|
||||
* CTL = <any US-ASCII control character
|
||||
(octets 0 - 31) and DEL (127)>
|
||||
*/
|
||||
private boolean isControl(final char c) {
|
||||
final int i = c;
|
||||
return (i == 127 || (i >=0 && i <= 31));
|
||||
}
|
||||
|
||||
/*
|
||||
* separators = "(" | ")" | "<" | ">" | "@"
|
||||
* | "," | ";" | ":" | "\" | <">
|
||||
* | "/" | "[" | "]" | "?" | "="
|
||||
* | "{" | "}" | SP | HT
|
||||
*/
|
||||
private boolean isSeparator(final char c) {
|
||||
return (c == '(' || c == ')' || c == '<' || c == '>'
|
||||
|| c == '@' || c == ',' || c == ';' || c == ':'
|
||||
|| c == '\\' || c == '\"' || c == '/'
|
||||
|| c == '[' || c == ']' || c == '?' || c == '='
|
||||
|| c == '{' || c == '}' || c == ' ' || c == '\t');
|
||||
}
|
||||
|
||||
/*
|
||||
* token = 1*<any CHAR except CTLs or separators>
|
||||
*/
|
||||
protected void consumeToken() {
|
||||
if (!isTokenChar(src.charAt(offs))) {
|
||||
parseError();
|
||||
}
|
||||
while(offs < src.length()) {
|
||||
if (!isTokenChar(src.charAt(offs))) {
|
||||
break;
|
||||
}
|
||||
offs++;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isTokenChar(final char c) {
|
||||
return (isChar(c) && !isControl(c) && !isSeparator(c));
|
||||
}
|
||||
|
||||
private static final String TOPLABEL = "\\p{Alpha}([\\p{Alnum}-]*\\p{Alnum})?";
|
||||
private static final String DOMAINLABEL = "\\p{Alnum}([\\p{Alnum}-]*\\p{Alnum})?";
|
||||
private static final String HOSTNAME = "(" + DOMAINLABEL + "\\.)*" + TOPLABEL + "\\.?";
|
||||
private static final String IPV4ADDRESS = "\\d+\\.\\d+\\.\\d+\\.\\d+";
|
||||
private static final String HOST = "(" + HOSTNAME + ")|(" + IPV4ADDRESS + ")";
|
||||
private static final String PORT = "\\d*";
|
||||
private static final String HOSTPORT = "(" + HOST + ")(\\:" + PORT + ")?";
|
||||
private static final Pattern HOSTPORT_PATTERN = Pattern.compile(HOSTPORT);
|
||||
|
||||
protected void consumeHostPort() {
|
||||
final Matcher m = HOSTPORT_PATTERN.matcher(src.substring(offs));
|
||||
if (!m.find()) {
|
||||
parseError();
|
||||
}
|
||||
if (m.start() != 0) {
|
||||
parseError();
|
||||
}
|
||||
offs += m.end();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* warn-agent = ( host [ ":" port ] ) | pseudonym
|
||||
* pseudonym = token
|
||||
*/
|
||||
protected void consumeWarnAgent() {
|
||||
final int curr_offs = offs;
|
||||
try {
|
||||
consumeHostPort();
|
||||
warnAgent = src.substring(curr_offs, offs);
|
||||
consumeCharacter(' ');
|
||||
return;
|
||||
} catch (final IllegalArgumentException e) {
|
||||
offs = curr_offs;
|
||||
}
|
||||
consumeToken();
|
||||
warnAgent = src.substring(curr_offs, offs);
|
||||
consumeCharacter(' ');
|
||||
}
|
||||
|
||||
/*
|
||||
* quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
|
||||
* qdtext = <any TEXT except <">>
|
||||
*/
|
||||
protected void consumeQuotedString() {
|
||||
if (src.charAt(offs) != '\"') {
|
||||
parseError();
|
||||
}
|
||||
offs++;
|
||||
boolean foundEnd = false;
|
||||
while(offs < src.length() && !foundEnd) {
|
||||
final char c = src.charAt(offs);
|
||||
if (offs + 1 < src.length() && c == '\\'
|
||||
&& isChar(src.charAt(offs+1))) {
|
||||
offs += 2; // consume quoted-pair
|
||||
} else if (c == '\"') {
|
||||
foundEnd = true;
|
||||
offs++;
|
||||
} else if (c != '\"' && !isControl(c)) {
|
||||
offs++;
|
||||
} else {
|
||||
parseError();
|
||||
}
|
||||
}
|
||||
if (!foundEnd) {
|
||||
parseError();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* warn-text = quoted-string
|
||||
*/
|
||||
protected void consumeWarnText() {
|
||||
final int curr = offs;
|
||||
consumeQuotedString();
|
||||
warnText = src.substring(curr, offs);
|
||||
}
|
||||
|
||||
private static final String MONTH = "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec";
|
||||
private static final String WEEKDAY = "Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday";
|
||||
private static final String WKDAY = "Mon|Tue|Wed|Thu|Fri|Sat|Sun";
|
||||
private static final String TIME = "\\d{2}:\\d{2}:\\d{2}";
|
||||
private static final String DATE3 = "(" + MONTH + ") ( |\\d)\\d";
|
||||
private static final String DATE2 = "\\d{2}-(" + MONTH + ")-\\d{2}";
|
||||
private static final String DATE1 = "\\d{2} (" + MONTH + ") \\d{4}";
|
||||
private static final String ASCTIME_DATE = "(" + WKDAY + ") (" + DATE3 + ") (" + TIME + ") \\d{4}";
|
||||
private static final String RFC850_DATE = "(" + WEEKDAY + "), (" + DATE2 + ") (" + TIME + ") GMT";
|
||||
private static final String RFC1123_DATE = "(" + WKDAY + "), (" + DATE1 + ") (" + TIME + ") GMT";
|
||||
private static final String HTTP_DATE = "(" + RFC1123_DATE + ")|(" + RFC850_DATE + ")|(" + ASCTIME_DATE + ")";
|
||||
private static final String WARN_DATE = "\"(" + HTTP_DATE + ")\"";
|
||||
private static final Pattern WARN_DATE_PATTERN = Pattern.compile(WARN_DATE);
|
||||
|
||||
/*
|
||||
* warn-date = <"> HTTP-date <">
|
||||
*/
|
||||
protected void consumeWarnDate() {
|
||||
final int curr = offs;
|
||||
final Matcher m = WARN_DATE_PATTERN.matcher(src.substring(offs));
|
||||
if (!m.lookingAt()) {
|
||||
parseError();
|
||||
}
|
||||
offs += m.end();
|
||||
warnDate = DateUtils.parseDate(src.substring(curr+1,offs-1));
|
||||
}
|
||||
|
||||
/*
|
||||
* warning-value = warn-code SP warn-agent SP warn-text [SP warn-date]
|
||||
*/
|
||||
protected void consumeWarnValue() {
|
||||
consumeLinearWhitespace();
|
||||
consumeWarnCode();
|
||||
consumeWarnAgent();
|
||||
consumeWarnText();
|
||||
if (offs + 1 < src.length() && src.charAt(offs) == ' ' && src.charAt(offs+1) == '\"') {
|
||||
consumeCharacter(' ');
|
||||
consumeWarnDate();
|
||||
}
|
||||
consumeLinearWhitespace();
|
||||
if (offs != src.length()) {
|
||||
consumeCharacter(',');
|
||||
}
|
||||
}
|
||||
|
||||
protected void consumeCharacter(final char c) {
|
||||
if (offs + 1 > src.length()
|
||||
|| c != src.charAt(offs)) {
|
||||
parseError();
|
||||
}
|
||||
offs++;
|
||||
}
|
||||
|
||||
/*
|
||||
* warn-code = 3DIGIT
|
||||
*/
|
||||
protected void consumeWarnCode() {
|
||||
if (offs + 4 > src.length()
|
||||
|| !Character.isDigit(src.charAt(offs))
|
||||
|| !Character.isDigit(src.charAt(offs + 1))
|
||||
|| !Character.isDigit(src.charAt(offs + 2))
|
||||
|| src.charAt(offs + 3) != ' ') {
|
||||
parseError();
|
||||
}
|
||||
warnCode = Integer.parseInt(src.substring(offs,offs+3));
|
||||
offs += 4;
|
||||
}
|
||||
|
||||
private void parseError() {
|
||||
final String s = src.substring(init_offs);
|
||||
throw new IllegalArgumentException("Bad warn code \"" + s + "\"");
|
||||
}
|
||||
|
||||
/** Returns the 3-digit code associated with this warning.
|
||||
* @return <code>int</code>
|
||||
*/
|
||||
public int getWarnCode() { return warnCode; }
|
||||
|
||||
/** Returns the "warn-agent" string associated with this warning,
|
||||
* which is either the name or pseudonym of the server that added
|
||||
* this particular Warning header.
|
||||
* @return {@link String}
|
||||
*/
|
||||
public String getWarnAgent() { return warnAgent; }
|
||||
|
||||
/** Returns the human-readable warning text for this warning. Note
|
||||
* that the original quoted-string is returned here, including
|
||||
* escaping for any contained characters. In other words, if the
|
||||
* header was:
|
||||
* <pre>
|
||||
* Warning: 110 fred "Response is stale"
|
||||
* </pre>
|
||||
* then this method will return <code>"\"Response is stale\""</code>
|
||||
* (surrounding quotes included).
|
||||
* @return {@link String}
|
||||
*/
|
||||
public String getWarnText() { return warnText; }
|
||||
|
||||
/** Returns the date and time when this warning was added, or
|
||||
* <code>null</code> if a warning date was not supplied in the
|
||||
* header.
|
||||
* @return {@link Date}
|
||||
*/
|
||||
public Date getWarnDate() { return warnDate; }
|
||||
|
||||
/** Formats a <code>WarningValue</code> as a {@link String}
|
||||
* suitable for including in a header. For example, you can:
|
||||
* <pre>
|
||||
* WarningValue wv = ...;
|
||||
* HttpResponse resp = ...;
|
||||
* resp.addHeader("Warning", wv.toString());
|
||||
* </pre>
|
||||
* @return {@link String}
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
if (warnDate != null) {
|
||||
return String.format("%d %s %s \"%s\"", warnCode,
|
||||
warnAgent, warnText, DateUtils.formatDate(warnDate));
|
||||
} else {
|
||||
return String.format("%d %s %s", warnCode, warnAgent, warnText);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you 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.
|
||||
====================================================================
|
||||
|
||||
This software consists of voluntary contributions made by many
|
||||
individuals on behalf of the Apache Software Foundation. For more
|
||||
information on the Apache Software Foundation, please see
|
||||
<http://www.apache.org/>.
|
||||
-->
|
||||
</head>
|
||||
<body bgcolor="white">
|
||||
|
||||
<p>
|
||||
This package contains a cache module that can be used for HTTP/1.1
|
||||
client-side caching. The primary classes in this package are the
|
||||
{@link org.apache.http.impl.client.cache.CachingHttpClient},
|
||||
which is a drop-in replacement for
|
||||
a {@link org.apache.http.impl.client.DefaultHttpClient} that adds
|
||||
caching, and the {@link org.apache.http.impl.client.cache.CacheConfig}
|
||||
class that can be used for configuring it.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue