Repository: liberapay/liberapay.com

Issue:
`Cache-Control: immutable`
It's [a new experimental HTTP feature](https://bitsup.blogspot.fr/2016/05/cache-control-immutable.html) that we should probably start using. It's low priority though.



Discussion:

Resolving patch:
diff --git a/liberapay/utils/http_caching.py b/liberapay/utils/http_caching.py
--- a/liberapay/utils/http_caching.py
+++ b/liberapay/utils/http_caching.py
@@ -79,47 +79,41 @@ def try_to_serve_304(dispatch_result, request, response, etag):
         # This is a request for a dynamic resource.
         return
 
+    # Compare the etag in the request's querystring to the one we have.
     qs_etag = request.qs.get('etag')
     if qs_etag and qs_etag != etag:
         # Don't serve one version of a file as if it were another.
         raise response.error(410)
 
+    # Compare the etag in the request's headers to the one we have.
     headers_etag = request.headers.get(b'If-None-Match', b'').decode('ascii', 'replace')
-    if not headers_etag:
-        # This client doesn't want a 304.
-        return
-
-    if headers_etag != etag:
-        # Cache miss, the client sent an old or invalid etag.
-        return
-
-    # Huzzah!
-    # =======
-    # We can serve a 304! :D
+    if headers_etag and headers_etag == etag:
+        # Success! We can serve a 304.
+        raise response.success(304)
 
-    raise response.success(304)
 
-
-def add_caching_to_response(response, request=None, etag=None):
+def add_caching_to_response(state, website, response, request=None, etag=None):
     """Set caching headers.
     """
-    if not etag:
-        # This is a dynamic resource, disable caching by default
-        if b'Cache-Control' not in response.headers:
-            response.headers[b'Cache-Control'] = b'no-cache'
-        return
-
-    assert request is not None  # sanity check
-
     if response.code not in (200, 304):
         return
-
-    # https://developers.google.com/speed/docs/best-practices/caching
-    response.headers[b'Etag'] = etag.encode('ascii')
-
-    if request.qs.get('etag'):
-        # We can cache "indefinitely" when the querystring contains the etag.
-        response.headers[b'Cache-Control'] = b'public, max-age=31536000'
+    if b'Cache-Control' in response.headers:
+        # The caching policy has already been defined somewhere else
+        return
+    if etag:
+        try:
+            assert not response.headers.cookie
+        except Exception as e:
+            website.tell_sentry(e, state)
+            response.headers.cookie.clear()
+        # https://developers.google.com/speed/docs/best-practices/caching
+        response.headers[b'Etag'] = etag.encode('ascii')
+        if request.qs.get('etag'):
+            # We can cache "indefinitely" when the querystring contains the etag.
+            response.headers[b'Cache-Control'] = b'public, max-age=31536000, immutable'
+        else:
+            # Otherwise we cache for 1 hour
+            response.headers[b'Cache-Control'] = b'public, max-age=3600'
     else:
-        # Otherwise we cache for 1 hour
-        response.headers[b'Cache-Control'] = b'public, max-age=3600'
+        # This is a dynamic resource, disable caching by default
+        response.headers[b'Cache-Control'] = b'no-cache'


Test patch:
diff --git a/tests/py/test_state_chain.py b/tests/py/test_state_chain.py
--- a/tests/py/test_state_chain.py
+++ b/tests/py/test_state_chain.py
@@ -123,7 +123,7 @@ def test_caching_of_assets_with_etag(self):
         url = self.client.website.asset('jquery.min.js')
         assert url.startswith('http://localhost/assets/jquery.min.js?etag=')
         r = self.client.GET(url[len('http://localhost'):])
-        assert r.headers[b'Cache-Control'] == b'public, max-age=31536000'
+        assert r.headers[b'Cache-Control'] == b'public, max-age=31536000, immutable'
         assert b'Vary' not in r.headers
         assert not r.headers.cookie