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 2012/12/20 17:56:43 UTC

svn commit: r1424589 - 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/

Author: olegk
Date: Thu Dec 20 16:56:41 2012
New Revision: 1424589

URL: http://svn.apache.org/viewvc?rev=1424589&view=rev
Log:
HTTPCLIENT-1250: Allow query string to be ignored when determining cacheability for HTTP 1.0 responses
Contributed by Don Brown <mrdon at twdata.org>

Modified:
    httpcomponents/httpclient/trunk/RELEASE_NOTES.txt
    httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheConfig.java
    httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java
    httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java
    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/TestProtocolRecommendations.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=1424589&r1=1424588&r2=1424589&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/RELEASE_NOTES.txt (original)
+++ httpcomponents/httpclient/trunk/RELEASE_NOTES.txt Thu Dec 20 16:56:41 2012
@@ -1,6 +1,10 @@
 Changes in trunk
 -------------------
 
+* [HTTPCLIENT-1250] Allow query string to be ignored when determining cacheability for HTTP 1.0 
+  responses.
+  Contributed by Don Brown <mrdon at twdata.org> 
+
 * [HTTPCLIENT-1284] HttpClient incorrectly generates Host header when physical connection
   route differs from the host name specified in the request URI. 
   Contributed by Oleg Kalnichevski <olegk at apache.org>

Modified: httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheConfig.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheConfig.java?rev=1424589&r1=1424588&r2=1424589&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheConfig.java (original)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CacheConfig.java Thu Dec 20 16:56:41 2012
@@ -145,6 +145,7 @@ public class CacheConfig implements Clon
     private int asynchronousWorkersCore;
     private int asynchronousWorkerIdleLifetimeSecs;
     private int revalidationQueueSize;
+    private boolean neverCacheHTTP10ResponsesWithQuery;
 
     /**
      * @deprecated (4.3) use {@link Builder}.
@@ -176,7 +177,8 @@ public class CacheConfig implements Clon
             int asynchronousWorkersMax,
             int asynchronousWorkersCore,
             int asynchronousWorkerIdleLifetimeSecs,
-            int revalidationQueueSize) {
+            int revalidationQueueSize,
+            boolean neverCacheHTTP10ResponsesWithQuery) {
         super();
         this.maxObjectSize = maxObjectSize;
         this.maxCacheEntries = maxCacheEntries;
@@ -241,6 +243,15 @@ public class CacheConfig implements Clon
     }
 
     /**
+     * Returns whether the cache will never cache HTTP 1.0 responses with a query string or not.
+     * @return {@code true} to not cache query string responses, {@code false} to cache if explicit cache headers are
+     * found
+     */
+    public boolean isNeverCacheHTTP10ResponsesWithQuery() {
+        return neverCacheHTTP10ResponsesWithQuery;
+    }
+
+    /**
      * Returns the maximum number of cache entries the cache will retain.
      */
     public int getMaxCacheEntries() {
@@ -470,6 +481,7 @@ public class CacheConfig implements Clon
         private int asynchronousWorkersCore;
         private int asynchronousWorkerIdleLifetimeSecs;
         private int revalidationQueueSize;
+        private boolean neverCacheHTTP10ResponsesWithQuery;
 
         Builder() {
             this.maxObjectSize = DEFAULT_MAX_OBJECT_SIZE_BYTES;
@@ -602,6 +614,18 @@ public class CacheConfig implements Clon
             return this;
         }
 
+        /**
+         * Sets whether the cache should never cache HTTP 1.0 responses with a query string or not.
+         * @param neverCache1_0ResponsesWithQueryString true to never cache responses with a query
+         * string, false to cache if explicit cache headers are found.  Set this to {@code true}
+         * to better emulate IE, which also never caches responses, regardless of what caching
+         * headers may be present.
+         */
+        public Builder setNeverCache1_0ResponsesWithQueryString(boolean b) {
+            this.neverCacheHTTP10ResponsesWithQuery = b;
+            return this;
+        }
+
         public CacheConfig build() {
             return new CacheConfig(
                     maxObjectSize,
@@ -614,7 +638,8 @@ public class CacheConfig implements Clon
                     asynchronousWorkersMax,
                     asynchronousWorkersCore,
                     asynchronousWorkerIdleLifetimeSecs,
-                    revalidationQueueSize);
+                    revalidationQueueSize,
+                    neverCacheHTTP10ResponsesWithQuery);
         }
 
     }
@@ -632,6 +657,7 @@ public class CacheConfig implements Clon
                 .append(", asynchronousWorkersCore=").append(this.asynchronousWorkersCore)
                 .append(", asynchronousWorkerIdleLifetimeSecs=").append(this.asynchronousWorkerIdleLifetimeSecs)
                 .append(", revalidationQueueSize=").append(this.revalidationQueueSize)
+                .append(", neverCacheHTTP10ResponsesWithQuery=").append(this.neverCacheHTTP10ResponsesWithQuery)
                 .append("]");
         return builder.toString();
     }

Modified: httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java?rev=1424589&r1=1424588&r2=1424589&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java (original)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingExec.java Thu Dec 20 16:56:41 2012
@@ -133,7 +133,8 @@ public class CachingExec implements Clie
         this.responseCompliance = new ResponseProtocolCompliance();
         this.requestCompliance = new RequestProtocolCompliance();
         this.responseCachingPolicy = new ResponseCachingPolicy(
-                this.cacheConfig.getMaxObjectSize(), this.cacheConfig.isSharedCache());
+                this.cacheConfig.getMaxObjectSize(), this.cacheConfig.isSharedCache(),
+                this.cacheConfig.isNeverCacheHTTP10ResponsesWithQuery());
         this.asynchRevalidator = makeAsynchronousValidator(config);
     }
 

Modified: httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java?rev=1424589&r1=1424588&r2=1424589&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java (original)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/main/java/org/apache/http/impl/client/cache/CachingHttpClient.java Thu Dec 20 16:56:41 2012
@@ -174,7 +174,8 @@ public class CachingHttpClient implement
         this.backend = client;
         this.responseCache = cache;
         this.validityPolicy = new CacheValidityPolicy();
-        this.responseCachingPolicy = new ResponseCachingPolicy(maxObjectSizeBytes, sharedCache);
+        this.responseCachingPolicy = new ResponseCachingPolicy(maxObjectSizeBytes, sharedCache,
+                config.isNeverCacheHTTP10ResponsesWithQuery());
         this.responseGenerator = new CachedHttpResponseGenerator(this.validityPolicy);
         this.cacheableRequestPolicy = new CacheableRequestPolicy();
         this.suitabilityChecker = new CachedResponseSuitabilityChecker(this.validityPolicy, config);

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=1424589&r1=1424588&r2=1424589&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 Thu Dec 20 16:56:41 2012
@@ -56,6 +56,7 @@ class ResponseCachingPolicy {
 
     private final long maxObjectSizeBytes;
     private final boolean sharedCache;
+    private final boolean neverCache1_0ResponsesWithQueryString;
     private final Log log = LogFactory.getLog(getClass());
     private static final Set<Integer> cacheableStatuses = 
     	new HashSet<Integer>(Arrays.asList(HttpStatus.SC_OK,
@@ -66,7 +67,6 @@ class ResponseCachingPolicy {
     private static final Set<Integer> uncacheableStatuses =
     		new HashSet<Integer>(Arrays.asList(HttpStatus.SC_PARTIAL_CONTENT,
     				HttpStatus.SC_SEE_OTHER));
-    	
     /**
      * Define a cache policy that limits the size of things that should be stored
      * in the cache to a maximum of {@link HttpResponse} bytes in size.
@@ -74,10 +74,15 @@ class ResponseCachingPolicy {
      * @param maxObjectSizeBytes the size to limit items into the cache
      * @param sharedCache whether to behave as a shared cache (true) or a
      * non-shared/private cache (false)
+     * @param neverCache1_0ResponsesWithQueryString true to never cache HTTP 1.0 responses with a query string, false
+     * to cache if explicit cache headers are found.
      */
-    public ResponseCachingPolicy(long maxObjectSizeBytes, boolean sharedCache) {
+    public ResponseCachingPolicy(long maxObjectSizeBytes, boolean sharedCache,
+                                 boolean neverCache1_0ResponsesWithQueryString
+    ) {
         this.maxObjectSizeBytes = maxObjectSizeBytes;
         this.sharedCache = sharedCache;
+        this.neverCache1_0ResponsesWithQueryString = neverCache1_0ResponsesWithQueryString;
     }
 
     /**
@@ -216,10 +221,14 @@ class ResponseCachingPolicy {
             return false;
         }
 
-        if (request.getRequestLine().getUri().contains("?") &&
-            (!isExplicitlyCacheable(response) || from1_0Origin(response))) {
-            log.debug("Response was not cacheable.");
-            return false;
+        if (request.getRequestLine().getUri().contains("?")) {
+            if (neverCache1_0ResponsesWithQueryString && from1_0Origin(response)) {
+                log.debug("Response was not cacheable as it had a query string.");
+                return false;
+            } else if (!isExplicitlyCacheable(response)) {
+                log.debug("Response was not cacheable as it is missing explicit caching headers.");
+                return false;
+            }
         }
 
         if (expiresHeaderLessOrEqualToDateHeaderAndNoCacheControl(response)) {

Modified: httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRecommendations.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRecommendations.java?rev=1424589&r1=1424588&r2=1424589&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRecommendations.java (original)
+++ httpcomponents/httpclient/trunk/httpclient-cache/src/test/java/org/apache/http/impl/client/cache/TestProtocolRecommendations.java Thu Dec 20 16:56:41 2012
@@ -65,7 +65,6 @@ import org.junit.Test;
  */
 public class TestProtocolRecommendations extends AbstractProtocolTest {
 
-    private Date tenSecondsFromNow;
     private Date now;
     private Date tenSecondsAgo;
     private Date twoMinutesAgo;
@@ -77,7 +76,6 @@ public class TestProtocolRecommendations
         now = new Date();
         tenSecondsAgo = new Date(now.getTime() - 10 * 1000L);
         twoMinutesAgo = new Date(now.getTime() - 2 * 60 * 1000L);
-        tenSecondsFromNow = new Date(now.getTime() + 10 * 1000L);
     }
 
     /* "identity: The default (identity) encoding; the use of no
@@ -1445,18 +1443,8 @@ public class TestProtocolRecommendations
      * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.9
      */
     @Test
-    public void responseToGetWithQueryFrom1_0OriginIsNotCached()
+    public void responseToGetWithQueryFrom1_0OriginAndNoExpiresIsNotCached()
         throws Exception {
-        HttpRequestWrapper req1 = HttpRequestWrapper.wrap(
-                new HttpGet("http://foo.example.com/bar?baz=quux"));
-        HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK");
-        resp1.setEntity(HttpTestUtils.makeBody(200));
-        resp1.setHeader("Content-Length","200");
-        resp1.setHeader("Date", formatDate(now));
-        resp1.setHeader("Expires", formatDate(tenSecondsFromNow));
-
-        backendExpectsAnyRequestAndReturn(resp1);
-
         HttpRequestWrapper req2 = HttpRequestWrapper.wrap(
                 new HttpGet("http://foo.example.com/bar?baz=quux"));
         HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK");
@@ -1467,25 +1455,13 @@ public class TestProtocolRecommendations
         backendExpectsAnyRequestAndReturn(resp2);
 
         replayMocks();
-        impl.execute(route, req1);
         impl.execute(route, req2);
         verifyMocks();
     }
 
     @Test
-    public void responseToGetWithQueryFrom1_0OriginVia1_1ProxyIsNotCached()
+    public void responseToGetWithQueryFrom1_0OriginVia1_1ProxyAndNoExpiresIsNotCached()
         throws Exception {
-        HttpRequestWrapper req1 = HttpRequestWrapper.wrap(
-                new HttpGet("http://foo.example.com/bar?baz=quux"));
-        HttpResponse resp1 = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
-        resp1.setEntity(HttpTestUtils.makeBody(200));
-        resp1.setHeader("Content-Length","200");
-        resp1.setHeader("Date", formatDate(now));
-        resp1.setHeader("Expires", formatDate(tenSecondsFromNow));
-        resp1.setHeader("Via","1.0 someproxy");
-
-        backendExpectsAnyRequestAndReturn(resp1);
-
         HttpRequestWrapper req2 = HttpRequestWrapper.wrap(
                 new HttpGet("http://foo.example.com/bar?baz=quux"));
         HttpResponse resp2 = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK");
@@ -1497,7 +1473,6 @@ public class TestProtocolRecommendations
         backendExpectsAnyRequestAndReturn(resp2);
 
         replayMocks();
-        impl.execute(route, req1);
         impl.execute(route, req2);
         verifyMocks();
     }

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=1424589&r1=1424588&r2=1424589&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 Thu Dec 20 16:56:41 2012
@@ -61,7 +61,7 @@ public class TestResponseCachingPolicy {
         sixSecondsAgo = new Date(now.getTime() - 6 * 1000L);
         tenSecondsFromNow = new Date(now.getTime() + 10 * 1000L);
 
-        policy = new ResponseCachingPolicy(0, true);
+        policy = new ResponseCachingPolicy(0, true, false);
         request = new BasicHttpRequest("GET","/",HTTP_1_1);
         response = new BasicHttpResponse(
                 new BasicStatusLine(HTTP_1_1, HttpStatus.SC_OK, ""));
@@ -83,7 +83,7 @@ public class TestResponseCachingPolicy {
 
     @Test
     public void testResponsesToRequestsWithAuthorizationHeadersAreCacheableByNonSharedCache() {
-        policy = new ResponseCachingPolicy(0, false);
+        policy = new ResponseCachingPolicy(0, false, false);
         request = new BasicHttpRequest("GET","/",HTTP_1_1);
         request.setHeader("Authorization","Basic dXNlcjpwYXNzd2Q=");
         Assert.assertTrue(policy.isResponseCacheable(request,response));
@@ -225,7 +225,7 @@ public class TestResponseCachingPolicy {
 
     @Test
     public void test200ResponseWithPrivateCacheControlIsCacheableByNonSharedCache() {
-        policy = new ResponseCachingPolicy(0, false);
+        policy = new ResponseCachingPolicy(0, false, false);
         response.setStatusCode(HttpStatus.SC_OK);
         response.setHeader("Cache-Control", "private");
         Assert.assertTrue(policy.isResponseCacheable("GET", response));
@@ -384,6 +384,13 @@ public class TestResponseCachingPolicy {
     }
 
     @Test
+    public void testResponsesToGETWithQueryParamsButNoExplicitCachingAreNotCacheableEvenWhen1_0QueryCachingDisabled() {
+        policy = new ResponseCachingPolicy(0, true, true);
+        request = new BasicHttpRequest("GET", "/foo?s=bar");
+        Assert.assertFalse(policy.isResponseCacheable(request, response));
+    }
+
+    @Test
     public void testResponsesToGETWithQueryParamsAndExplicitCachingAreCacheable() {
         request = new BasicHttpRequest("GET", "/foo?s=bar");
         response.setHeader("Date", formatDate(now));
@@ -392,6 +399,15 @@ public class TestResponseCachingPolicy {
     }
 
     @Test
+    public void testResponsesToGETWithQueryParamsAndExplicitCachingAreCacheableEvenWhen1_0QueryCachingDisabled() {
+        policy = new ResponseCachingPolicy(0, true, true);
+        request = new BasicHttpRequest("GET", "/foo?s=bar");
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
+        Assert.assertTrue(policy.isResponseCacheable(request, response));
+    }
+
+    @Test
     public void getsWithQueryParametersDirectlyFrom1_0OriginsAreNotCacheable() {
         request = new BasicHttpRequest("GET", "/foo?s=bar");
         response = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK");
@@ -399,7 +415,25 @@ public class TestResponseCachingPolicy {
     }
 
     @Test
-    public void getsWithQueryParametersDirectlyFrom1_0OriginsAreNotCacheableEvenWithExpires() {
+    public void getsWithQueryParametersDirectlyFrom1_0OriginsAreNotCacheableEvenWithSetting() {
+        policy = new ResponseCachingPolicy(0, true, true);
+        request = new BasicHttpRequest("GET", "/foo?s=bar");
+        response = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK");
+        Assert.assertFalse(policy.isResponseCacheable(request, response));
+    }
+
+    @Test
+    public void getsWithQueryParametersDirectlyFrom1_0OriginsAreCacheableWithExpires() {
+        request = new BasicHttpRequest("GET", "/foo?s=bar");
+        response = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK");
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
+        Assert.assertTrue(policy.isResponseCacheable(request, response));
+    }
+
+    @Test
+    public void getsWithQueryParametersDirectlyFrom1_0OriginsCanBeNotCacheableEvenWithExpires() {
+        policy = new ResponseCachingPolicy(0, true, true);
         request = new BasicHttpRequest("GET", "/foo?s=bar");
         response = new BasicHttpResponse(HttpVersion.HTTP_1_0, HttpStatus.SC_OK, "OK");
         response.setHeader("Date", formatDate(now));
@@ -415,7 +449,19 @@ public class TestResponseCachingPolicy {
     }
 
     @Test
-    public void getsWithQueryParametersFrom1_0OriginsViaProxiesAreNotCacheableEvenWithExpires() {
+    public void getsWithQueryParametersFrom1_0OriginsViaProxiesAreCacheableWithExpires() {
+        request = new BasicHttpRequest("GET", "/foo?s=bar");
+        Date now = new Date();
+        Date tenSecondsFromNow = new Date(now.getTime() + 10 * 1000L);
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
+        response.setHeader("Via", "1.0 someproxy");
+        Assert.assertTrue(policy.isResponseCacheable(request, response));
+    }
+
+    @Test
+    public void getsWithQueryParametersFrom1_0OriginsViaProxiesCanNotBeCacheableEvenWithExpires() {
+        policy = new ResponseCachingPolicy(0, true, true);
         request = new BasicHttpRequest("GET", "/foo?s=bar");
         Date now = new Date();
         Date tenSecondsFromNow = new Date(now.getTime() + 10 * 1000L);
@@ -426,7 +472,17 @@ public class TestResponseCachingPolicy {
     }
 
     @Test
-    public void getsWithQueryParametersFrom1_0OriginsViaExplicitProxiesAreNotCacheableEvenWithExpires() {
+    public void getsWithQueryParametersFrom1_0OriginsViaExplicitProxiesAreCacheableWithExpires() {
+        request = new BasicHttpRequest("GET", "/foo?s=bar");
+        response.setHeader("Date", formatDate(now));
+        response.setHeader("Expires", formatDate(tenSecondsFromNow));
+        response.setHeader("Via", "HTTP/1.0 someproxy");
+        Assert.assertTrue(policy.isResponseCacheable(request, response));
+    }
+
+    @Test
+    public void getsWithQueryParametersFrom1_0OriginsViaExplicitProxiesCanNotBeCacheableEvenWithExpires() {
+        policy = new ResponseCachingPolicy(0, true, true);
         request = new BasicHttpRequest("GET", "/foo?s=bar");
         response.setHeader("Date", formatDate(now));
         response.setHeader("Expires", formatDate(tenSecondsFromNow));