openvibers/openvibe.reviews Commits
Static assets are immutable only when ?v= is the served file's hash (roadmap WS-P task 10): any ?v= used to be cached for a year, so after a deploy or a rollback an older page's URL could pin the wrong bytes; other ?v= values and plain URLs now get a 5-minute cache, like Blog, Codes and the others
Changes
@@@ -111,7 +111,13 @@ function createApp({ config, svc, viewers, platform, sync, keys, db, log = consoapp.use('/shared', require('openvibe-shared/serve').handler());app.use(express.static(PUBLIC_DIR, {index: false, redirect: false,-- setHeaders(res) { res.setHeader('Cache-Control', res.req && res.req.query && res.req.query.v ? 'public, max-age=31536000, immutable' : 'public, max-age=3600'); },++ // Immutable only when ?v= is the hash of the bytes served: an older page's URL after a deploy (or a++ // rollback) gets the current file with a short cache, never pinned for a year (WS-P task 10).++ setHeaders(res, filePath) {++ const v = res.req && res.req.query && res.req.query.v;++ const rel = path.relative(PUBLIC_DIR, filePath).split(path.sep).join('/');++ res.setHeader('Cache-Control', v && v === require('./render/layout').assetVersion(rel) ? 'public, max-age=31536000, immutable' : 'public, max-age=300');++ },}));app.use(createMachine({ svc, config }));@@@ -29,6 +29,8 @@ function asset(rel) {}return `/${rel}?v=${hashes.get(rel)}`;}++/** The ?v= this process renders for a public/ file (the static route caches only that one as immutable). */++function assetVersion(rel) { asset(rel); return hashes.get(rel); }const LINKS = [{ label: 'Entities', href: '/', key: 'home' },@@@ -102,4 +104,4 @@ document.addEventListener('DOMContentLoaded', function () {</html>`;}--module.exports = { renderPage, asset, SITE_NAME, DEFAULT_DESCRIPTION, NETWORK_URL };++module.exports = { renderPage, asset, SITE_NAME, DEFAULT_DESCRIPTION, NETWORK_URL, assetVersion };nnew file mode 100644@@@ -0,0 +1,24 @@++'use strict';++// Static assets (WS-P task 10): a ?v= URL is cached for a year only when ?v= is the hash of the bytes++// served. An older page's URL after a deploy or a rollback, or any other ?v=, gets the current file++// with a short cache, so no cache can pin the wrong bytes under that URL.++const assert = require('assert');++const { boot } = require('./helpers');++const { assetVersion } = require('../server/render/layout');++++(async () => {++ const h = await boot();++ try {++ const v = assetVersion('css/reviews.css');++ assert.match(v, /^[0-9a-f]{10}$/);++ let r = await fetch(`${h.base}/css/reviews.css?v=${v}`);++ assert.strictEqual(r.status, 200);++ assert.strictEqual(r.headers.get('cache-control'), 'public, max-age=31536000, immutable', 'the current hash is immutable');++ r = await fetch(`${h.base}/css/reviews.css?v=0123456789`);++ assert.strictEqual(r.status, 200);++ assert.strictEqual(r.headers.get('cache-control'), 'public, max-age=300', 'another ?v= is never pinned');++ r = await fetch(`${h.base}/css/reviews.css`);++ assert.strictEqual(r.headers.get('cache-control'), 'public, max-age=300');++ console.log('asset cache: all checks passed');++ } finally { await h.stop(); }++})().catch((e) => { console.error(e); process.exit(1); });
@@ -111,7 +111,13 @@ function createApp({ config, svc, viewers, platform, sync, keys, db, log = conso
@@ -111,7 +111,13 @@ function createApp({ config, svc, viewers, platform, sync, keys, db, log = conso
app.use('/shared', require('openvibe-shared/serve').handler());
app.use('/shared', require('openvibe-shared/serve').handler());
app.use(express.static(PUBLIC_DIR, {
app.use(express.static(PUBLIC_DIR, {
index: false, redirect: false,
index: false, redirect: false,
- setHeaders(res) { res.setHeader('Cache-Control', res.req && res.req.query && res.req.query.v ? 'public, max-age=31536000, immutable' : 'public, max-age=3600'); },
+ // Immutable only when ?v= is the hash of the bytes served: an older page's URL after a deploy (or a
+ // rollback) gets the current file with a short cache, never pinned for a year (WS-P task 10).
+ setHeaders(res, filePath) {
+ const v = res.req && res.req.query && res.req.query.v;
+ const rel = path.relative(PUBLIC_DIR, filePath).split(path.sep).join('/');
+ res.setHeader('Cache-Control', v && v === require('./render/layout').assetVersion(rel) ? 'public, max-age=31536000, immutable' : 'public, max-age=300');
+ },
}));
}));
app.use(createMachine({ svc, config }));
app.use(createMachine({ svc, config }));
@@ -29,6 +29,8 @@ function asset(rel) {
@@ -29,6 +29,8 @@ function asset(rel) {
}
}
return `/${rel}?v=${hashes.get(rel)}`;
return `/${rel}?v=${hashes.get(rel)}`;
}
}
+/** The ?v= this process renders for a public/ file (the static route caches only that one as immutable). */
+function assetVersion(rel) { asset(rel); return hashes.get(rel); }
const LINKS = [
const LINKS = [
{ label: 'Entities', href: '/', key: 'home' },
{ label: 'Entities', href: '/', key: 'home' },
@@ -102,4 +104,4 @@ document.addEventListener('DOMContentLoaded', function () {
@@ -102,4 +104,4 @@ document.addEventListener('DOMContentLoaded', function () {
</html>`;
</html>`;
}
}
-module.exports = { renderPage, asset, SITE_NAME, DEFAULT_DESCRIPTION, NETWORK_URL };
+module.exports = { renderPage, asset, SITE_NAME, DEFAULT_DESCRIPTION, NETWORK_URL, assetVersion };
new file mode 100644
new file mode 100644
@@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
+'use strict';
+// Static assets (WS-P task 10): a ?v= URL is cached for a year only when ?v= is the hash of the bytes
+// served. An older page's URL after a deploy or a rollback, or any other ?v=, gets the current file
+// with a short cache, so no cache can pin the wrong bytes under that URL.
+const assert = require('assert');
+const { boot } = require('./helpers');
+const { assetVersion } = require('../server/render/layout');
+
+(async () => {
+ const h = await boot();
+ try {
+ const v = assetVersion('css/reviews.css');
+ assert.match(v, /^[0-9a-f]{10}$/);
+ let r = await fetch(`${h.base}/css/reviews.css?v=${v}`);
+ assert.strictEqual(r.status, 200);
+ assert.strictEqual(r.headers.get('cache-control'), 'public, max-age=31536000, immutable', 'the current hash is immutable');
+ r = await fetch(`${h.base}/css/reviews.css?v=0123456789`);
+ assert.strictEqual(r.status, 200);
+ assert.strictEqual(r.headers.get('cache-control'), 'public, max-age=300', 'another ?v= is never pinned');
+ r = await fetch(`${h.base}/css/reviews.css`);
+ assert.strictEqual(r.headers.get('cache-control'), 'public, max-age=300');
+ console.log('asset cache: all checks passed');
+ } finally { await h.stop(); }
+})().catch((e) => { console.error(e); process.exit(1); });