You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2010/04/30 23:00:10 UTC
svn commit: r939814 [2/6] - in /httpcomponents/httpclient/trunk: ./
httpclient-cache/ httpclient-cache/src/ httpclient-cache/src/main/
httpclient-cache/src/main/java/ httpclient-cache/src/main/java/org/
httpclient-cache/src/main/java/org/apache/ httpcl...
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachedHttpResponseGenerator.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachedHttpResponseGenerator.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachedHttpResponseGenerator.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachedHttpResponseGenerator.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,89 @@
+/*
+ * ====================================================================
+ * 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 org.apache.http.client.cache.impl;
+
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicHttpResponse;
+
+/**
+ * Rebuilds an {@link HttpResponse} from a {@link CacheEntry}
+ *
+ * @since 4.1
+ */
+public class CachedHttpResponseGenerator {
+
+ /**
+ * @param entry
+ * {@link CacheEntry} to transform into an {@link HttpResponse}
+ * @return {@link HttpResponse} that was constructed
+ */
+ HttpResponse generateResponse(CacheEntry entry) {
+
+ HttpResponse response = new BasicHttpResponse(CachingHttpClient.HTTP_1_1, entry
+ .getStatusCode(), entry.getReasonPhrase());
+
+ if (entry.getStatusCode() != HttpStatus.SC_NOT_MODIFIED) {
+ HttpEntity entity = new ByteArrayEntity(entry.getBody());
+ response.setEntity(entity);
+ response.setHeaders(entry.getAllHeaders());
+ addMissingContentLengthHeader(response, entity);
+ }
+
+ long age = entry.getCurrentAgeSecs();
+ if (age > 0) {
+ if (age >= (long) Integer.MAX_VALUE) {
+ response.setHeader(HeaderConstants.AGE, "2147483648");
+ } else {
+ response.setHeader(HeaderConstants.AGE, "" + ((int) age));
+ }
+ }
+
+ return response;
+ }
+
+ private void addMissingContentLengthHeader(HttpResponse response, HttpEntity entity) {
+ if (transferEncodingIsPresent(response))
+ return;
+
+ Header contentLength = response.getFirstHeader(HeaderConstants.CONTENT_LENGTH);
+ if (contentLength == null) {
+ contentLength = new BasicHeader(HeaderConstants.CONTENT_LENGTH, Long.toString(entity
+ .getContentLength()));
+ response.setHeader(contentLength);
+ }
+ }
+
+ private boolean transferEncodingIsPresent(HttpResponse response) {
+ Header hdr = response.getFirstHeader(HeaderConstants.TRANSFER_ENCODING);
+ return hdr != null;
+ }
+}
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachedResponseSuitabilityChecker.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachedResponseSuitabilityChecker.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachedResponseSuitabilityChecker.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachedResponseSuitabilityChecker.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,139 @@
+/*
+ * ====================================================================
+ * 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 org.apache.http.client.cache.impl;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.Header;
+import org.apache.http.HeaderElement;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+
+/**
+ * Determines whether a given response can be cached.
+ *
+ * @since 4.1
+ */
+public class CachedResponseSuitabilityChecker {
+
+ private static final Log LOG = LogFactory.getLog(CachedResponseSuitabilityChecker.class);
+
+ /**
+ * @param host
+ * {@link HttpHost}
+ * @param request
+ * {@link HttpRequest}
+ * @param entry
+ * {@link CacheEntry}
+ * @return boolean yes/no answer
+ */
+ public boolean canCachedResponseBeUsed(HttpHost host, HttpRequest request, CacheEntry entry) {
+ if (!entry.isResponseFresh()) {
+ LOG.debug("CachedResponseSuitabilityChecker: Cache Entry was NOT fresh enough");
+ return false;
+ }
+
+ if (!entry.contentLengthHeaderMatchesActualLength()) {
+ LOG
+ .debug("CachedResponseSuitabilityChecker: Cache Entry Content Length and header information DO NOT match.");
+ return false;
+ }
+
+ if (entry.modifiedSince(request)) {
+ LOG
+ .debug("CachedResponseSuitabilityChecker: Cache Entry modified times didn't line up. Cache Entry should NOT be used.");
+ return false;
+ }
+
+ for (Header ccHdr : request.getHeaders(HeaderConstants.CACHE_CONTROL)) {
+ for (HeaderElement elt : ccHdr.getElements()) {
+ if (HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elt.getName())) {
+ LOG
+ .debug("CachedResponseSuitabilityChecker: Response contained NO CACHE directive, cache was NOT suitable.");
+ return false;
+ }
+
+ if (HeaderConstants.CACHE_CONTROL_NO_STORE.equals(elt.getName())) {
+ LOG
+ .debug("CachedResponseSuitabilityChecker: Response contained NO SORE directive, cache was NOT suitable.");
+ return false;
+ }
+
+ if (HeaderConstants.CACHE_CONTROL_MAX_AGE.equals(elt.getName())) {
+ try {
+ int maxage = Integer.parseInt(elt.getValue());
+ if (entry.getCurrentAgeSecs() > maxage) {
+ LOG
+ .debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable.");
+ return false;
+ }
+ } catch (NumberFormatException nfe) {
+ // err conservatively
+ LOG
+ .debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable.");
+ return false;
+ }
+ }
+
+ if (HeaderConstants.CACHE_CONTROL_MAX_STALE.equals(elt.getName())) {
+ try {
+ int maxstale = Integer.parseInt(elt.getValue());
+ if (entry.getFreshnessLifetimeSecs() > maxstale) {
+ LOG
+ .debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable.");
+ return false;
+ }
+ } catch (NumberFormatException nfe) {
+ // err conservatively
+ LOG
+ .debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable.");
+ return false;
+ }
+ }
+
+ if (HeaderConstants.CACHE_CONTROL_MIN_FRESH.equals(elt.getName())) {
+ try {
+ int minfresh = Integer.parseInt(elt.getValue());
+ if (entry.getFreshnessLifetimeSecs() < minfresh) {
+ LOG
+ .debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable.");
+ return false;
+ }
+ } catch (NumberFormatException nfe) {
+ // err conservatively
+ LOG
+ .debug("CachedResponseSuitabilityChecker: Response from cache was NOT suitable.");
+ return false;
+ }
+ }
+ }
+ }
+
+ LOG.debug("CachedResponseSuitabilityChecker: Response from cache was suitable.");
+ return true;
+ }
+}
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachingHttpClient.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachingHttpClient.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachingHttpClient.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CachingHttpClient.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,444 @@
+/*
+ * ====================================================================
+ * 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 org.apache.http.client.cache.impl;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.ProtocolException;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.RequestLine;
+import org.apache.http.StatusLine;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.cache.HttpCacheOperationException;
+import org.apache.http.client.cache.HttpCacheUpdateCallback;
+import org.apache.http.client.cache.HttpCache;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.message.BasicStatusLine;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HttpContext;
+
+/**
+ * @since 4.1
+ */
+public class CachingHttpClient implements HttpClient {
+
+ private final static int MAX_CACHE_ENTRIES = 1000;
+ private final static int DEFAULT_MAX_OBJECT_SIZE_BYTES = 8192;
+
+ public static final ProtocolVersion HTTP_1_1 = new ProtocolVersion("HTTP", 1, 1);
+
+ private final static boolean SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS = false;
+
+ private HttpClient backend;
+ private ResponseCachingPolicy responseCachingPolicy;
+ private CacheEntryGenerator cacheEntryGenerator;
+ private URIExtractor uriExtractor;
+ private HttpCache<CacheEntry> responseCache;
+ private CachedHttpResponseGenerator responseGenerator;
+ private CacheInvalidator cacheInvalidator;
+ private CacheableRequestPolicy cacheableRequestPolicy;
+ private CachedResponseSuitabilityChecker suitabilityChecker;
+
+ private ConditionalRequestBuilder conditionalRequestBuilder;
+ private int maxObjectSizeBytes = DEFAULT_MAX_OBJECT_SIZE_BYTES;
+ private CacheEntryUpdater cacheEntryUpdater;
+
+ private volatile long cacheHits;
+ private volatile long cacheMisses;
+ private volatile long cacheUpdates;
+ private ResponseProtocolCompliance responseCompliance;
+ private RequestProtocolCompliance requestCompliance;
+ private static final Log LOG = LogFactory.getLog(CachingHttpClient.class);
+
+ public CachingHttpClient() {
+ this.backend = new DefaultHttpClient();
+ this.responseCachingPolicy = new ResponseCachingPolicy(maxObjectSizeBytes);
+ this.cacheEntryGenerator = new CacheEntryGenerator();
+ this.uriExtractor = new URIExtractor();
+ this.responseCache = new BasicHttpCache(MAX_CACHE_ENTRIES);
+ this.responseGenerator = new CachedHttpResponseGenerator();
+ this.cacheInvalidator = new CacheInvalidator(this.uriExtractor, this.responseCache);
+ this.cacheableRequestPolicy = new CacheableRequestPolicy();
+ this.suitabilityChecker = new CachedResponseSuitabilityChecker();
+ this.conditionalRequestBuilder = new ConditionalRequestBuilder();
+ this.cacheEntryUpdater = new CacheEntryUpdater();
+ this.responseCompliance = new ResponseProtocolCompliance();
+ this.requestCompliance = new RequestProtocolCompliance();
+ }
+
+ public CachingHttpClient(HttpCache<CacheEntry> cache, int maxObjectSizeBytes) {
+ this.responseCache = cache;
+
+ this.backend = new DefaultHttpClient();
+ this.responseCachingPolicy = new ResponseCachingPolicy(maxObjectSizeBytes);
+ this.cacheEntryGenerator = new CacheEntryGenerator();
+ this.uriExtractor = new URIExtractor();
+ this.responseGenerator = new CachedHttpResponseGenerator();
+ this.cacheInvalidator = new CacheInvalidator(this.uriExtractor, this.responseCache);
+ this.cacheableRequestPolicy = new CacheableRequestPolicy();
+ this.suitabilityChecker = new CachedResponseSuitabilityChecker();
+ this.conditionalRequestBuilder = new ConditionalRequestBuilder();
+ this.cacheEntryUpdater = new CacheEntryUpdater();
+ this.responseCompliance = new ResponseProtocolCompliance();
+ this.requestCompliance = new RequestProtocolCompliance();
+ }
+
+ public CachingHttpClient(HttpClient client, HttpCache<CacheEntry> cache, int maxObjectSizeBytes) {
+ this.responseCache = cache;
+
+ this.backend = client;
+ this.responseCachingPolicy = new ResponseCachingPolicy(maxObjectSizeBytes);
+ this.cacheEntryGenerator = new CacheEntryGenerator();
+ this.uriExtractor = new URIExtractor();
+ this.responseGenerator = new CachedHttpResponseGenerator();
+ this.cacheInvalidator = new CacheInvalidator(this.uriExtractor, this.responseCache);
+ this.cacheableRequestPolicy = new CacheableRequestPolicy();
+ this.suitabilityChecker = new CachedResponseSuitabilityChecker();
+ this.conditionalRequestBuilder = new ConditionalRequestBuilder();
+ this.cacheEntryUpdater = new CacheEntryUpdater();
+ this.maxObjectSizeBytes = maxObjectSizeBytes;
+ this.responseCompliance = new ResponseProtocolCompliance();
+ this.requestCompliance = new RequestProtocolCompliance();
+ }
+
+ public CachingHttpClient(HttpClient backend, ResponseCachingPolicy responseCachingPolicy,
+ CacheEntryGenerator cacheEntryGenerator, URIExtractor uriExtractor,
+ HttpCache<CacheEntry> responseCache, CachedHttpResponseGenerator responseGenerator,
+ CacheInvalidator cacheInvalidator, CacheableRequestPolicy cacheableRequestPolicy,
+ CachedResponseSuitabilityChecker suitabilityChecker,
+ ConditionalRequestBuilder conditionalRequestBuilder, CacheEntryUpdater entryUpdater,
+ ResponseProtocolCompliance responseCompliance,
+ RequestProtocolCompliance requestCompliance) {
+ this.backend = backend;
+ this.responseCachingPolicy = responseCachingPolicy;
+ this.cacheEntryGenerator = cacheEntryGenerator;
+ this.uriExtractor = uriExtractor;
+ this.responseCache = responseCache;
+ this.responseGenerator = responseGenerator;
+ this.cacheInvalidator = cacheInvalidator;
+ this.cacheableRequestPolicy = cacheableRequestPolicy;
+ this.suitabilityChecker = suitabilityChecker;
+ this.conditionalRequestBuilder = conditionalRequestBuilder;
+ this.cacheEntryUpdater = entryUpdater;
+ this.responseCompliance = responseCompliance;
+ this.requestCompliance = requestCompliance;
+ }
+
+ public long getCacheHits() {
+ return cacheHits;
+ }
+
+ public long getCacheMisses() {
+ return cacheMisses;
+ }
+
+ public long getCacheUpdates() {
+ return cacheUpdates;
+ }
+
+ public HttpResponse execute(HttpHost target, HttpRequest request) throws IOException {
+ HttpContext defaultContext = null;
+ return execute(target, request, defaultContext);
+ }
+
+ public <T> T execute(HttpHost target, HttpRequest request,
+ ResponseHandler<? extends T> responseHandler) throws IOException {
+ return execute(target, request, responseHandler, null);
+ }
+
+ public <T> T execute(HttpHost target, HttpRequest request,
+ ResponseHandler<? extends T> responseHandler, HttpContext context) throws IOException {
+ HttpResponse resp = execute(target, request, context);
+ return responseHandler.handleResponse(resp);
+ }
+
+ public HttpResponse execute(HttpUriRequest request) throws IOException {
+ HttpContext context = null;
+ return execute(request, context);
+ }
+
+ public HttpResponse execute(HttpUriRequest request, HttpContext context) throws IOException {
+ URI uri = request.getURI();
+ HttpHost httpHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
+ return execute(httpHost, request, context);
+ }
+
+ public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler)
+ throws IOException {
+ return execute(request, responseHandler, null);
+ }
+
+ public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler,
+ HttpContext context) throws IOException {
+ HttpResponse resp = execute(request, context);
+ return responseHandler.handleResponse(resp);
+ }
+
+ public ClientConnectionManager getConnectionManager() {
+ return backend.getConnectionManager();
+ }
+
+ public HttpParams getParams() {
+ return backend.getParams();
+ }
+
+ protected Date getCurrentDate() {
+ return new Date();
+ }
+
+ protected CacheEntry getCacheEntry(HttpHost target, HttpRequest request) {
+ String uri = uriExtractor.getURI(target, request);
+ CacheEntry entry = null;
+ try {
+ entry = responseCache.getEntry(uri);
+ } catch (HttpCacheOperationException probablyIgnore) {
+ // TODO: do something useful with this exception
+ }
+
+ if (entry == null || !entry.hasVariants())
+ return entry;
+
+ String variantUri = uriExtractor.getVariantURI(target, request, entry);
+ try {
+ return responseCache.getEntry(variantUri);
+ } catch (HttpCacheOperationException probablyIgnore) {
+ return null;
+ }
+ }
+
+ public HttpResponse execute(HttpHost target, HttpRequest request, HttpContext context)
+ throws IOException {
+
+ if (clientRequestsOurOptions(request)) {
+ return new OptionsHttp11Response();
+ }
+
+ List<RequestProtocolError> fatalError = requestCompliance
+ .requestIsFatallyNonCompliant(request);
+
+ for (RequestProtocolError error : fatalError) {
+ return requestCompliance.getErrorForRequest(error);
+ }
+
+ try {
+ request = requestCompliance.makeRequestCompliant(request);
+ } catch (ProtocolException e) {
+ throw new ClientProtocolException(e);
+ }
+
+ cacheInvalidator.flushInvalidatedCacheEntries(target, request);
+
+ if (!cacheableRequestPolicy.isServableFromCache(request)) {
+ return callBackend(target, request, context);
+ }
+
+ CacheEntry entry = getCacheEntry(target, request);
+ if (entry == null) {
+ cacheMisses++;
+ LOG.debug("CLIENT: Cache Miss.");
+ return callBackend(target, request, context);
+ }
+
+ LOG.debug("CLIENT: Cache HIT.");
+ cacheHits++;
+
+ if (suitabilityChecker.canCachedResponseBeUsed(target, request, entry)) {
+ return responseGenerator.generateResponse(entry);
+ }
+
+ if (entry.isRevalidatable()) {
+ LOG.debug("CLIENT: Revalidate the entry.");
+
+ try {
+ return revalidateCacheEntry(target, request, context, entry);
+ } catch (IOException ioex) {
+ HttpResponse response = responseGenerator.generateResponse(entry);
+ response.addHeader(HeaderConstants.WARNING, "111 Revalidation Failed - "
+ + ioex.getMessage());
+ return response;
+ } catch (ProtocolException e) {
+ throw new ClientProtocolException(e);
+ }
+ }
+ return callBackend(target, request, context);
+ }
+
+ private boolean clientRequestsOurOptions(HttpRequest request) {
+ 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;
+ }
+
+ protected HttpResponse callBackend(HttpHost target, HttpRequest request, HttpContext context)
+ throws IOException {
+
+ Date requestDate = getCurrentDate();
+
+ try {
+ LOG.debug("CLIENT: Calling the backend.");
+ HttpResponse backendResponse = backend.execute(target, request, context);
+ return handleBackendResponse(target, request, requestDate, getCurrentDate(),
+ backendResponse);
+ } catch (ClientProtocolException cpex) {
+ throw cpex;
+ } catch (IOException ex) {
+ StatusLine status = new BasicStatusLine(HTTP_1_1, HttpStatus.SC_SERVICE_UNAVAILABLE, ex
+ .getMessage());
+ return new BasicHttpResponse(status);
+ }
+
+ }
+
+ protected HttpResponse revalidateCacheEntry(HttpHost target, HttpRequest request,
+ HttpContext context, CacheEntry cacheEntry) throws IOException, ProtocolException {
+ HttpRequest conditionalRequest = conditionalRequestBuilder.buildConditionalRequest(request,
+ cacheEntry);
+ Date requestDate = getCurrentDate();
+
+ HttpResponse backendResponse = backend.execute(target, conditionalRequest, context);
+
+ Date responseDate = getCurrentDate();
+
+ int statusCode = backendResponse.getStatusLine().getStatusCode();
+ if (statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_OK) {
+ cacheUpdates++;
+ cacheEntryUpdater.updateCacheEntry(cacheEntry, requestDate, responseDate,
+ backendResponse);
+ storeInCache(target, request, cacheEntry);
+ return responseGenerator.generateResponse(cacheEntry);
+ }
+
+ return handleBackendResponse(target, conditionalRequest, requestDate, responseDate,
+ backendResponse);
+ }
+
+ protected void storeInCache(HttpHost target, HttpRequest request, CacheEntry entry) {
+ if (entry.hasVariants()) {
+ try {
+ String uri = uriExtractor.getURI(target, request);
+ HttpCacheUpdateCallback<CacheEntry> callback = storeVariantEntry(target, request, entry);
+ responseCache.updateCacheEntry(uri, callback);
+ } catch (HttpCacheOperationException probablyIgnore) {
+ // TODO: do something useful with this exception
+ }
+ } else {
+ storeNonVariantEntry(target, request, entry);
+ }
+ }
+
+ private void storeNonVariantEntry(HttpHost target, HttpRequest req, CacheEntry entry) {
+ String uri = uriExtractor.getURI(target, req);
+ try {
+ responseCache.putEntry(uri, entry);
+ } catch (HttpCacheOperationException probablyIgnore) {
+ // TODO: do something useful with this exception
+ }
+ }
+
+ protected HttpCacheUpdateCallback<CacheEntry> storeVariantEntry(final HttpHost target, final HttpRequest req,
+ final CacheEntry entry) {
+ return new HttpCacheUpdateCallback<CacheEntry>() {
+ public CacheEntry getUpdatedEntry(CacheEntry existing) throws HttpCacheOperationException {
+
+ String variantURI = uriExtractor.getVariantURI(target, req, entry);
+ responseCache.putEntry(variantURI, entry);
+
+ if (existing != null) {
+ existing.addVariantURI(variantURI);
+ return existing;
+ } else {
+ entry.addVariantURI(variantURI);
+ return entry;
+ }
+ }
+ };
+ }
+
+ protected HttpResponse handleBackendResponse(HttpHost target, HttpRequest request,
+ Date requestDate, Date responseDate, HttpResponse backendResponse) throws IOException {
+
+ LOG.debug("CLIENT: Handling Backend response.");
+ responseCompliance.ensureProtocolCompliance(request, backendResponse);
+
+ boolean cacheable = responseCachingPolicy.isResponseCacheable(request, backendResponse);
+
+ if (cacheable) {
+
+ SizeLimitedResponseReader responseReader = getResponseReader(backendResponse);
+
+ if (responseReader.isResponseTooLarge()) {
+ return responseReader.getReconstructedResponse();
+ }
+
+ CacheEntry entry = cacheEntryGenerator.generateEntry(requestDate, responseDate,
+ backendResponse, responseReader.getResponseBytes());
+ storeInCache(target, request, entry);
+ return responseGenerator.generateResponse(entry);
+ }
+
+ String uri = uriExtractor.getURI(target, request);
+ try {
+ responseCache.removeEntry(uri);
+ } catch (HttpCacheOperationException coe) {
+ // TODO: track failed state
+ }
+ return backendResponse;
+ }
+
+ protected SizeLimitedResponseReader getResponseReader(HttpResponse backEndResponse)
+ throws IOException {
+ return new SizeLimitedResponseReader(maxObjectSizeBytes, backEndResponse);
+ }
+
+ public boolean supportsRangeAndContentRangeHeaders() {
+ return SUPPORTS_RANGE_AND_CONTENT_RANGE_HEADERS;
+ }
+
+}
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CombinedInputStream.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CombinedInputStream.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CombinedInputStream.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/CombinedInputStream.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,73 @@
+/*
+ * ====================================================================
+ * 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 org.apache.http.client.cache.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A class that presents two inputstreams as a single stream
+ *
+ * @since 4.1
+ */
+class CombinedInputStream extends InputStream {
+
+ private InputStream inputStream1;
+ private InputStream inputStream2;
+
+ /**
+ *
+ * @param inputStream1
+ * First stream to read
+ * @param inputStream2
+ * Second stream to read
+ */
+ public CombinedInputStream(InputStream inputStream1, InputStream inputStream2) {
+ if (inputStream1 == null)
+ throw new IllegalArgumentException("inputStream1 cannot be null");
+ if (inputStream2 == null)
+ throw new IllegalArgumentException("inputStream2 cannot be null");
+
+ this.inputStream1 = inputStream1;
+ this.inputStream2 = inputStream2;
+ }
+
+ @Override
+ public int available() throws IOException {
+ return inputStream1.available() + inputStream2.available();
+ }
+
+ @Override
+ public int read() throws IOException {
+ int result = inputStream1.read();
+
+ if (result == -1)
+ result = inputStream2.read();
+
+ return result;
+ }
+}
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ConditionalRequestBuilder.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ConditionalRequestBuilder.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ConditionalRequestBuilder.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ConditionalRequestBuilder.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,54 @@
+/*
+ * ====================================================================
+ * 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 org.apache.http.client.cache.impl;
+
+import org.apache.http.Header;
+import org.apache.http.HttpRequest;
+import org.apache.http.ProtocolException;
+import org.apache.http.impl.client.RequestWrapper;
+
+/**
+ * @since 4.1
+ */
+public class ConditionalRequestBuilder {
+
+ public HttpRequest buildConditionalRequest(HttpRequest request, CacheEntry cacheEntry)
+ throws ProtocolException {
+ RequestWrapper wrapperRequest = new RequestWrapper(request);
+ wrapperRequest.resetHeaders();
+ Header eTag = cacheEntry.getFirstHeader(HeaderConstants.ETAG);
+ if (eTag != null) {
+ wrapperRequest.setHeader("If-None-Match", eTag.getValue());
+ } else {
+ Header lastModified = cacheEntry.getFirstHeader(HeaderConstants.LAST_MODIFIED);
+ wrapperRequest.setHeader("If-Modified-Since", lastModified.getValue());
+ }
+ return wrapperRequest;
+
+ }
+
+}
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/DefaultCacheEntrySerializer.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/DefaultCacheEntrySerializer.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/DefaultCacheEntrySerializer.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/DefaultCacheEntrySerializer.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,109 @@
+/*
+ * ====================================================================
+ * 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 org.apache.http.client.cache.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+
+import org.apache.http.client.cache.HttpCacheEntrySerializer;
+
+/**
+ * {@link HttpCacheEntrySerializer} implementation that uses the default (native)
+ * serialization.
+ *
+ * @see java.io.Serializable
+ *
+ * @since 4.1
+ */
+public class DefaultCacheEntrySerializer implements HttpCacheEntrySerializer<CacheEntry> {
+
+ public void writeTo(CacheEntry cacheEntry, OutputStream os) throws IOException {
+
+ ObjectOutputStream oos = null;
+ try {
+ oos = new ObjectOutputStream(os);
+ // write CacheEntry
+ oos.writeObject(cacheEntry);
+ // write headers as a String [][]
+ // Header [] headers = cacheEntry.getAllHeaders();
+ // if(null == headers || headers.length < 1) return;
+ // String [][] sheaders = new String[headers.length][2];
+ // for(int i=0; i < headers.length; i++) {
+ // sheaders[i][0] = headers[i].getName();
+ // sheaders[i][1] = headers[i].getValue();
+ // }
+ // oos.writeObject(sheaders);
+ } finally {
+ try {
+ oos.close();
+ } catch (Exception ignore) {
+ }
+ try {
+ os.close();
+ } catch (Exception ignore) {
+ }
+ }
+
+ }
+
+ public CacheEntry readFrom(InputStream is) throws IOException {
+
+ ObjectInputStream ois = null;
+ try {
+ ois = new ObjectInputStream(is);
+ // read CacheEntry
+ CacheEntry cacheEntry = (CacheEntry) ois.readObject();
+ // read headers as a String [][]
+ // String [][] sheaders = (String[][])ois.readObject();
+ // if(null == sheaders || sheaders.length < 1) return cacheEntry;
+ // BasicHeader [] headers = new BasicHeader[sheaders.length];
+ // for(int i=0; i < sheaders.length; i++) {
+ // String [] sheader = sheaders[i];
+ // headers[i] = new BasicHeader(sheader[0], sheader[1]);
+ // }
+ // cacheEntry.setResponseHeaders(headers);
+ return cacheEntry;
+ } catch (ClassNotFoundException cnfe) {
+ // CacheEntry should be known, it not we have a runtime issue
+ throw new RuntimeException(cnfe);
+ } finally {
+ try {
+ ois.close();
+ } catch (Exception ignore) {
+ }
+ try {
+ is.close();
+ } catch (Exception ignore) {
+ }
+ }
+
+ }
+
+}
\ No newline at end of file
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/HeaderConstants.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/HeaderConstants.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/HeaderConstants.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/HeaderConstants.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,68 @@
+/*
+ * ====================================================================
+ * 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 org.apache.http.client.cache.impl;
+
+/**
+ * @since 4.1
+ */
+public class HeaderConstants {
+
+ public static final String GET_METHOD = "GET";
+ public static final String HEAD_METHOD = "HEAD";
+ public static final String OPTIONS_METHOD = "OPTIONS";
+ public static final String PUT_METHOD = "PUT";
+ public static final String DELETE_METHOD = "DELETE";
+ public static final String TRACE_METHOD = "TRACE";
+
+ public static final String LAST_MODIFIED = "Last-Modified";
+ public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
+ public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
+ public static final String IF_NONE_MATCH = "If-None-Match";
+
+ public static final String PRAGMA = "Pragma";
+ public static final String MAX_FORWARDS = "Max-Forwards";
+ public static final String ETAG = "ETag";
+ public static final String EXPIRES = "Expires";
+ public static final String AGE = "Age";
+ public static final String CONTENT_LENGTH = "Content-Length";
+ public static final String DATE = "Date";
+ public static final String VARY = "Vary";
+
+ public static final String CACHE_CONTROL = "Cache-Control";
+ public static final String CACHE_CONTROL_NO_STORE = "no-store";
+ public static final String CACHE_CONTROL_NO_CACHE = "no-cache";
+ public static final String CACHE_CONTROL_MAX_AGE = "max-age";
+ public static final String CACHE_CONTROL_MAX_STALE = "max-stale";
+ public static final String CACHE_CONTROL_MIN_FRESH = "min-fresh";
+
+ public static final String TRANSFER_ENCODING = "Transfer-Encoding";
+ public static final String WARNING = "Warning";
+ public static final String EXPECT = "Expect";
+ public static final String RANGE = "Range";
+ public static final String CONTENT_RANGE = "Content-Range";
+
+}
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/OptionsHttp11Response.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/OptionsHttp11Response.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/OptionsHttp11Response.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/OptionsHttp11Response.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,162 @@
+/*
+ * ====================================================================
+ * 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 org.apache.http.client.cache.impl;
+
+import java.util.Locale;
+
+import org.apache.http.Header;
+import org.apache.http.HeaderIterator;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.StatusLine;
+import org.apache.http.message.AbstractHttpMessage;
+import org.apache.http.message.BasicStatusLine;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpParams;
+
+/**
+ * @since 4.1
+ */
+public final class OptionsHttp11Response extends AbstractHttpMessage implements HttpResponse {
+
+ StatusLine statusLine = new BasicStatusLine(CachingHttpClient.HTTP_1_1,
+ HttpStatus.SC_NOT_IMPLEMENTED, "");
+ ProtocolVersion version = CachingHttpClient.HTTP_1_1;
+
+ public StatusLine getStatusLine() {
+ return statusLine;
+ }
+
+ public void setStatusLine(StatusLine statusline) {
+ // No-op on purpose, this class is not going to be doing any work.
+ }
+
+ public void setStatusLine(ProtocolVersion ver, int code) {
+ // No-op on purpose, this class is not going to be doing any work.
+ }
+
+ public void setStatusLine(ProtocolVersion ver, int code, String reason) {
+ // No-op on purpose, this class is not going to be doing any work.
+ }
+
+ public void setStatusCode(int code) throws IllegalStateException {
+ // No-op on purpose, this class is not going to be doing any work.
+ }
+
+ public void setReasonPhrase(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(HttpEntity entity) {
+ // No-op on purpose, this class is not going to be doing any work.
+ }
+
+ public Locale getLocale() {
+ return null;
+ }
+
+ public void setLocale(Locale loc) {
+ // No-op on purpose, this class is not going to be doing any work.
+ }
+
+ public ProtocolVersion getProtocolVersion() {
+ return version;
+ }
+
+ public boolean containsHeader(String name) {
+ return this.headergroup.containsHeader(name);
+ }
+
+ public Header[] getHeaders(String name) {
+ return this.headergroup.getHeaders(name);
+ }
+
+ public Header getFirstHeader(String name) {
+ return this.headergroup.getFirstHeader(name);
+ }
+
+ public Header getLastHeader(String name) {
+ return this.headergroup.getLastHeader(name);
+ }
+
+ public Header[] getAllHeaders() {
+ return this.headergroup.getAllHeaders();
+ }
+
+ public void addHeader(Header header) {
+ // No-op on purpose, this class is not going to be doing any work.
+ }
+
+ public void addHeader(String name, String value) {
+ // No-op on purpose, this class is not going to be doing any work.
+ }
+
+ public void setHeader(Header header) {
+ // No-op on purpose, this class is not going to be doing any work.
+ }
+
+ public void setHeader(String name, String value) {
+ // No-op on purpose, this class is not going to be doing any work.
+ }
+
+ public void setHeaders(Header[] headers) {
+ // No-op on purpose, this class is not going to be doing any work.
+ }
+
+ public void removeHeader(Header header) {
+ // No-op on purpose, this class is not going to be doing any work.
+ }
+
+ public void removeHeaders(String name) {
+ // No-op on purpose, this class is not going to be doing any work.
+ }
+
+ public HeaderIterator headerIterator() {
+ return this.headergroup.iterator();
+ }
+
+ public HeaderIterator headerIterator(String name) {
+ return this.headergroup.iterator(name);
+ }
+
+ public HttpParams getParams() {
+ if (this.params == null) {
+ this.params = new BasicHttpParams();
+ }
+ return this.params;
+ }
+
+ public void setParams(HttpParams params) {
+ // No-op on purpose, this class is not going to be doing any work.
+ }
+}
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/RequestProtocolCompliance.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/RequestProtocolCompliance.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/RequestProtocolCompliance.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/RequestProtocolCompliance.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,308 @@
+/*
+ * ====================================================================
+ * 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 org.apache.http.client.cache.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.http.Header;
+import org.apache.http.HeaderElement;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.ProtocolException;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.entity.AbstractHttpEntity;
+import org.apache.http.impl.client.RequestWrapper;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.BasicHttpResponse;
+import org.apache.http.message.BasicStatusLine;
+
+/**
+ * @since 4.1
+ */
+public class RequestProtocolCompliance {
+
+ public List<RequestProtocolError> requestIsFatallyNonCompliant(HttpRequest request) {
+ List<RequestProtocolError> theErrors = new ArrayList<RequestProtocolError>();
+
+ RequestProtocolError anError = requestContainsBodyButNoLength(request);
+ if (anError != null) {
+ theErrors.add(anError);
+ }
+
+ anError = requestHasWeakETagAndRange(request);
+ if (anError != null) {
+ theErrors.add(anError);
+ }
+
+ anError = requestHasWeekETagForPUTOrDELETEIfMatch(request);
+ if (anError != null) {
+ theErrors.add(anError);
+ }
+
+ return theErrors;
+ }
+
+ public HttpRequest makeRequestCompliant(HttpRequest request) throws ProtocolException {
+ if (requestMustNotHaveEntity(request)) {
+ ((HttpEntityEnclosingRequest) request).setEntity(null);
+ }
+
+ verifyRequestWithExpectContinueFlagHas100continueHeader(request);
+ verifyOPTIONSRequestWithBodyHasContentType(request);
+ decrementOPTIONSMaxForwardsIfGreaterThen0(request);
+
+ if (requestVersionIsTooLow(request)) {
+ return upgradeRequestTo(request, CachingHttpClient.HTTP_1_1);
+ }
+
+ if (requestMinorVersionIsTooHighMajorVersionsMatch(request)) {
+ return downgradeRequestTo(request, CachingHttpClient.HTTP_1_1);
+ }
+
+ return request;
+ }
+
+ private boolean requestMustNotHaveEntity(HttpRequest request) {
+ return HeaderConstants.TRACE_METHOD.equals(request.getRequestLine().getMethod())
+ && request instanceof HttpEntityEnclosingRequest;
+ }
+
+ private void decrementOPTIONSMaxForwardsIfGreaterThen0(HttpRequest request) {
+ if (!HeaderConstants.OPTIONS_METHOD.equals(request.getRequestLine().getMethod())) {
+ return;
+ }
+
+ Header maxForwards = request.getFirstHeader(HeaderConstants.MAX_FORWARDS);
+ if (maxForwards == null) {
+ return;
+ }
+
+ request.removeHeaders(HeaderConstants.MAX_FORWARDS);
+ Integer currentMaxForwards = Integer.valueOf(maxForwards.getValue());
+
+ request.setHeader(HeaderConstants.MAX_FORWARDS, Integer.toString(currentMaxForwards - 1));
+ }
+
+ private void verifyOPTIONSRequestWithBodyHasContentType(HttpRequest request) {
+ if (!HeaderConstants.OPTIONS_METHOD.equals(request.getRequestLine().getMethod())) {
+ return;
+ }
+
+ if (!(request instanceof HttpEntityEnclosingRequest)) {
+ return;
+ }
+
+ addContentTypeHeaderIfMissing((HttpEntityEnclosingRequest) request);
+ }
+
+ private void addContentTypeHeaderIfMissing(HttpEntityEnclosingRequest request) {
+ if (request.getEntity().getContentType() == null) {
+ ((AbstractHttpEntity) request.getEntity()).setContentType("application/octet-stream");
+ }
+ }
+
+ private void verifyRequestWithExpectContinueFlagHas100continueHeader(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(HttpRequest request) {
+ boolean hasHeader = false;
+
+ Header[] expectHeaders = request.getHeaders(HeaderConstants.EXPECT);
+ List<HeaderElement> expectElementsThatAreNot100Continue = new ArrayList<HeaderElement>();
+
+ for (Header h : expectHeaders) {
+ for (HeaderElement elt : h.getElements()) {
+ if (!("100-continue".equalsIgnoreCase(elt.getName()))) {
+ expectElementsThatAreNot100Continue.add(elt);
+ } else {
+ hasHeader = true;
+ }
+ }
+
+ if (hasHeader) {
+ request.removeHeader(h);
+ for (HeaderElement elt : expectElementsThatAreNot100Continue) {
+ BasicHeader newHeader = new BasicHeader(HeaderConstants.EXPECT, elt.getName());
+ request.addHeader(newHeader);
+ }
+ return;
+ } else {
+ expectElementsThatAreNot100Continue = new ArrayList<HeaderElement>();
+ }
+ }
+ }
+
+ private void add100ContinueHeaderIfMissing(HttpRequest request) {
+ boolean hasHeader = false;
+
+ for (Header h : request.getHeaders(HeaderConstants.EXPECT)) {
+ for (HeaderElement elt : h.getElements()) {
+ if ("100-continue".equalsIgnoreCase(elt.getName())) {
+ hasHeader = true;
+ }
+ }
+ }
+
+ if (!hasHeader) {
+ request.addHeader(HeaderConstants.EXPECT, "100-continue");
+ }
+ }
+
+ private HttpRequest upgradeRequestTo(HttpRequest request, ProtocolVersion version)
+ throws ProtocolException {
+ RequestWrapper newRequest = new RequestWrapper(request);
+ newRequest.setProtocolVersion(version);
+
+ return newRequest;
+ }
+
+ private HttpRequest downgradeRequestTo(HttpRequest request, ProtocolVersion version)
+ throws ProtocolException {
+ RequestWrapper newRequest = new RequestWrapper(request);
+ newRequest.setProtocolVersion(version);
+
+ return newRequest;
+ }
+
+ protected boolean requestMinorVersionIsTooHighMajorVersionsMatch(HttpRequest request) {
+ ProtocolVersion requestProtocol = request.getProtocolVersion();
+ if (requestProtocol.getMajor() != CachingHttpClient.HTTP_1_1.getMajor()) {
+ return false;
+ }
+
+ if (requestProtocol.getMinor() > CachingHttpClient.HTTP_1_1.getMinor()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ protected boolean requestVersionIsTooLow(HttpRequest request) {
+ return request.getProtocolVersion().compareToVersion(CachingHttpClient.HTTP_1_1) < 0;
+ }
+
+ public HttpResponse getErrorForRequest(RequestProtocolError errorCheck) {
+ switch (errorCheck) {
+ case BODY_BUT_NO_LENGTH_ERROR:
+ return new BasicHttpResponse(new BasicStatusLine(CachingHttpClient.HTTP_1_1,
+ HttpStatus.SC_LENGTH_REQUIRED, ""));
+
+ case WEAK_ETAG_AND_RANGE_ERROR:
+ return new BasicHttpResponse(new BasicStatusLine(CachingHttpClient.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(CachingHttpClient.HTTP_1_1,
+ HttpStatus.SC_BAD_REQUEST,
+ "Weak eTag not compatible with PUT or DELETE requests"));
+
+ default:
+ throw new IllegalStateException(
+ "The request was compliant, therefore no error can be generated for it.");
+
+ }
+ }
+
+ private RequestProtocolError requestHasWeakETagAndRange(HttpRequest request) {
+ // TODO: Should these be looking at all the headers marked as Range?
+ String method = request.getRequestLine().getMethod();
+ if (!(HeaderConstants.GET_METHOD.equals(method))) {
+ return null;
+ }
+
+ Header range = request.getFirstHeader("Range");
+ if (range == null)
+ return null;
+
+ Header ifRange = request.getFirstHeader("If-Range");
+ if (ifRange == null)
+ return null;
+
+ String val = ifRange.getValue();
+ if (val.startsWith("W/")) {
+ return RequestProtocolError.WEAK_ETAG_AND_RANGE_ERROR;
+ }
+
+ return null;
+ }
+
+ private RequestProtocolError requestHasWeekETagForPUTOrDELETEIfMatch(HttpRequest request) {
+ // TODO: Should these be looking at all the headers marked as
+ // If-Match/If-None-Match?
+
+ String method = request.getRequestLine().getMethod();
+ if (!(HeaderConstants.PUT_METHOD.equals(method) || HeaderConstants.DELETE_METHOD
+ .equals(method))) {
+ return null;
+ }
+
+ Header ifMatch = request.getFirstHeader("If-Match");
+ if (ifMatch != null) {
+ String val = ifMatch.getValue();
+ if (val.startsWith("W/")) {
+ return RequestProtocolError.WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR;
+ }
+ } else {
+ Header ifNoneMatch = request.getFirstHeader("If-None-Match");
+ if (ifNoneMatch == null)
+ return null;
+
+ String val2 = ifNoneMatch.getValue();
+ if (val2.startsWith("W/")) {
+ return RequestProtocolError.WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR;
+ }
+ }
+
+ return null;
+ }
+
+ private RequestProtocolError requestContainsBodyButNoLength(HttpRequest request) {
+ if (!(request instanceof HttpEntityEnclosingRequest)) {
+ return null;
+ }
+
+ if (request.getFirstHeader(HeaderConstants.CONTENT_LENGTH) != null
+ && ((HttpEntityEnclosingRequest) request).getEntity() != null)
+ return null;
+
+ return RequestProtocolError.BODY_BUT_NO_LENGTH_ERROR;
+ }
+}
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/RequestProtocolError.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/RequestProtocolError.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/RequestProtocolError.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/RequestProtocolError.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,39 @@
+/*
+ * ====================================================================
+ * 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 org.apache.http.client.cache.impl;
+
+/**
+ * @since 4.1
+ */
+public enum RequestProtocolError {
+
+ UNKNOWN,
+ BODY_BUT_NO_LENGTH_ERROR,
+ WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR,
+ WEAK_ETAG_AND_RANGE_ERROR
+
+}
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ResponseCachingPolicy.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ResponseCachingPolicy.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ResponseCachingPolicy.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ResponseCachingPolicy.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,189 @@
+/*
+ * ====================================================================
+ * 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 org.apache.http.client.cache.impl;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.Header;
+import org.apache.http.HeaderElement;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.impl.cookie.DateParseException;
+import org.apache.http.impl.cookie.DateUtils;
+
+/**
+ * Determines if an HttpResponse can be cached.
+ *
+ * @since 4.1
+ */
+public class ResponseCachingPolicy {
+
+ private int maxObjectSizeBytes;
+ private static final Log LOG = LogFactory.getLog(ResponseCachingPolicy.class);
+
+ public ResponseCachingPolicy(int maxObjectSizeBytes) {
+ this.maxObjectSizeBytes = maxObjectSizeBytes;
+ }
+
+ /**
+ * Determines if an HttpResponse can be cached.
+ *
+ * @param httpMethod
+ * @param response
+ * @return
+ */
+ public boolean isResponseCacheable(String httpMethod, HttpResponse response) {
+ boolean cacheable = false;
+
+ if (!HeaderConstants.GET_METHOD.equals(httpMethod)) {
+ LOG.debug("ResponseCachingPolicy: response was not cacheable.");
+ return false;
+ }
+
+ switch (response.getStatusLine().getStatusCode()) {
+ case HttpStatus.SC_OK:
+ case HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION:
+ case HttpStatus.SC_MULTIPLE_CHOICES:
+ case HttpStatus.SC_MOVED_PERMANENTLY:
+ case HttpStatus.SC_GONE:
+ // these response codes MAY be cached
+ cacheable = true;
+ LOG.debug("ResponseCachingPolicy: response WAS cacheable.");
+ break;
+ case HttpStatus.SC_PARTIAL_CONTENT:
+ // we don't implement Range requests and hence are not
+ // allowed to cache partial content
+ LOG.debug("ResponseCachingPolicy: response was not cacheable (Partial Content).");
+ return cacheable;
+
+ default:
+ // If the status code is not one of the recognized
+ // available codes in HttpStatus Don't Cache
+ LOG.debug("ResponseCachingPolicy: response was not cacheable (Unknown Status code).");
+ return cacheable;
+ }
+
+ Header contentLength = response.getFirstHeader(HeaderConstants.CONTENT_LENGTH);
+ if (contentLength != null) {
+ int contentLengthValue = Integer.parseInt(contentLength.getValue());
+ if (contentLengthValue > this.maxObjectSizeBytes)
+ return false;
+ }
+
+ Header[] ageHeaders = response.getHeaders(HeaderConstants.AGE);
+
+ if (ageHeaders.length > 1)
+ return false;
+
+ Header[] expiresHeaders = response.getHeaders(HeaderConstants.EXPIRES);
+
+ if (expiresHeaders.length > 1)
+ return false;
+
+ Header[] dateHeaders = response.getHeaders(HeaderConstants.DATE);
+
+ if (dateHeaders.length != 1)
+ return false;
+
+ try {
+ DateUtils.parseDate(dateHeaders[0].getValue());
+ } catch (DateParseException dpe) {
+ return false;
+ }
+
+ for (Header varyHdr : response.getHeaders(HeaderConstants.VARY)) {
+ for (HeaderElement elem : varyHdr.getElements()) {
+ if ("*".equals(elem.getName())) {
+ return false;
+ }
+ }
+ }
+
+ if (isExplicitlyNonCacheable(response))
+ return false;
+
+ return (cacheable || isExplicitlyCacheable(response));
+ }
+
+ protected boolean isExplicitlyNonCacheable(HttpResponse response) {
+ Header[] cacheControlHeaders = response.getHeaders(HeaderConstants.CACHE_CONTROL);
+ for (Header header : cacheControlHeaders) {
+ for (HeaderElement elem : header.getElements()) {
+ if (HeaderConstants.CACHE_CONTROL_NO_STORE.equals(elem.getName())
+ || HeaderConstants.CACHE_CONTROL_NO_CACHE.equals(elem.getName())
+ || "private".equals(elem.getName())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ protected boolean isExplicitlyCacheable(HttpResponse response) {
+ if (response.getFirstHeader(HeaderConstants.EXPIRES) != null)
+ return true;
+ Header[] cacheControlHeaders = response.getHeaders(HeaderConstants.CACHE_CONTROL);
+ for (Header header : cacheControlHeaders) {
+ for (HeaderElement elem : header.getElements()) {
+ if ("max-age".equals(elem.getName()) || "s-maxage".equals(elem.getName())
+ || "must-revalidate".equals(elem.getName())
+ || "proxy-revalidate".equals(elem.getName())
+ || "public".equals(elem.getName())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ *
+ * @param request
+ * @param response
+ * @return
+ */
+ public boolean isResponseCacheable(HttpRequest request, HttpResponse response) {
+ if (requestProtocolGreaterThanAccepted(request)) {
+ LOG.debug("ResponseCachingPolicy: response was not cacheable.");
+ return false;
+ }
+
+ if (request.getRequestLine().getUri().contains("?") && !isExplicitlyCacheable(response)) {
+ LOG.debug("ResponseCachingPolicy: response was not cacheable.");
+ return false;
+ }
+
+ String method = request.getRequestLine().getMethod();
+ return isResponseCacheable(method, response);
+ }
+
+ private boolean requestProtocolGreaterThanAccepted(HttpRequest req) {
+ return req.getProtocolVersion().compareToVersion(CachingHttpClient.HTTP_1_1) > 0;
+ }
+
+}
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ResponseProtocolCompliance.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ResponseProtocolCompliance.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ResponseProtocolCompliance.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/ResponseProtocolCompliance.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,201 @@
+/*
+ * ====================================================================
+ * 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 org.apache.http.client.cache.impl;
+
+import java.util.Date;
+
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.impl.client.RequestWrapper;
+import org.apache.http.impl.cookie.DateUtils;
+
+/**
+ * @since 4.1
+ */
+public class ResponseProtocolCompliance {
+
+ public void ensureProtocolCompliance(HttpRequest request, HttpResponse response)
+ throws ClientProtocolException {
+ if (backendResponseMustNotHaveBody(request, response)) {
+ response.setEntity(null);
+ }
+
+ authenticationRequiredDidNotHaveAProxyAuthenticationHeader(request, response);
+
+ notAllowedResponseDidNotHaveAnAllowHeader(request, response);
+
+ unauthorizedResponseDidNotHaveAWWWAuthenticateHeader(request, response);
+
+ requestDidNotExpect100ContinueButResponseIsOne(request, response);
+
+ transferEncodingIsNotReturnedTo1_0Client(request, response);
+
+ ensurePartialContentIsNotSentToAClientThatDidNotRequestIt(request, response);
+
+ ensure200ForOPTIONSRequestWithNoBodyHasContentLengthZero(request, response);
+
+ ensure206ContainsDateHeader(response);
+ }
+
+ private void authenticationRequiredDidNotHaveAProxyAuthenticationHeader(HttpRequest request,
+ HttpResponse response) throws ClientProtocolException {
+ if (response.getStatusLine().getStatusCode() != HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED)
+ return;
+
+ if (response.getFirstHeader("Proxy-Authenticate") == null)
+ throw new ClientProtocolException(
+ "407 Response did not contain a Proxy-Authentication header");
+ }
+
+ private void notAllowedResponseDidNotHaveAnAllowHeader(HttpRequest request,
+ HttpResponse response) throws ClientProtocolException {
+ if (response.getStatusLine().getStatusCode() != HttpStatus.SC_METHOD_NOT_ALLOWED)
+ return;
+
+ if (response.getFirstHeader("Allow") == null)
+ throw new ClientProtocolException("405 Response did not contain an Allow header.");
+ }
+
+ private void unauthorizedResponseDidNotHaveAWWWAuthenticateHeader(HttpRequest request,
+ HttpResponse response) throws ClientProtocolException {
+ if (response.getStatusLine().getStatusCode() != HttpStatus.SC_UNAUTHORIZED)
+ return;
+
+ if (response.getFirstHeader("WWW-Authenticate") == null) {
+ throw new ClientProtocolException(
+ "401 Response did not contain required WWW-Authenticate challenge header");
+ }
+ }
+
+ private void ensure206ContainsDateHeader(HttpResponse response) {
+ if (response.getFirstHeader(HeaderConstants.DATE) == null) {
+ response.addHeader(HeaderConstants.DATE, DateUtils.formatDate(new Date()));
+ }
+
+ }
+
+ private void ensurePartialContentIsNotSentToAClientThatDidNotRequestIt(HttpRequest request,
+ HttpResponse response) throws ClientProtocolException {
+ if (request.getFirstHeader(HeaderConstants.RANGE) != null)
+ return;
+
+ if (response.getFirstHeader(HeaderConstants.CONTENT_RANGE) != null) {
+ throw new ClientProtocolException(
+ "Content-Range was returned for a request that did not ask for a Content-Range.");
+ }
+
+ }
+
+ private void ensure200ForOPTIONSRequestWithNoBodyHasContentLengthZero(HttpRequest request,
+ HttpResponse response) {
+ if (!request.getRequestLine().getMethod().equalsIgnoreCase(HeaderConstants.OPTIONS_METHOD)) {
+ return;
+ }
+
+ if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
+ return;
+ }
+
+ if (response.getFirstHeader(HeaderConstants.CONTENT_LENGTH) == null) {
+ response.addHeader(HeaderConstants.CONTENT_LENGTH, "0");
+ }
+ }
+
+ private boolean backendResponseMustNotHaveBody(HttpRequest request, 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(HttpRequest request,
+ HttpResponse response) throws ClientProtocolException {
+ if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CONTINUE) {
+ return;
+ }
+
+ if (!requestWasWrapped(request)) {
+ return;
+ }
+
+ ProtocolVersion originalProtocol = getOriginalRequestProtocol((RequestWrapper) request);
+
+ if (originalProtocol.compareToVersion(CachingHttpClient.HTTP_1_1) >= 0) {
+ return;
+ }
+
+ if (originalRequestDidNotExpectContinue((RequestWrapper) request)) {
+ throw new ClientProtocolException("The incoming request did not contain a "
+ + "100-continue header, but the response was a Status 100, continue.");
+
+ }
+ }
+
+ private void transferEncodingIsNotReturnedTo1_0Client(HttpRequest request, HttpResponse response) {
+ if (!requestWasWrapped(request)) {
+ return;
+ }
+
+ ProtocolVersion originalProtocol = getOriginalRequestProtocol((RequestWrapper) request);
+
+ if (originalProtocol.compareToVersion(CachingHttpClient.HTTP_1_1) >= 0) {
+ return;
+ }
+
+ removeResponseTransferEncoding(response);
+ }
+
+ private void removeResponseTransferEncoding(HttpResponse response) {
+ response.removeHeaders("TE");
+ response.removeHeaders(HeaderConstants.TRANSFER_ENCODING);
+ }
+
+ private boolean originalRequestDidNotExpectContinue(RequestWrapper request) {
+
+ try {
+ HttpEntityEnclosingRequest original = (HttpEntityEnclosingRequest) request
+ .getOriginal();
+
+ return !original.expectContinue();
+ } catch (ClassCastException ex) {
+ return false;
+ }
+ }
+
+ private ProtocolVersion getOriginalRequestProtocol(RequestWrapper request) {
+ return request.getOriginal().getProtocolVersion();
+ }
+
+ private boolean requestWasWrapped(HttpRequest request) {
+ return request instanceof RequestWrapper;
+ }
+
+}
Added: httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/SizeLimitedResponseReader.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/SizeLimitedResponseReader.java?rev=939814&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/SizeLimitedResponseReader.java (added)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/client/cache/impl/SizeLimitedResponseReader.java Fri Apr 30 21:00:08 2010
@@ -0,0 +1,148 @@
+/*
+ * ====================================================================
+ * 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 org.apache.http.client.cache.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.message.BasicHttpResponse;
+
+/**
+ * @since 4.1
+ */
+public class SizeLimitedResponseReader {
+
+ private int maxResponseSizeBytes;
+ private HttpResponse response;
+ ByteArrayOutputStream outputStream;
+ InputStream contentInputStream;
+
+ private boolean isTooLarge;
+ private boolean responseIsConsumed;
+ private byte[] sizeLimitedContent;
+ private boolean outputStreamConsumed;
+
+ public SizeLimitedResponseReader(int maxResponseSizeBytes, HttpResponse response)
+ throws IOException {
+ this.maxResponseSizeBytes = maxResponseSizeBytes;
+ this.response = response;
+ }
+
+ public boolean isResponseTooLarge() throws IOException {
+ if (!responseIsConsumed)
+ isTooLarge = consumeResponse();
+
+ return isTooLarge;
+ }
+
+ private synchronized boolean consumeResponse() throws IOException {
+
+ if (responseIsConsumed)
+ throw new IllegalStateException(
+ "You cannot call this method more than once, because it consumes an underlying stream");
+
+ responseIsConsumed = true;
+
+ HttpEntity entity = response.getEntity();
+ if (entity == null)
+ return false;
+
+ contentInputStream = entity.getContent();
+ int bytes = 0;
+
+ outputStream = new ByteArrayOutputStream();
+
+ int current;
+
+ while (bytes < maxResponseSizeBytes && (current = contentInputStream.read()) != -1) {
+ outputStream.write(current);
+ bytes++;
+ }
+
+ if ((current = contentInputStream.read()) != -1) {
+ outputStream.write(current);
+ return true;
+ }
+
+ return false;
+ }
+
+ private synchronized void consumeOutputStream() {
+ if (outputStreamConsumed)
+ throw new IllegalStateException(
+ "underlying output stream has already been written to byte[]");
+
+ if (!responseIsConsumed)
+ throw new IllegalStateException("Must call consumeResponse first.");
+
+ sizeLimitedContent = outputStream.toByteArray();
+ outputStreamConsumed = true;
+ }
+
+ public byte[] getResponseBytes() {
+ if (!outputStreamConsumed)
+ consumeOutputStream();
+
+ return sizeLimitedContent;
+ }
+
+ public HttpResponse getReconstructedResponse() throws IOException {
+
+ InputStream combinedStream = getCombinedInputStream();
+
+ return constructResponse(response, combinedStream);
+ }
+
+ protected InputStream getCombinedInputStream() throws IOException {
+ InputStream input1 = new ByteArrayInputStream(getResponseBytes());
+ InputStream input2 = getContentInputStream();
+ return new CombinedInputStream(input1, input2);
+ }
+
+ protected InputStream getContentInputStream() {
+ return contentInputStream;
+ }
+
+ protected HttpResponse constructResponse(HttpResponse originalResponse,
+ InputStream combinedStream) throws IOException {
+ HttpResponse response = new BasicHttpResponse(originalResponse.getProtocolVersion(),
+ HttpStatus.SC_OK, "Success");
+
+ HttpEntity entity = new InputStreamEntity(combinedStream, -1);
+ response.setEntity(entity);
+ response.setHeaders(originalResponse.getAllHeaders());
+
+ return response;
+ }
+
+}