|
| 1 | +import aiohttp |
1 | 2 | import click |
| 3 | +import hashlib |
2 | 4 | import os |
3 | 5 | import time |
4 | 6 |
|
@@ -32,7 +34,7 @@ def _view(wiki_page, user, page: str) -> web.Response: |
32 | 34 | return wrap_page(page, "Page", variables, templates) |
33 | 35 |
|
34 | 36 |
|
35 | | -def view(user, page: str, if_modified_since) -> web.Response: |
| 37 | +def view(user, page: str, if_modified_since, if_none_match) -> web.Response: |
36 | 38 | if page.endswith("/"): |
37 | 39 | page += "Main Page" |
38 | 40 |
|
@@ -68,16 +70,22 @@ def view(user, page: str, if_modified_since) -> web.Response: |
68 | 70 | if can_cache and namespaced_page in metadata.LAST_TIME_RENDERED: |
69 | 71 | if ( |
70 | 72 | if_modified_since is not None |
71 | | - and metadata.LAST_TIME_RENDERED[namespaced_page] <= if_modified_since.timestamp() |
| 73 | + and metadata.LAST_TIME_RENDERED[namespaced_page][0] <= if_modified_since.timestamp() |
72 | 74 | ): |
73 | 75 | # We already rendered this page before. If the browser has it in his |
74 | 76 | # cache, he can simply reuse that if we haven't rendered since. |
75 | 77 | response = web.HTTPNotModified() |
| 78 | + elif ( |
| 79 | + not user and if_none_match is not None and metadata.LAST_TIME_RENDERED[namespaced_page][1] == if_none_match |
| 80 | + ): |
| 81 | + # We already rendered this page before. If the browser has it in his |
| 82 | + # cache, he can simply reuse that if the content is still the same. |
| 83 | + response = web.HTTPNotModified() |
76 | 84 | elif ( |
77 | 85 | not user |
78 | 86 | and cache_filename |
79 | 87 | and os.path.exists(cache_filename) |
80 | | - and os.path.getmtime(cache_filename) >= metadata.LAST_TIME_RENDERED[namespaced_page] |
| 88 | + and os.path.getmtime(cache_filename) >= metadata.LAST_TIME_RENDERED[namespaced_page][0] |
81 | 89 | ): |
82 | 90 | # We already rendered this page to disk. Serve from there. |
83 | 91 | with open(cache_filename) as fp: |
@@ -105,13 +113,28 @@ def view(user, page: str, if_modified_since) -> web.Response: |
105 | 113 | # Only update the time if we don't have one yet. This makes sure |
106 | 114 | # that LAST_TIME_RENDERED has the oldest timestamp possible. |
107 | 115 | if namespaced_page not in metadata.LAST_TIME_RENDERED: |
108 | | - metadata.LAST_TIME_RENDERED[namespaced_page] = page_time |
| 116 | + metadata.LAST_TIME_RENDERED[namespaced_page] = (page_time, None) |
109 | 117 |
|
110 | | - response = web.Response(body=body, content_type="text/html", status=status_code) |
| 118 | + # Update the ETag if we don't have one yet. We only generate ETags for anonymous users. |
| 119 | + if not user and metadata.LAST_TIME_RENDERED[namespaced_page][1] is None: |
| 120 | + etag = hashlib.sha256(body.encode("utf-8")).hexdigest() |
| 121 | + metadata.LAST_TIME_RENDERED[namespaced_page] = (metadata.LAST_TIME_RENDERED[namespaced_page][0], etag) |
| 122 | + |
| 123 | + if if_none_match is not None and etag == if_none_match: |
| 124 | + # Now we rendered the page, we find out that the etag did match after all. |
| 125 | + # Return this information to the client, instead of the payload. |
| 126 | + response = web.HTTPNotModified() |
| 127 | + |
| 128 | + if response is None: |
| 129 | + response = web.Response(body=body, content_type="text/html", status=status_code) |
111 | 130 |
|
112 | 131 | # Inform the browser under which rules it can cache this page. |
113 | 132 | if can_cache: |
114 | | - response.last_modified = metadata.LAST_TIME_RENDERED[namespaced_page] |
| 133 | + response.last_modified = metadata.LAST_TIME_RENDERED[namespaced_page][0] |
| 134 | + if not user: |
| 135 | + # ETags are weak, as we don't actually know if we are byte-for-byte the same because |
| 136 | + # of things like gzip compression. |
| 137 | + response.etag = aiohttp.ETag(metadata.LAST_TIME_RENDERED[namespaced_page][1], is_weak=True) |
115 | 138 | response.headers["Vary"] = "Accept-Encoding, Cookie" |
116 | 139 | response.headers["Cache-Control"] = "private, must-revalidate, max-age=0" |
117 | 140 | return response |
|
0 commit comments