@qtoggle/qui 1.20.0-alpha.2 → 1.20.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/qui/__init__.py +6 -2
- package/qui/constants.py +8 -0
- package/qui/templates/qui.html +2 -2
- package/qui/web/tornado.py +22 -1
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/qui/__init__.py
CHANGED
|
@@ -5,7 +5,7 @@ import secrets
|
|
|
5
5
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
-
from . import settings
|
|
8
|
+
from . import constants, settings
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
logger = logging.getLogger(__name__)
|
|
@@ -89,7 +89,11 @@ def configure(
|
|
|
89
89
|
if not settings.package_name:
|
|
90
90
|
settings.package_name = re.sub(r"[^a-zA-Z_0-9]", "", settings.name)
|
|
91
91
|
|
|
92
|
-
if settings.debug:
|
|
92
|
+
if settings.debug or constants.NON_RELEASE_VERSION_RE.match(version):
|
|
93
|
+
# A long-lived cache on static assets is only safe because the build hash changes whenever the content does.
|
|
94
|
+
# Deriving it from a version the release pipeline never stamped would make it one constant shared by every
|
|
95
|
+
# build from source, freezing whatever a browser happened to fetch first. Fall back to a per-process hash, so
|
|
96
|
+
# that restarting the server is enough to invalidate it.
|
|
93
97
|
settings.build_hash = secrets.token_hex()[:16]
|
|
94
98
|
|
|
95
99
|
else:
|
package/qui/constants.py
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
BASE_PREFIX_HEADER = "X-Forwarded-Path"
|
|
5
|
+
|
|
6
|
+
# Matches a version the release pipeline never stamped: an all-zero version, or the literal placeholder word. Spelled
|
|
7
|
+
# as a pattern rather than as plain strings on purpose -- the release tooling rewrites those exact literals wherever
|
|
8
|
+
# they appear in the source, which would quietly turn this into a test against the real version.
|
|
9
|
+
NON_RELEASE_VERSION_RE = re.compile(r"^(?:0(?:\.0)+|unknown[-_]version)$")
|
package/qui/templates/qui.html
CHANGED
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
background_color|urlquote }}&description={{
|
|
37
37
|
description|urlquote }}&h={{ build_hash }}">
|
|
38
38
|
{% endif %}
|
|
39
|
-
<link rel="icon" href="{{ static_url }}/img/launcher-icon-32.png">
|
|
40
|
-
<link rel="apple-touch-icon" href="{{ static_url }}/img/launcher-icon-32.png">
|
|
39
|
+
<link rel="icon" href="{{ static_url }}/img/launcher-icon-32.png?h={{ build_hash }}">
|
|
40
|
+
<link rel="apple-touch-icon" href="{{ static_url }}/img/launcher-icon-32.png?h={{ build_hash }}">
|
|
41
41
|
|
|
42
42
|
{% block _meta %}
|
|
43
43
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
package/qui/web/tornado.py
CHANGED
|
@@ -103,6 +103,27 @@ class JSModuleMapperStaticFileHandler(StaticFileHandler):
|
|
|
103
103
|
return False
|
|
104
104
|
|
|
105
105
|
|
|
106
|
+
class CachedStaticFileHandler(StaticFileHandler):
|
|
107
|
+
# A year, rather than Tornado's ten. Long enough that nothing revalidates in practice, short enough to bound the
|
|
108
|
+
# damage should a build ever be shipped under a build hash that has already been served.
|
|
109
|
+
CACHE_MAX_AGE = 365 * 24 * 3600
|
|
110
|
+
|
|
111
|
+
def get_cache_time(self, path: str, modified: Any, mime_type: str) -> int:
|
|
112
|
+
# Tornado grants its long max-age only to requests carrying a "v" argument, while QUI stamps every asset URL
|
|
113
|
+
# with "h" instead, so by default nothing here is ever cacheable. Honour "h", but only when it matches the hash
|
|
114
|
+
# this process is currently serving: a URL left over from an earlier build names content we can no longer
|
|
115
|
+
# vouch for, and must keep revalidating.
|
|
116
|
+
if self.get_argument("h", None) == settings.build_hash:
|
|
117
|
+
return self.CACHE_MAX_AGE
|
|
118
|
+
|
|
119
|
+
return super().get_cache_time(path, modified, mime_type)
|
|
120
|
+
|
|
121
|
+
def set_extra_headers(self, path: str) -> None:
|
|
122
|
+
# The URL names an exact build, so there is nothing to revalidate even when the user reloads the page.
|
|
123
|
+
if self.get_argument("h", None) == settings.build_hash:
|
|
124
|
+
self.set_header("Cache-Control", f"max-age={self.CACHE_MAX_AGE}, immutable")
|
|
125
|
+
|
|
126
|
+
|
|
106
127
|
class RedirectFrontendHandler(RequestHandler):
|
|
107
128
|
def get(self) -> None:
|
|
108
129
|
base_prefix = self.request.headers.get(constants.BASE_PREFIX_HEADER, "/")
|
|
@@ -192,7 +213,7 @@ def make_routing_table() -> list[URLSpec]:
|
|
|
192
213
|
spec_list.append(
|
|
193
214
|
URLSpec(
|
|
194
215
|
rf"^{static_url}/(.*)$",
|
|
195
|
-
|
|
216
|
+
CachedStaticFileHandler,
|
|
196
217
|
{"path": frontend_path},
|
|
197
218
|
name="static",
|
|
198
219
|
)
|