hc-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ol...@apache.org
Subject 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...
Date Fri, 30 Apr 2010 21:00:10 GMT
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;
+    }
+
+}



Mime
View raw message