You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2010/05/07 16:16:14 UTC

svn commit: r942077 - /httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestProtocolRequirements.java

Author: olegk
Date: Fri May  7 14:16:14 2010
New Revision: 942077

URL: http://svn.apache.org/viewvc?rev=942077&view=rev
Log:
HTTPCLIENT-427: Added some more acceptance tests as specified in the Section 13 of RFC 2616, in terms of MUST and MUST NOT requirements
Contributed by Jonathan Moore <jonathan_moore at comcast.com>

Modified:
    httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestProtocolRequirements.java

Modified: httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestProtocolRequirements.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestProtocolRequirements.java?rev=942077&r1=942076&r2=942077&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestProtocolRequirements.java (original)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/client/cache/impl/TestProtocolRequirements.java Fri May  7 14:16:14 2010
@@ -2468,25 +2468,32 @@ public class TestProtocolRequirements {
         resp2.setHeader("ETag", "\"etag\"");
         resp2.setHeader("Via", "1.1 fred");
 
-        EasyMock.expect(
-                mockBackend.execute(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class),
-                        (HttpContext) EasyMock.isNull())).andReturn(resp1);
+        backendExpectsAnyRequest().andReturn(resp1);
+        EasyMock.expect(mockBackend.execute(EasyMock.eq(host), 
+                                            eqRequest(validate), 
+                                            (HttpContext)EasyMock.isNull()))
+            .andReturn(resp2);
 
-        EasyMock.expect(
-                mockBackend.execute(EasyMock.eq(host), eqRequest(validate), (HttpContext) EasyMock
-                        .isNull())).andReturn(resp2);
+        HttpRequest req3 = new BasicHttpRequest("GET", "/", HTTP_1_1);
 
         replayMocks();
 
         HttpResponse stale = impl.execute(host, req1);
         Assert.assertNotNull(stale.getFirstHeader("Warning"));
 
-        HttpResponse result = impl.execute(host, req2);
+        HttpResponse result1 = impl.execute(host, req2);
+        HttpResponse result2 = impl.execute(host, req3);
 
         verifyMocks();
 
         boolean found1xxWarning = false;
-        for (Header h : result.getHeaders("Warning")) {
+        for (Header h : result1.getHeaders("Warning")) {
+            for (HeaderElement elt : h.getElements()) {
+                if (elt.getName().startsWith("1"))
+                    found1xxWarning = true;
+            }
+        }
+        for (Header h : result2.getHeaders("Warning")) {
             for (HeaderElement elt : h.getElements()) {
                 if (elt.getName().startsWith("1"))
                     found1xxWarning = true;
@@ -2526,9 +2533,9 @@ public class TestProtocolRequirements {
         resp2.setHeader("ETag", "\"etag\"");
         resp1.setHeader("Via", "1.1 xproxy");
 
-        EasyMock.expect(
-                mockBackend.execute(EasyMock.isA(HttpHost.class), EasyMock.isA(HttpRequest.class),
-                        (HttpContext) EasyMock.isNull())).andReturn(resp1);
+        HttpRequest req3 = new BasicHttpRequest("GET", "/", HTTP_1_1);
+
+        backendExpectsAnyRequest().andReturn(resp1);
 
         EasyMock.expect(
                 mockBackend.execute(EasyMock.eq(host), eqRequest(validate), (HttpContext) EasyMock
@@ -2539,12 +2546,23 @@ public class TestProtocolRequirements {
         HttpResponse stale = impl.execute(host, req1);
         Assert.assertNotNull(stale.getFirstHeader("Warning"));
 
-        HttpResponse result = impl.execute(host, req2);
+        HttpResponse result1 = impl.execute(host, req2);
+        HttpResponse result2 = impl.execute(host, req3);
 
         verifyMocks();
 
         boolean found214Warning = false;
-        for (Header h : result.getHeaders("Warning")) {
+        for (Header h : result1.getHeaders("Warning")) {
+            for (HeaderElement elt : h.getElements()) {
+                String[] parts = elt.getName().split(" ");
+                if ("214".equals(parts[0]))
+                    found214Warning = true;
+            }
+        }
+        Assert.assertTrue(found214Warning);
+
+        found214Warning = false;
+        for (Header h : result2.getHeaders("Warning")) {
             for (HeaderElement elt : h.getElements()) {
                 String[] parts = elt.getName().split(" ");
                 if ("214".equals(parts[0]))
@@ -3041,7 +3059,6 @@ public class TestProtocolRequirements {
     }
 
     private void testDoesNotAddHeaderToOriginResponse(String header) throws Exception {
-        originResponse = make200Response();
         originResponse.removeHeaders(header);
 
         backendExpectsAnyRequest().andReturn(originResponse);
@@ -3120,8 +3137,7 @@ public class TestProtocolRequirements {
         HttpRequest req1 = new BasicHttpRequest("GET", "/", HTTP_1_1);
         HttpRequest req2 = new BasicHttpRequest("GET", "/", HTTP_1_1);
 
-        originResponse = make200Response();
-        originResponse.setHeader("Cache-Control", "max-age=3600");
+        originResponse.addHeader("Cache-Control", "max-age=3600");
         originResponse.removeHeaders(header);
 
         backendExpectsAnyRequest().andReturn(originResponse);
@@ -3154,7 +3170,493 @@ public class TestProtocolRequirements {
         testDoesNotAddHeaderOnCacheHit("Last-Modified");
     }
 
+    private void testDoesNotModifyHeaderOnRequest(String header, String value) throws Exception {
+        BasicHttpEntityEnclosingRequest req = 
+            new BasicHttpEntityEnclosingRequest("POST","/",HTTP_1_1);
+        req.setEntity(makeBody(128));
+        req.setHeader("Content-Length","128");
+        req.setHeader(header,value);
+
+        Capture<HttpRequest> cap = new Capture<HttpRequest>();
+        
+        EasyMock.expect(mockBackend.execute(EasyMock.eq(host),
+                                            EasyMock.capture(cap),
+                                            (HttpContext)EasyMock.isNull()))
+            .andReturn(originResponse);
+        
+        replayMocks();
+        impl.execute(host, req);
+        verifyMocks();
+        
+        HttpRequest captured = cap.getValue();
+        Assert.assertEquals(value, captured.getFirstHeader(header).getValue());
+    }
+
+    @Test
+    public void testDoesNotModifyContentLocationHeaderOnRequest() throws Exception {
+        String url = "http://foo.example.com/other";
+        testDoesNotModifyHeaderOnRequest("Content-Location",url);
+    }
+
+    @Test
+    public void testDoesNotModifyContentMD5HeaderOnRequest() throws Exception {
+        testDoesNotModifyHeaderOnRequest("Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
+    }
+    
+    @Test
+    public void testDoesNotModifyETagHeaderOnRequest() throws Exception {
+        testDoesNotModifyHeaderOnRequest("ETag","\"etag\"");
+    }
+
+    @Test
+    public void testDoesNotModifyLastModifiedHeaderOnRequest() throws Exception {
+        long tenSecondsAgo = System.currentTimeMillis() - 10 * 1000L;
+        String lm = DateUtils.formatDate(new Date(tenSecondsAgo));
+        testDoesNotModifyHeaderOnRequest("Last-Modified", lm);
+    }
+
+    private void testDoesNotAddHeaderToRequestIfNotPresent(String header) throws Exception {
+        BasicHttpEntityEnclosingRequest req = 
+            new BasicHttpEntityEnclosingRequest("POST","/",HTTP_1_1);
+        req.setEntity(makeBody(128));
+        req.setHeader("Content-Length","128");
+        req.removeHeaders(header);
+
+        Capture<HttpRequest> cap = new Capture<HttpRequest>();
+        
+        EasyMock.expect(mockBackend.execute(EasyMock.eq(host),
+                                            EasyMock.capture(cap),
+                                            (HttpContext)EasyMock.isNull()))
+            .andReturn(originResponse);
+        
+        replayMocks();
+        impl.execute(host, req);
+        verifyMocks();
+        
+        HttpRequest captured = cap.getValue();
+        Assert.assertNull(captured.getFirstHeader(header));
+    }
+
+    @Test
+    public void testDoesNotAddContentLocationToRequestIfNotPresent() throws Exception {
+        testDoesNotAddHeaderToRequestIfNotPresent("Content-Location");
+    }
+
+    @Test
+    public void testDoesNotAddContentMD5ToRequestIfNotPresent() throws Exception {
+        testDoesNotAddHeaderToRequestIfNotPresent("Content-MD5");
+    }
+
+    @Test
+    public void testDoesNotAddETagToRequestIfNotPresent() throws Exception {
+        testDoesNotAddHeaderToRequestIfNotPresent("ETag");
+    }
+
+    @Test
+    public void testDoesNotAddLastModifiedToRequestIfNotPresent() throws Exception {
+        testDoesNotAddHeaderToRequestIfNotPresent("Last-Modified");
+    }
+
+    /* " A transparent proxy MUST NOT modify any of the following
+     * fields in a response: - Expires
+     * but it MAY add any of these fields if not already present. If
+     * an Expires header is added, it MUST be given a field-value
+     * identical to that of the Date header in that response.
+     */
+    @Test
+    public void testDoesNotModifyExpiresHeaderFromOrigin() throws Exception {
+        long inTenSeconds = System.currentTimeMillis() + 10 * 1000L;
+        String expires = DateUtils.formatDate(new Date(inTenSeconds));
+        testDoesNotModifyHeaderFromOrigin("Expires", expires);
+    }
+
+    @Test
+    public void testDoesNotModifyExpiresHeaderFromOriginOnCacheHit() throws Exception {
+        long inTenSeconds = System.currentTimeMillis() + 10 * 1000L;
+        String expires = DateUtils.formatDate(new Date(inTenSeconds));
+        testDoesNotModifyHeaderFromOriginOnCacheHit("Expires", expires);
+    }
+
+    @Test
+    public void testExpiresHeaderMatchesDateIfAddedToOriginResponse() throws Exception {
+        originResponse.removeHeaders("Expires");
+        
+        backendExpectsAnyRequest().andReturn(originResponse);
+
+        replayMocks();
+        HttpResponse result = impl.execute(host, request);
+        verifyMocks();
+
+        Header expHdr = result.getFirstHeader("Expires");
+        if (expHdr != null) {
+            Assert.assertEquals(result.getFirstHeader("Date").getValue(),
+                                expHdr.getValue());
+        }
+    }
+
+    @Test
+    public void testExpiresHeaderMatchesDateIfAddedToCacheHit() throws Exception {
+        HttpRequest req1 = new BasicHttpRequest("GET", "/", HTTP_1_1);
+        HttpRequest req2 = new BasicHttpRequest("GET", "/", HTTP_1_1);
+
+        originResponse.setHeader("Cache-Control","max-age=3600");
+        originResponse.removeHeaders("Expires");
+
+        backendExpectsAnyRequest().andReturn(originResponse);
+
+        replayMocks();
+        impl.execute(host, req1);
+        HttpResponse result = impl.execute(host, req2);
+        verifyMocks();
+
+        Header expHdr = result.getFirstHeader("Expires");
+        if (expHdr != null) {
+            Assert.assertEquals(result.getFirstHeader("Date").getValue(),
+                                expHdr.getValue());
+        }
+    }
+
+    /* "A proxy MUST NOT modify or add any of the following fields in
+     * a message that contains the no-transform cache-control
+     * directive, or in any request: - Content-Encoding - Content-Range
+     * - Content-Type"
+     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.2
+     */
+    private void testDoesNotModifyHeaderFromOriginResponseWithNoTransform(String header, String value) throws Exception {
+        originResponse.addHeader("Cache-Control","no-transform");
+        originResponse.setHeader(header, value);
+
+        backendExpectsAnyRequest().andReturn(originResponse);
+
+        replayMocks();
+        HttpResponse result = impl.execute(host, request);
+        verifyMocks();
+
+        Assert.assertEquals(value, result.getFirstHeader(header).getValue());
+    }
+
+    @Test
+    public void testDoesNotModifyContentEncodingHeaderFromOriginResponseWithNoTransform() throws Exception {
+        testDoesNotModifyHeaderFromOriginResponseWithNoTransform("Content-Encoding","gzip");
+    }
+
+    @Test
+    public void testDoesNotModifyContentRangeHeaderFromOriginResponseWithNoTransform() throws Exception {
+        request.setHeader("If-Range","\"etag\"");
+        request.setHeader("Range","bytes=0-49");
+
+        originResponse = new BasicHttpResponse(HTTP_1_1, 206, "Partial Content");
+        originResponse.setEntity(makeBody(50));
+        testDoesNotModifyHeaderFromOriginResponseWithNoTransform("Content-Range","bytes 0-49/128");
+    }
+
+    @Test
+    public void testDoesNotModifyContentTypeHeaderFromOriginResponseWithNoTransform() throws Exception {
+        testDoesNotModifyHeaderFromOriginResponseWithNoTransform("Content-Type","text/html;charset=utf-8");
+    }
+
+    private void testDoesNotModifyHeaderOnCachedResponseWithNoTransform(String header, String value) throws Exception {
+        HttpRequest req1 = new BasicHttpRequest("GET", "/", HTTP_1_1);
+        HttpRequest req2 = new BasicHttpRequest("GET", "/", HTTP_1_1);
+
+        originResponse.addHeader("Cache-Control","max-age=3600, no-transform");
+        originResponse.setHeader(header, value);
+
+        backendExpectsAnyRequest().andReturn(originResponse);
 
+        replayMocks();
+        impl.execute(host, req1);
+        HttpResponse result = impl.execute(host, req2);
+        verifyMocks();
+
+        Assert.assertEquals(value, result.getFirstHeader(header).getValue());
+    }
+
+    @Test
+    public void testDoesNotModifyContentEncodingHeaderOnCachedResponseWithNoTransform() throws Exception {
+        testDoesNotModifyHeaderOnCachedResponseWithNoTransform("Content-Encoding","gzip");
+    }
+
+    @Test
+    public void testDoesNotModifyContentTypeHeaderOnCachedResponseWithNoTransform() throws Exception {
+        testDoesNotModifyHeaderOnCachedResponseWithNoTransform("Content-Type","text/html;charset=utf-8");
+    }
+
+    @Test
+    public void testDoesNotModifyContentRangeHeaderOnCachedResponseWithNoTransform() throws Exception {
+        HttpRequest req1 = new BasicHttpRequest("GET", "/", HTTP_1_1);
+        req1.setHeader("If-Range","\"etag\"");
+        req1.setHeader("Range","bytes=0-49");
+        HttpRequest req2 = new BasicHttpRequest("GET", "/", HTTP_1_1);
+        req2.setHeader("If-Range","\"etag\"");
+        req2.setHeader("Range","bytes=0-49");
+
+        originResponse.addHeader("Cache-Control","max-age=3600, no-transform");
+        originResponse.setHeader("Content-Range", "bytes 0-49/128");
+
+        backendExpectsAnyRequest().andReturn(originResponse).times(1,2);
+
+        replayMocks();
+        impl.execute(host, req1);
+        HttpResponse result = impl.execute(host, req2);
+        verifyMocks();
+
+        Assert.assertEquals("bytes 0-49/128", 
+                            result.getFirstHeader("Content-Range").getValue());
+    }
+
+    @Test
+    public void testDoesNotAddContentEncodingHeaderToOriginResponseWithNoTransformIfNotPresent() throws Exception {
+        originResponse.addHeader("Cache-Control","no-transform");
+        testDoesNotAddHeaderToOriginResponse("Content-Encoding");
+    }
+
+    @Test
+    public void testDoesNotAddContentRangeHeaderToOriginResponseWithNoTransformIfNotPresent() throws Exception {
+        originResponse.addHeader("Cache-Control","no-transform");
+        testDoesNotAddHeaderToOriginResponse("Content-Range");
+    }
+
+    @Test
+    public void testDoesNotAddContentTypeHeaderToOriginResponseWithNoTransformIfNotPresent() throws Exception {
+        originResponse.addHeader("Cache-Control","no-transform");
+        testDoesNotAddHeaderToOriginResponse("Content-Type");
+    }
+
+    /* no add on cache hit with no-transform */
+    @Test
+    public void testDoesNotAddContentEncodingHeaderToCachedResponseWithNoTransformIfNotPresent() throws Exception {
+        originResponse.addHeader("Cache-Control","no-transform");
+        testDoesNotAddHeaderOnCacheHit("Content-Encoding");
+    }
+
+    @Test
+    public void testDoesNotAddContentRangeHeaderToCachedResponseWithNoTransformIfNotPresent() throws Exception {
+        originResponse.addHeader("Cache-Control","no-transform");
+        testDoesNotAddHeaderOnCacheHit("Content-Range");
+    }
+
+    @Test
+    public void testDoesNotAddContentTypeHeaderToCachedResponseWithNoTransformIfNotPresent() throws Exception {
+        originResponse.addHeader("Cache-Control","no-transform");
+        testDoesNotAddHeaderOnCacheHit("Content-Type");
+    }
+
+    /* no modify on request */
+    @Test
+    public void testDoesNotAddContentEncodingToRequestIfNotPresent() throws Exception {
+        testDoesNotAddHeaderToRequestIfNotPresent("Content-Encoding");
+    }
+    
+    @Test
+    public void testDoesNotAddContentRangeToRequestIfNotPresent() throws Exception {
+        testDoesNotAddHeaderToRequestIfNotPresent("Content-Range");
+    }
+
+    @Test
+    public void testDoesNotAddContentTypeToRequestIfNotPresent() throws Exception {
+        testDoesNotAddHeaderToRequestIfNotPresent("Content-Type");
+    }
+
+    @Test
+    public void testDoesNotAddContentEncodingHeaderToRequestIfNotPresent() throws Exception {
+        testDoesNotAddHeaderToRequestIfNotPresent("Content-Encoding");
+    }
+
+    @Test
+    public void testDoesNotAddContentRangeHeaderToRequestIfNotPresent() throws Exception {
+        testDoesNotAddHeaderToRequestIfNotPresent("Content-Range");
+    }
+
+    @Test
+    public void testDoesNotAddContentTypeHeaderToRequestIfNotPresent() throws Exception {
+        testDoesNotAddHeaderToRequestIfNotPresent("Content-Type");
+    }
+
+    /* "When a cache makes a validating request to a server, and the
+     * server provides a 304 (Not Modified) response or a 206 (Partial
+     * Content) response, the cache then constructs a response to send
+     * to the requesting client. 
+     *
+     * If the status code is 304 (Not Modified), the cache uses the
+     * entity-body stored in the cache entry as the entity-body of
+     * this outgoing response.
+     * 
+     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.3
+     */
+    public void testCachedEntityBodyIsUsedForResponseAfter304Validation() throws Exception {
+        HttpRequest req1 = new BasicHttpRequest("GET", "/", HTTP_1_1);
+        HttpResponse resp1 = make200Response();
+        resp1.setHeader("Cache-Control","max-age=3600");
+        resp1.setHeader("ETag","\"etag\"");
+
+        HttpRequest req2 = new BasicHttpRequest("GET", "/", HTTP_1_1);
+        req2.setHeader("Cache-Control","max-age=0, max-stale=0");
+        HttpResponse resp2 = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_NOT_MODIFIED, "Not Modified");
+        
+        backendExpectsAnyRequest().andReturn(resp1);
+        backendExpectsAnyRequest().andReturn(resp2);
+        
+        replayMocks();
+        impl.execute(host, req1);
+        HttpResponse result = impl.execute(host, req2);
+        verifyMocks();
+
+        InputStream i1 = resp1.getEntity().getContent();
+        InputStream i2 = result.getEntity().getContent();
+        int b1, b2;
+        while((b1 = i1.read()) != -1) {
+            b2 = i2.read();
+            Assert.assertEquals(b1, b2);
+        }
+        b2 = i2.read();
+        Assert.assertEquals(-1, b2);
+        i1.close();
+        i2.close();
+    }
+
+    /* "The end-to-end headers stored in the cache entry are used for
+     * the constructed response, except that ...
+     *
+     * - any end-to-end headers provided in the 304 or 206 response MUST
+     *  replace the corresponding headers from the cache entry.
+     *
+     * Unless the cache decides to remove the cache entry, it MUST
+     * also replace the end-to-end headers stored with the cache entry
+     * with corresponding headers received in the incoming response,
+     * except for Warning headers as described immediately above."
+     */
+    private void decorateWithEndToEndHeaders(HttpResponse r) {
+        r.setHeader("Allow","GET");
+        r.setHeader("Content-Encoding","gzip");
+        r.setHeader("Content-Language","en");
+        r.setHeader("Content-Length", "128");
+        r.setHeader("Content-Location","http://foo.example.com/other");
+        r.setHeader("Content-MD5", "Q2hlY2sgSW50ZWdyaXR5IQ==");
+        r.setHeader("Content-Type", "text/html;charset=utf-8");
+        r.setHeader("Expires", DateUtils.formatDate(new Date(System.currentTimeMillis() + 10 * 1000L)));
+        r.setHeader("Last-Modified", DateUtils.formatDate(new Date(System.currentTimeMillis() - 10 * 1000L)));
+        r.setHeader("Location", "http://foo.example.com/other2");
+        r.setHeader("Pragma", "x-pragma");
+        r.setHeader("Retry-After","180");
+    }
+
+    @Test
+    public void testResponseIncludesCacheEntryEndToEndHeadersForResponseAfter304Validation() throws Exception {
+        HttpRequest req1 = new BasicHttpRequest("GET", "/", HTTP_1_1);
+        HttpResponse resp1 = make200Response();
+        resp1.setHeader("Cache-Control","max-age=3600");
+        resp1.setHeader("ETag","\"etag\"");
+        decorateWithEndToEndHeaders(resp1);
+        
+        HttpRequest req2 = new BasicHttpRequest("GET", "/", HTTP_1_1);
+        req2.setHeader("Cache-Control", "max-age=0, max-stale=0");
+        HttpResponse resp2 = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_NOT_MODIFIED, "Not Modified");
+        resp2.setHeader("Date", DateUtils.formatDate(new Date()));
+        resp2.setHeader("Server", "MockServer/1.0");
+
+        backendExpectsAnyRequest().andReturn(resp1);
+        backendExpectsAnyRequest().andReturn(resp2);
+        
+        replayMocks();
+        impl.execute(host, req1);
+        HttpResponse result = impl.execute(host, req2);
+        verifyMocks();
+
+        String[] endToEndHeaders = { 
+            "Cache-Control", "ETag", "Allow", "Content-Encoding", 
+            "Content-Language", "Content-Length", "Content-Location",
+            "Content-MD5", "Content-Type", "Expires", "Last-Modified",
+            "Location", "Pragma", "Retry-After"
+        };
+        for(String h : endToEndHeaders) {
+            Assert.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp1, h),
+                                HttpTestUtils.getCanonicalHeaderValue(result, h));
+        }
+    }
+
+    @Test
+    public void testUpdatedEndToEndHeadersFrom304ArePassedOnResponseAndUpdatedInCacheEntry() throws Exception {
+
+        HttpRequest req1 = new BasicHttpRequest("GET", "/", HTTP_1_1);
+        HttpResponse resp1 = make200Response();
+        resp1.setHeader("Cache-Control","max-age=3600");
+        resp1.setHeader("ETag","\"etag\"");
+        decorateWithEndToEndHeaders(resp1);
+        
+        HttpRequest req2 = new BasicHttpRequest("GET", "/", HTTP_1_1);
+        req2.setHeader("Cache-Control", "max-age=0, max-stale=0");
+        HttpResponse resp2 = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_NOT_MODIFIED, "Not Modified");
+        resp2.setHeader("Cache-Control", "max-age=1800");
+        resp2.setHeader("Date", DateUtils.formatDate(new Date()));
+        resp2.setHeader("Server", "MockServer/1.0");
+        resp2.setHeader("Allow", "GET,HEAD");
+        resp2.setHeader("Content-Language", "en,en-us");
+        resp2.setHeader("Content-Location", "http://foo.example.com/new");
+        resp2.setHeader("Content-Type","text/html");
+        resp2.setHeader("Expires", DateUtils.formatDate(new Date(System.currentTimeMillis() + 5 * 1000L)));
+        resp2.setHeader("Location", "http://foo.example.com/new2");
+        resp2.setHeader("Pragma","x-new-pragma");
+        resp2.setHeader("Retry-After","120");
+
+        backendExpectsAnyRequest().andReturn(resp1);
+        backendExpectsAnyRequest().andReturn(resp2);
+
+        HttpRequest req3 = new BasicHttpRequest("GET", "/", HTTP_1_1);
+        
+        replayMocks();
+        impl.execute(host, req1);
+        HttpResponse result1 = impl.execute(host, req2);
+        HttpResponse result2 = impl.execute(host, req3);
+        verifyMocks();
+
+        String[] endToEndHeaders = { 
+            "Date", "Cache-Control", "Allow", "Content-Language",
+            "Content-Location", "Content-Type", "Expires", "Location", 
+            "Pragma", "Retry-After"
+        };
+        for(String h : endToEndHeaders) {
+            Assert.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp2, h),
+                                HttpTestUtils.getCanonicalHeaderValue(result1, h));
+            Assert.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp2, h),
+                                HttpTestUtils.getCanonicalHeaderValue(result2, h));
+        }
+    }
+
+    /* "If a header field-name in the incoming response matches more
+     * than one header in the cache entry, all such old headers MUST
+     * be replaced."
+     */
+    @Test
+    public void testMultiHeadersAreSuccessfullyReplacedOn304Validation() throws Exception {
+        HttpRequest req1 = new BasicHttpRequest("GET", "/", HTTP_1_1);
+        HttpResponse resp1 = make200Response();
+        resp1.addHeader("Cache-Control","max-age=3600");
+        resp1.addHeader("Cache-Control","public");
+        resp1.setHeader("ETag","\"etag\"");
+        
+        HttpRequest req2 = new BasicHttpRequest("GET", "/", HTTP_1_1);
+        req2.setHeader("Cache-Control", "max-age=0, max-stale=0");
+        HttpResponse resp2 = new BasicHttpResponse(HTTP_1_1, HttpStatus.SC_NOT_MODIFIED, "Not Modified");
+        resp2.setHeader("Cache-Control", "max-age=1800");
+
+        backendExpectsAnyRequest().andReturn(resp1);
+        backendExpectsAnyRequest().andReturn(resp2);
+
+        HttpRequest req3 = new BasicHttpRequest("GET", "/", HTTP_1_1);
+        
+        replayMocks();
+        impl.execute(host, req1);
+        HttpResponse result1 = impl.execute(host, req2);
+        HttpResponse result2 = impl.execute(host, req3);
+        verifyMocks();
+
+        final String h = "Cache-Control";
+        Assert.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp2, h),
+                            HttpTestUtils.getCanonicalHeaderValue(result1, h));
+        Assert.assertEquals(HttpTestUtils.getCanonicalHeaderValue(resp2, h),
+                            HttpTestUtils.getCanonicalHeaderValue(result2, h));
+    }
 
     private class FakeHeaderGroup extends HeaderGroup{