We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `liberapay/utils/http_caching.py`
Content:
```
1 """
2 Handles HTTP caching.
3 """
4 
5 import atexit
6 from hashlib import md5
7 import os
8 from tempfile import mkstemp
9 
10 from aspen.request_processor.dispatcher import DispatchResult, DispatchStatus
11 from pando import Response
12 
13 from liberapay.utils import b64encode_s, find_files
14 
15 
16 ETAGS = {}
17 
18 
19 def compile_assets(website):
20     cleanup = []
21     for spt in find_files(website.www_root+'/assets/', '*.spt'):
22         filepath = spt[:-4]  # /path/to/www/assets/foo.css
23         if not os.path.exists(filepath):
24             cleanup.append(filepath)
25         dispatch_result = DispatchResult(DispatchStatus.okay, spt, None, None, None)
26         state = dict(dispatch_result=dispatch_result, response=Response())
27         state['state'] = state
28         resource = website.request_processor.resources.get(spt)
29         content = resource.render(state, dispatch_result, None).body
30         if not isinstance(content, bytes):
31             content = content.encode('utf8')
32         tmpfd, tmpfpath = mkstemp(dir='.')
33         os.write(tmpfd, content)
34         os.close(tmpfd)
35         os.rename(tmpfpath, filepath)
36     if website.env.clean_assets:
37         atexit.register(lambda: rm_f(*cleanup))
38 
39 
40 def rm_f(*paths):
41     for path in paths:
42         try:
43             os.unlink(path)
44         except Exception:
45             pass
46 
47 
48 def clean_assets(www_root):
49     rm_f(*[spt[:-4] for spt in find_files(www_root+'/assets/', '*.spt')])
50 
51 
52 def asset_etag(path):
53     if path.endswith('.spt'):
54         return ''
55     if path in ETAGS:
56         return ETAGS[path]
57     with open(path, 'rb') as f:
58         h = b64encode_s(md5(f.read()).digest())
59     ETAGS[path] = h
60     return h
61 
62 
63 # algorithm functions
64 
65 def get_etag_for_file(dispatch_result, website, state):
66     if dispatch_result.status != DispatchStatus.okay:
67         return {'etag': None}
68     try:
69         return {'etag': asset_etag(dispatch_result.match)}
70     except Exception as e:
71         website.tell_sentry(e, state)
72         return {'etag': None}
73 
74 
75 def try_to_serve_304(dispatch_result, request, response, etag):
76     """Try to serve a 304 for static resources.
77     """
78     if not etag:
79         # This is a request for a dynamic resource.
80         return
81 
82     qs_etag = request.qs.get('etag')
83     if qs_etag and qs_etag != etag:
84         # Don't serve one version of a file as if it were another.
85         raise response.error(410)
86 
87     headers_etag = request.headers.get(b'If-None-Match', b'').decode('ascii', 'replace')
88     if not headers_etag:
89         # This client doesn't want a 304.
90         return
91 
92     if headers_etag != etag:
93         # Cache miss, the client sent an old or invalid etag.
94         return
95 
96     # Huzzah!
97     # =======
98     # We can serve a 304! :D
99 
100     raise response.success(304)
101 
102 
103 def add_caching_to_response(response, request=None, etag=None):
104     """Set caching headers.
105     """
106     if not etag:
107         # This is a dynamic resource, disable caching by default
108         if b'Cache-Control' not in response.headers:
109             response.headers[b'Cache-Control'] = b'no-cache'
110         return
111 
112     assert request is not None  # sanity check
113 
114     if response.code not in (200, 304):
115         return
116 
117     # https://developers.google.com/speed/docs/best-practices/caching
118     response.headers[b'Etag'] = etag.encode('ascii')
119 
120     if request.qs.get('etag'):
121         # We can cache "indefinitely" when the querystring contains the etag.
122         response.headers[b'Cache-Control'] = b'public, max-age=31536000'
123     else:
124         # Otherwise we cache for 1 hour
125         response.headers[b'Cache-Control'] = b'public, max-age=3600'
126
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
         # 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'

{"golden_diff": "diff --git a/liberapay/utils/http_caching.py b/liberapay/utils/http_caching.py\n--- a/liberapay/utils/http_caching.py\n+++ b/liberapay/utils/http_caching.py\n@@ -79,47 +79,41 @@\n         # This is a request for a dynamic resource.\n         return\n \n+    # Compare the etag in the request's querystring to the one we have.\n     qs_etag = request.qs.get('etag')\n     if qs_etag and qs_etag != etag:\n         # Don't serve one version of a file as if it were another.\n         raise response.error(410)\n \n+    # Compare the etag in the request's headers to the one we have.\n     headers_etag = request.headers.get(b'If-None-Match', b'').decode('ascii', 'replace')\n-    if not headers_etag:\n-        # This client doesn't want a 304.\n-        return\n-\n-    if headers_etag != etag:\n-        # Cache miss, the client sent an old or invalid etag.\n-        return\n-\n-    # Huzzah!\n-    # =======\n-    # We can serve a 304! :D\n+    if headers_etag and headers_etag == etag:\n+        # Success! We can serve a 304.\n+        raise response.success(304)\n \n-    raise response.success(304)\n \n-\n-def add_caching_to_response(response, request=None, etag=None):\n+def add_caching_to_response(state, website, response, request=None, etag=None):\n     \"\"\"Set caching headers.\n     \"\"\"\n-    if not etag:\n-        # This is a dynamic resource, disable caching by default\n-        if b'Cache-Control' not in response.headers:\n-            response.headers[b'Cache-Control'] = b'no-cache'\n-        return\n-\n-    assert request is not None  # sanity check\n-\n     if response.code not in (200, 304):\n         return\n-\n-    # https://developers.google.com/speed/docs/best-practices/caching\n-    response.headers[b'Etag'] = etag.encode('ascii')\n-\n-    if request.qs.get('etag'):\n-        # We can cache \"indefinitely\" when the querystring contains the etag.\n-        response.headers[b'Cache-Control'] = b'public, max-age=31536000'\n+    if b'Cache-Control' in response.headers:\n+        # The caching policy has already been defined somewhere else\n+        return\n+    if etag:\n+        try:\n+            assert not response.headers.cookie\n+        except Exception as e:\n+            website.tell_sentry(e, state)\n+            response.headers.cookie.clear()\n+        # https://developers.google.com/speed/docs/best-practices/caching\n+        response.headers[b'Etag'] = etag.encode('ascii')\n+        if request.qs.get('etag'):\n+            # We can cache \"indefinitely\" when the querystring contains the etag.\n+            response.headers[b'Cache-Control'] = b'public, max-age=31536000, immutable'\n+        else:\n+            # Otherwise we cache for 1 hour\n+            response.headers[b'Cache-Control'] = b'public, max-age=3600'\n     else:\n-        # Otherwise we cache for 1 hour\n-        response.headers[b'Cache-Control'] = b'public, max-age=3600'\n+        # This is a dynamic resource, disable caching by default\n+        response.headers[b'Cache-Control'] = b'no-cache'\n", "issue": "`Cache-Control: immutable`\nIt'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.\n\n", "before_files": [{"content": "\"\"\"\nHandles HTTP caching.\n\"\"\"\n\nimport atexit\nfrom hashlib import md5\nimport os\nfrom tempfile import mkstemp\n\nfrom aspen.request_processor.dispatcher import DispatchResult, DispatchStatus\nfrom pando import Response\n\nfrom liberapay.utils import b64encode_s, find_files\n\n\nETAGS = {}\n\n\ndef compile_assets(website):\n    cleanup = []\n    for spt in find_files(website.www_root+'/assets/', '*.spt'):\n        filepath = spt[:-4]  # /path/to/www/assets/foo.css\n        if not os.path.exists(filepath):\n            cleanup.append(filepath)\n        dispatch_result = DispatchResult(DispatchStatus.okay, spt, None, None, None)\n        state = dict(dispatch_result=dispatch_result, response=Response())\n        state['state'] = state\n        resource = website.request_processor.resources.get(spt)\n        content = resource.render(state, dispatch_result, None).body\n        if not isinstance(content, bytes):\n            content = content.encode('utf8')\n        tmpfd, tmpfpath = mkstemp(dir='.')\n        os.write(tmpfd, content)\n        os.close(tmpfd)\n        os.rename(tmpfpath, filepath)\n    if website.env.clean_assets:\n        atexit.register(lambda: rm_f(*cleanup))\n\n\ndef rm_f(*paths):\n    for path in paths:\n        try:\n            os.unlink(path)\n        except Exception:\n            pass\n\n\ndef clean_assets(www_root):\n    rm_f(*[spt[:-4] for spt in find_files(www_root+'/assets/', '*.spt')])\n\n\ndef asset_etag(path):\n    if path.endswith('.spt'):\n        return ''\n    if path in ETAGS:\n        return ETAGS[path]\n    with open(path, 'rb') as f:\n        h = b64encode_s(md5(f.read()).digest())\n    ETAGS[path] = h\n    return h\n\n\n# algorithm functions\n\ndef get_etag_for_file(dispatch_result, website, state):\n    if dispatch_result.status != DispatchStatus.okay:\n        return {'etag': None}\n    try:\n        return {'etag': asset_etag(dispatch_result.match)}\n    except Exception as e:\n        website.tell_sentry(e, state)\n        return {'etag': None}\n\n\ndef try_to_serve_304(dispatch_result, request, response, etag):\n    \"\"\"Try to serve a 304 for static resources.\n    \"\"\"\n    if not etag:\n        # This is a request for a dynamic resource.\n        return\n\n    qs_etag = request.qs.get('etag')\n    if qs_etag and qs_etag != etag:\n        # Don't serve one version of a file as if it were another.\n        raise response.error(410)\n\n    headers_etag = request.headers.get(b'If-None-Match', b'').decode('ascii', 'replace')\n    if not headers_etag:\n        # This client doesn't want a 304.\n        return\n\n    if headers_etag != etag:\n        # Cache miss, the client sent an old or invalid etag.\n        return\n\n    # Huzzah!\n    # =======\n    # We can serve a 304! :D\n\n    raise response.success(304)\n\n\ndef add_caching_to_response(response, request=None, etag=None):\n    \"\"\"Set caching headers.\n    \"\"\"\n    if not etag:\n        # This is a dynamic resource, disable caching by default\n        if b'Cache-Control' not in response.headers:\n            response.headers[b'Cache-Control'] = b'no-cache'\n        return\n\n    assert request is not None  # sanity check\n\n    if response.code not in (200, 304):\n        return\n\n    # https://developers.google.com/speed/docs/best-practices/caching\n    response.headers[b'Etag'] = etag.encode('ascii')\n\n    if request.qs.get('etag'):\n        # We can cache \"indefinitely\" when the querystring contains the etag.\n        response.headers[b'Cache-Control'] = b'public, max-age=31536000'\n    else:\n        # Otherwise we cache for 1 hour\n        response.headers[b'Cache-Control'] = b'public, max-age=3600'\n", "path": "liberapay/utils/http_caching.py"}], "after_files": [{"content": "\"\"\"\nHandles HTTP caching.\n\"\"\"\n\nimport atexit\nfrom hashlib import md5\nimport os\nfrom tempfile import mkstemp\n\nfrom aspen.request_processor.dispatcher import DispatchResult, DispatchStatus\nfrom pando import Response\n\nfrom liberapay.utils import b64encode_s, find_files\n\n\nETAGS = {}\n\n\ndef compile_assets(website):\n    cleanup = []\n    for spt in find_files(website.www_root+'/assets/', '*.spt'):\n        filepath = spt[:-4]  # /path/to/www/assets/foo.css\n        if not os.path.exists(filepath):\n            cleanup.append(filepath)\n        dispatch_result = DispatchResult(DispatchStatus.okay, spt, None, None, None)\n        state = dict(dispatch_result=dispatch_result, response=Response())\n        state['state'] = state\n        resource = website.request_processor.resources.get(spt)\n        content = resource.render(state, dispatch_result, None).body\n        if not isinstance(content, bytes):\n            content = content.encode('utf8')\n        tmpfd, tmpfpath = mkstemp(dir='.')\n        os.write(tmpfd, content)\n        os.close(tmpfd)\n        os.rename(tmpfpath, filepath)\n    if website.env.clean_assets:\n        atexit.register(lambda: rm_f(*cleanup))\n\n\ndef rm_f(*paths):\n    for path in paths:\n        try:\n            os.unlink(path)\n        except Exception:\n            pass\n\n\ndef clean_assets(www_root):\n    rm_f(*[spt[:-4] for spt in find_files(www_root+'/assets/', '*.spt')])\n\n\ndef asset_etag(path):\n    if path.endswith('.spt'):\n        return ''\n    if path in ETAGS:\n        return ETAGS[path]\n    with open(path, 'rb') as f:\n        h = b64encode_s(md5(f.read()).digest())\n    ETAGS[path] = h\n    return h\n\n\n# algorithm functions\n\ndef get_etag_for_file(dispatch_result, website, state):\n    if dispatch_result.status != DispatchStatus.okay:\n        return {'etag': None}\n    try:\n        return {'etag': asset_etag(dispatch_result.match)}\n    except Exception as e:\n        website.tell_sentry(e, state)\n        return {'etag': None}\n\n\ndef try_to_serve_304(dispatch_result, request, response, etag):\n    \"\"\"Try to serve a 304 for static resources.\n    \"\"\"\n    if not etag:\n        # This is a request for a dynamic resource.\n        return\n\n    # Compare the etag in the request's querystring to the one we have.\n    qs_etag = request.qs.get('etag')\n    if qs_etag and qs_etag != etag:\n        # Don't serve one version of a file as if it were another.\n        raise response.error(410)\n\n    # Compare the etag in the request's headers to the one we have.\n    headers_etag = request.headers.get(b'If-None-Match', b'').decode('ascii', 'replace')\n    if headers_etag and headers_etag == etag:\n        # Success! We can serve a 304.\n        raise response.success(304)\n\n\ndef add_caching_to_response(state, website, response, request=None, etag=None):\n    \"\"\"Set caching headers.\n    \"\"\"\n    if response.code not in (200, 304):\n        return\n    if b'Cache-Control' in response.headers:\n        # The caching policy has already been defined somewhere else\n        return\n    if etag:\n        try:\n            assert not response.headers.cookie\n        except Exception as e:\n            website.tell_sentry(e, state)\n            response.headers.cookie.clear()\n        # https://developers.google.com/speed/docs/best-practices/caching\n        response.headers[b'Etag'] = etag.encode('ascii')\n        if request.qs.get('etag'):\n            # We can cache \"indefinitely\" when the querystring contains the etag.\n            response.headers[b'Cache-Control'] = b'public, max-age=31536000, immutable'\n        else:\n            # Otherwise we cache for 1 hour\n            response.headers[b'Cache-Control'] = b'public, max-age=3600'\n    else:\n        # This is a dynamic resource, disable caching by default\n        response.headers[b'Cache-Control'] = b'no-cache'\n", "path": "liberapay/utils/http_caching.py"}]}