hc-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ol...@apache.org
Subject svn commit: r963495 - in /httpcomponents/httpclient/trunk: ./ httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ httpclient-cache/src/test/java/org/apache/http/impl/client/cache/
Date Mon, 12 Jul 2010 21:36:47 GMT
Author: olegk
Date: Mon Jul 12 21:36:47 2010
New Revision: 963495

URL: http://svn.apache.org/viewvc?rev=963495&view=rev
Log:
HTTPCLIENT-963: Fixed handling of 'Cache-Control: no-store' on requests
Contributed by Jonathan Moore <jonathan_moore at comcast.com>


Modified:
    httpcomponents/httpclient/trunk/RELEASE_NOTES.txt
    httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseCachingPolicy.java
    httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRequirements.java
    httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseCachingPolicy.java

Modified: httpcomponents/httpclient/trunk/RELEASE_NOTES.txt
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/RELEASE_NOTES.txt?rev=963495&r1=963494&r2=963495&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/RELEASE_NOTES.txt (original)
+++ httpcomponents/httpclient/trunk/RELEASE_NOTES.txt Mon Jul 12 21:36:47 2010
@@ -5,10 +5,13 @@ Changes since 4.1 ALPHA2
   proxy-revalidate Cache-Control directives.
   Contributed by Jonathan Moore <jonathan_moore at comcast.com>
 
-* [HTTPCLIENT-964] no-cache directives with field names are no longer transmitted 
+* [HTTPCLIENT-964] 'no-cache' directives with field names are no longer transmitted 
   downstream.
   Contributed by Jonathan Moore <jonathan_moore at comcast.com>
 
+* [HTTPCLIENT-963] Fixed handling of 'Cache-Control: no-store' on requests.
+  Contributed by Jonathan Moore <jonathan_moore at comcast.com>
+
 * [HTTPCLIENT-962] Fixed handling of Authorization headers in shared cache mode.
   Contributed by Jonathan Moore <jonathan_moore at comcast.com>
 

Modified: httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseCachingPolicy.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseCachingPolicy.java?rev=963495&r1=963494&r2=963495&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseCachingPolicy.java
(original)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/ResponseCachingPolicy.java
Mon Jul 12 21:36:47 2010
@@ -30,6 +30,7 @@ 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.HttpMessage;
 import org.apache.http.HttpRequest;
 import org.apache.http.HttpResponse;
 import org.apache.http.HttpStatus;
@@ -153,12 +154,12 @@ public class ResponseCachingPolicy {
         return false;
     }
 
-    protected boolean hasCacheControlParameterFrom(HttpResponse response, String[] params)
{
-        Header[] cacheControlHeaders = response.getHeaders(HeaderConstants.CACHE_CONTROL);
+    protected boolean hasCacheControlParameterFrom(HttpMessage msg, String[] params) {
+        Header[] cacheControlHeaders = msg.getHeaders(HeaderConstants.CACHE_CONTROL);
         for (Header header : cacheControlHeaders) {
             for (HeaderElement elem : header.getElements()) {
                 for (String param : params) {
-                    if (param.equals(elem.getName())) {
+                    if (param.equalsIgnoreCase(elem.getName())) {
                         return true;
                     }
                 }
@@ -189,6 +190,10 @@ public class ResponseCachingPolicy {
             log.debug("Response was not cacheable.");
             return false;
         }
+        String[] uncacheableRequestDirectives = { "no-store" };
+        if (hasCacheControlParameterFrom(request,uncacheableRequestDirectives)) {
+            return false;
+        }
 
         if (request.getRequestLine().getUri().contains("?") && !isExplicitlyCacheable(response))
{
             log.debug("Response was not cacheable.");

Modified: httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRequirements.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRequirements.java?rev=963495&r1=963494&r2=963495&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRequirements.java
(original)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRequirements.java
Mon Jul 12 21:36:47 2010
@@ -5047,7 +5047,214 @@ public class TestProtocolRequirements {
         }
     }
 
+    /* "[The cache control directive] "private" Indicates that all or part of
+     * the response message is intended for a single user and MUST NOT be
+     * cached by a shared cache."
+     *
+     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.1
+     */
+    @Test
+    public void testCacheControlPrivateIsNotCacheableBySharedCache()
+    throws Exception {
+       if (impl.isSharedCache()) {
+               HttpRequest req1 = new BasicHttpRequest("GET","/",HTTP_1_1);
+               HttpResponse resp1 = make200Response();
+               resp1.setHeader("Cache-Control","private,max-age=3600");
 
+               backendExpectsAnyRequest().andReturn(resp1);
+
+               HttpRequest req2 = new BasicHttpRequest("GET","/",HTTP_1_1);
+               HttpResponse resp2 = make200Response();
+               // this backend request MUST happen
+               backendExpectsAnyRequest().andReturn(resp2);
+
+               replayMocks();
+               impl.execute(host,req1);
+               impl.execute(host,req2);
+               verifyMocks();
+       }
+    }
+
+    @Test
+    public void testCacheControlPrivateOnFieldIsNotReturnedBySharedCache()
+    throws Exception {
+       if (impl.isSharedCache()) {
+               HttpRequest req1 = new BasicHttpRequest("GET","/",HTTP_1_1);
+               HttpResponse resp1 = make200Response();
+               resp1.setHeader("X-Personal","stuff");
+               resp1.setHeader("Cache-Control","private=\"X-Personal\",s-maxage=3600");
+
+               backendExpectsAnyRequest().andReturn(resp1);
+
+               HttpRequest req2 = new BasicHttpRequest("GET","/",HTTP_1_1);
+               HttpResponse resp2 = make200Response();
+
+               // this backend request MAY happen
+               backendExpectsAnyRequest().andReturn(resp2).times(0,1);
+
+               replayMocks();
+               impl.execute(host,req1);
+               HttpResponse result = impl.execute(host,req2);
+               verifyMocks();
+               Assert.assertNull(result.getFirstHeader("X-Personal"));
+       }
+    }
+
+    /* "If the no-cache directive does not specify a field-name, then a
+     * cache MUST NOT use the response to satisfy a subsequent request
+     * without successful revalidation with the origin server. This allows
+     * an origin server to prevent caching even by caches that have been
+     * configured to return stale responses to client requests."
+     *
+     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.1
+     */
+    @Test
+    public void testNoCacheCannotSatisfyASubsequentRequestWithoutRevalidation()
+    throws Exception {
+        HttpRequest req1 = new BasicHttpRequest("GET","/",HTTP_1_1);
+        HttpResponse resp1 = make200Response();
+        resp1.setHeader("ETag","\"etag\"");
+        resp1.setHeader("Cache-Control","no-cache");
+
+        backendExpectsAnyRequest().andReturn(resp1);
+
+        HttpRequest req2 = new BasicHttpRequest("GET","/",HTTP_1_1);
+        HttpResponse resp2 = make200Response();
+
+        // this MUST happen
+        backendExpectsAnyRequest().andReturn(resp2);
+
+        replayMocks();
+        impl.execute(host,req1);
+        impl.execute(host,req2);
+        verifyMocks();
+    }
+
+    @Test
+    public void testNoCacheCannotSatisfyASubsequentRequestWithoutRevalidationEvenWithContraryIndications()
+    throws Exception {
+        HttpRequest req1 = new BasicHttpRequest("GET","/",HTTP_1_1);
+        HttpResponse resp1 = make200Response();
+        resp1.setHeader("ETag","\"etag\"");
+        resp1.setHeader("Cache-Control","no-cache,s-maxage=3600");
+
+        backendExpectsAnyRequest().andReturn(resp1);
+
+        HttpRequest req2 = new BasicHttpRequest("GET","/",HTTP_1_1);
+        req2.setHeader("Cache-Control","max-stale=7200");
+        HttpResponse resp2 = make200Response();
+
+        // this MUST happen
+        backendExpectsAnyRequest().andReturn(resp2);
+
+        replayMocks();
+        impl.execute(host,req1);
+        impl.execute(host,req2);
+        verifyMocks();
+    }
+
+    /* "If the no-cache directive does specify one or more field-names, then
+     * a cache MAY use the response to satisfy a subsequent request, subject
+     * to any other restrictions on caching. However, the specified
+     * field-name(s) MUST NOT be sent in the response to a subsequent request
+     * without successful revalidation with the origin server."
+     */
+    @Test
+    public void testNoCacheOnFieldIsNotReturnedWithoutRevalidation()
+    throws Exception {
+        HttpRequest req1 = new BasicHttpRequest("GET","/",HTTP_1_1);
+        HttpResponse resp1 = make200Response();
+        resp1.setHeader("ETag","\"etag\"");
+        resp1.setHeader("X-Stuff","things");
+        resp1.setHeader("Cache-Control","no-cache=\"X-Stuff\", max-age=3600");
+
+        backendExpectsAnyRequest().andReturn(resp1);
+
+        HttpRequest req2 = new BasicHttpRequest("GET","/",HTTP_1_1);
+        HttpResponse resp2 = make200Response();
+        resp2.setHeader("ETag","\"etag\"");
+        resp2.setHeader("X-Stuff","things");
+        resp2.setHeader("Cache-Control","no-cache=\"X-Stuff\",max-age=3600");
+
+        Capture<HttpRequest> cap = new Capture<HttpRequest>();
+        EasyMock.expect(mockBackend.execute(EasyMock.eq(host),
+                                            EasyMock.capture(cap),
+                                            (HttpContext)EasyMock.isNull()))
+                .andReturn(resp2).times(0,1);
+
+        replayMocks();
+        impl.execute(host,req1);
+        HttpResponse result = impl.execute(host,req2);
+        verifyMocks();
+
+        if (!cap.hasCaptured()) {
+            Assert.assertNull(result.getFirstHeader("X-Stuff"));
+        }
+    }
+
+    /* "The purpose of the no-store directive is to prevent the inadvertent
+     * release or retention of sensitive information (for example, on backup
+     * tapes). The no-store directive applies to the entire message, and MAY
+     * be sent either in a response or in a request. If sent in a request, a
+     * cache MUST NOT store any part of either this request or any response
+     * to it. If sent in a response, a cache MUST NOT store any part of
+     * either this response or the request that elicited it. This directive
+     * applies to both non- shared and shared caches. "MUST NOT store" in
+     * this context means that the cache MUST NOT intentionally store the
+     * information in non-volatile storage, and MUST make a best-effort
+     * attempt to remove the information from volatile storage as promptly
+     * as possible after forwarding it."
+     *
+     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.2
+     */
+    @Test
+    public void testNoStoreOnRequestIsNotStoredInCache()
+    throws Exception {
+        emptyMockCacheExpectsNoPuts();
+        request.setHeader("Cache-Control","no-store");
+        backendExpectsAnyRequest().andReturn(originResponse);
+
+        replayMocks();
+        impl.execute(host,request);
+        verifyMocks();
+    }
+
+    @Test
+    public void testNoStoreOnRequestIsNotStoredInCacheEvenIfResponseMarkedCacheable()
+    throws Exception {
+        emptyMockCacheExpectsNoPuts();
+        request.setHeader("Cache-Control","no-store");
+        originResponse.setHeader("Cache-Control","max-age=3600");
+        backendExpectsAnyRequest().andReturn(originResponse);
+
+        replayMocks();
+        impl.execute(host,request);
+        verifyMocks();
+    }
+
+    @Test
+    public void testNoStoreOnResponseIsNotStoredInCache()
+    throws Exception {
+        emptyMockCacheExpectsNoPuts();
+        originResponse.setHeader("Cache-Control","no-store");
+        backendExpectsAnyRequest().andReturn(originResponse);
+
+        replayMocks();
+        impl.execute(host,request);
+        verifyMocks();
+    }
+
+    @Test
+    public void testNoStoreOnResponseIsNotStoredInCacheEvenWithContraryIndicators()
+    throws Exception {
+        emptyMockCacheExpectsNoPuts();
+        originResponse.setHeader("Cache-Control","no-store,max-age=3600");
+        backendExpectsAnyRequest().andReturn(originResponse);
+
+        replayMocks();
+        impl.execute(host,request);
+        verifyMocks();
+    }
 
     private class FakeHeaderGroup extends HeaderGroup{
 

Modified: httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseCachingPolicy.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseCachingPolicy.java?rev=963495&r1=963494&r2=963495&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseCachingPolicy.java
(original)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestResponseCachingPolicy.java
Mon Jul 12 21:36:47 2010
@@ -54,6 +54,7 @@ public class TestResponseCachingPolicy {
     @Before
     public void setUp() throws Exception {
         policy = new ResponseCachingPolicy(0);
+        request = new BasicHttpRequest("GET","/",HTTP_1_1);
         response = new BasicHttpResponse(
                 new BasicStatusLine(HTTP_1_1, HttpStatus.SC_OK, ""));
         response.setHeader("Date", DateUtils.formatDate(new Date()));
@@ -300,6 +301,13 @@ public class TestResponseCachingPolicy {
     }
 
     @Test
+    public void testResponsesToRequestsWithNoStoreAreNotCacheable() {
+        request.setHeader("Cache-Control","no-store");
+        response.setHeader("Cache-Control","public");
+        Assert.assertFalse(policy.isResponseCacheable(request,response));
+    }
+
+    @Test
     public void testResponsesWithMultipleAgeHeadersAreNotCacheable() {
         response.addHeader("Age", "3");
         response.addHeader("Age", "5");



Mime
View raw message