@quintype/framework 7.19.21 → 7.19.22-custom-maxage.0
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/server/handlers/cdn-caching.js +2 -1
- package/server/handlers/custom-route-handler.js +7 -1
- package/server/handlers/isomorphic-handler.js +7 -0
- package/server/handlers/story-redirect.js +2 -1
- package/server/routes.js +30 -5
- package/test/integration/sketches-proxy-test.js +73 -0
package/package.json
CHANGED
|
@@ -6,6 +6,7 @@ exports.addCacheHeadersToResult = function addCacheHeadersToResult({
|
|
|
6
6
|
cacheKeys,
|
|
7
7
|
cdnProvider = "cloudflare",
|
|
8
8
|
config,
|
|
9
|
+
maxAge = "15",
|
|
9
10
|
sMaxAge = "900",
|
|
10
11
|
networkOnly = false,
|
|
11
12
|
}) {
|
|
@@ -42,7 +43,7 @@ exports.addCacheHeadersToResult = function addCacheHeadersToResult({
|
|
|
42
43
|
} else {
|
|
43
44
|
res.setHeader(
|
|
44
45
|
"Cache-Control",
|
|
45
|
-
`public,max-age
|
|
46
|
+
`public,max-age=${maxAge},s-maxage=${sMaxAge},stale-while-revalidate=1000,stale-if-error=14400`
|
|
46
47
|
);
|
|
47
48
|
cdnProviderVal === "akamai" &&
|
|
48
49
|
res.setHeader("Edge-Control", `public,maxage=${sMaxAge},stale-while-revalidate=1000,stale-if-error=14400`);
|
|
@@ -19,6 +19,8 @@ function renderStaticPageContent(store, content) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
function writeStaticPageResponse(res, url, page, result, { config, renderLayout, seo }) {
|
|
22
|
+
const hideHeader = !page.metadata.header;
|
|
23
|
+
const hideFooter = !page.metadata.footer;
|
|
22
24
|
const qt = {
|
|
23
25
|
pageType: page.type,
|
|
24
26
|
// remove content from data to avoid the script tag inside json breaking the page
|
|
@@ -37,6 +39,8 @@ function writeStaticPageResponse(res, url, page, result, { config, renderLayout,
|
|
|
37
39
|
res.status(page["status-code"] || 200);
|
|
38
40
|
|
|
39
41
|
return renderLayout(res, {
|
|
42
|
+
hideHeader,
|
|
43
|
+
hideFooter,
|
|
40
44
|
title: page.title,
|
|
41
45
|
metadata: page.metadata,
|
|
42
46
|
content: renderStaticPageContent(store, page.content),
|
|
@@ -50,7 +54,7 @@ exports.customRouteHandler = function customRouteHandler(
|
|
|
50
54
|
req,
|
|
51
55
|
res,
|
|
52
56
|
next,
|
|
53
|
-
{ config, client, loadData, loadErrorData, renderLayout, logError, seo, domainSlug, cdnProvider = null, sMaxAge }
|
|
57
|
+
{ config, client, loadData, loadErrorData, renderLayout, logError, seo, domainSlug, cdnProvider = null, sMaxAge, maxAge }
|
|
54
58
|
) {
|
|
55
59
|
const url = urlLib.parse(req.url, true);
|
|
56
60
|
const path = req.params[0].endsWith("/") ? req.params[0].slice(0, -1) : req.params[0];
|
|
@@ -70,6 +74,7 @@ exports.customRouteHandler = function customRouteHandler(
|
|
|
70
74
|
cdnProvider: cdnProvider,
|
|
71
75
|
config: config,
|
|
72
76
|
sMaxAge,
|
|
77
|
+
maxAge,
|
|
73
78
|
});
|
|
74
79
|
|
|
75
80
|
let destination = page["destination-path"] || "/";
|
|
@@ -89,6 +94,7 @@ exports.customRouteHandler = function customRouteHandler(
|
|
|
89
94
|
cdnProvider: cdnProvider,
|
|
90
95
|
config: config,
|
|
91
96
|
sMaxAge,
|
|
97
|
+
maxAge,
|
|
92
98
|
});
|
|
93
99
|
addStaticPageMimeType({ res, page });
|
|
94
100
|
|
|
@@ -230,6 +230,7 @@ exports.handleIsomorphicDataLoad = function handleIsomorphicDataLoad(
|
|
|
230
230
|
redirectToLowercaseSlugs,
|
|
231
231
|
sMaxAge,
|
|
232
232
|
networkOnly,
|
|
233
|
+
maxAge,
|
|
233
234
|
}
|
|
234
235
|
) {
|
|
235
236
|
const url = urlLib.parse(req.query.path || "/", true);
|
|
@@ -302,6 +303,7 @@ exports.handleIsomorphicDataLoad = function handleIsomorphicDataLoad(
|
|
|
302
303
|
config: config,
|
|
303
304
|
sMaxAge,
|
|
304
305
|
networkOnly,
|
|
306
|
+
maxAge,
|
|
305
307
|
});
|
|
306
308
|
const seoInstance = getSeoInstance(seo, config, result.pageType);
|
|
307
309
|
res.json(
|
|
@@ -423,6 +425,7 @@ exports.handleIsomorphicRoute = function handleIsomorphicRoute(
|
|
|
423
425
|
shouldEncodeAmpUri,
|
|
424
426
|
publisherConfig,
|
|
425
427
|
sMaxAge,
|
|
428
|
+
maxAge,
|
|
426
429
|
}
|
|
427
430
|
) {
|
|
428
431
|
const url = urlLib.parse(req.url, true);
|
|
@@ -437,6 +440,7 @@ exports.handleIsomorphicRoute = function handleIsomorphicRoute(
|
|
|
437
440
|
cdnProvider: cdnProvider,
|
|
438
441
|
config: config,
|
|
439
442
|
sMaxAge: result.pageType === "tag-page" ? 600 : sMaxAge,
|
|
443
|
+
maxAge: maxAge,
|
|
440
444
|
});
|
|
441
445
|
return res.redirect(301, result.data.location);
|
|
442
446
|
}
|
|
@@ -463,6 +467,7 @@ exports.handleIsomorphicRoute = function handleIsomorphicRoute(
|
|
|
463
467
|
cdnProvider: cdnProvider,
|
|
464
468
|
config: config,
|
|
465
469
|
sMaxAge: result.pageType === "tag-page" ? 600 : sMaxAge,
|
|
470
|
+
maxAge: maxAge,
|
|
466
471
|
});
|
|
467
472
|
|
|
468
473
|
if (preloadJs) {
|
|
@@ -537,6 +542,7 @@ exports.handleStaticRoute = function handleStaticRoute(
|
|
|
537
542
|
oneSignalServiceWorkers,
|
|
538
543
|
publisherConfig,
|
|
539
544
|
sMaxAge,
|
|
545
|
+
maxAge,
|
|
540
546
|
}
|
|
541
547
|
) {
|
|
542
548
|
const url = urlLib.parse(path);
|
|
@@ -577,6 +583,7 @@ exports.handleStaticRoute = function handleStaticRoute(
|
|
|
577
583
|
cdnProvider: cdnProvider,
|
|
578
584
|
config: config,
|
|
579
585
|
sMaxAge,
|
|
586
|
+
maxAge,
|
|
580
587
|
});
|
|
581
588
|
|
|
582
589
|
const oneSignalScript = oneSignalServiceWorkers ? getOneSignalScript({ config, publisherConfig }) : null;
|
|
@@ -6,7 +6,7 @@ exports.redirectStory = function redirectStory(
|
|
|
6
6
|
req,
|
|
7
7
|
res,
|
|
8
8
|
next,
|
|
9
|
-
{ logError, config, client, cdnProvider = null, sMaxAge }
|
|
9
|
+
{ logError, config, client, cdnProvider = null, sMaxAge, maxAge }
|
|
10
10
|
) {
|
|
11
11
|
const storySlug = req.params.storySlug.toLowerCase() || "";
|
|
12
12
|
return Story.getStoryBySlug(client, storySlug)
|
|
@@ -18,6 +18,7 @@ exports.redirectStory = function redirectStory(
|
|
|
18
18
|
cdnProvider: cdnProvider,
|
|
19
19
|
config: config,
|
|
20
20
|
sMaxAge,
|
|
21
|
+
maxAge,
|
|
21
22
|
});
|
|
22
23
|
return res.redirect(301, `/${story.slug}`);
|
|
23
24
|
} else {
|
package/server/routes.js
CHANGED
|
@@ -39,6 +39,7 @@ const prerender = require("@quintype/prerender-node");
|
|
|
39
39
|
* @param {Array<string>} opts.extraRoutes Additionally forward some routes upstream. This takes an array of express compatible routes, such as ["/foo/*"]
|
|
40
40
|
* @param {boolean} opts.forwardAmp Forward amp story routes upstream (default false)
|
|
41
41
|
* @param {number} opts.sMaxAge Support overriding of proxied response cache header `s-maxage` from Sketches. For Breaking News and if the cacheability is Private, it is not overwritten instead the cache control will be the same as how it's set in sketches. We can set `upstreamRoutesSmaxage: 900` under `publisher` in publisher.yml config file that comes from BlackKnight or pass sMaxAge as a param.
|
|
42
|
+
* @param {number} opts.maxAge Support overriding of proxied response cache header `maxage` from Sketches. For Breaking News and if the cacheability is Private, it is not overwritten instead the cache control will be the same as how it's set in sketches. We can set `upstreamRoutesMaxage: 15` under `publisher` in publisher.yml config file that comes from BlackKnight or pass maxAge as a param.
|
|
42
43
|
* @param {boolean} opts.forwardFavicon Forward favicon requests to the CMS (default false)
|
|
43
44
|
* @param {boolean} opts.isSitemapUrlEnabled To enable /news_sitemap/today and /news_sitemap/yesterday sitemap news url (default /news_sitemap.xml)
|
|
44
45
|
*/
|
|
@@ -49,7 +50,7 @@ exports.upstreamQuintypeRoutes = function upstreamQuintypeRoutes(
|
|
|
49
50
|
forwardFavicon = false,
|
|
50
51
|
extraRoutes = [],
|
|
51
52
|
sMaxAge,
|
|
52
|
-
|
|
53
|
+
maxAge,
|
|
53
54
|
config = require("./publisher-config"),
|
|
54
55
|
getClient = require("./api-client").getClient,
|
|
55
56
|
isSitemapUrlEnabled = false,
|
|
@@ -67,8 +68,22 @@ exports.upstreamQuintypeRoutes = function upstreamQuintypeRoutes(
|
|
|
67
68
|
});
|
|
68
69
|
|
|
69
70
|
const _sMaxAge = get(config, ["publisher", "upstreamRoutesSmaxage"], sMaxAge);
|
|
71
|
+
const _maxAge = get(config, ["publisher", "upstreamRoutesMaxage"], maxAge);
|
|
70
72
|
|
|
71
|
-
parseInt(_sMaxAge) > 0
|
|
73
|
+
parseInt(_sMaxAge) > 0 &&
|
|
74
|
+
apiProxy.on("proxyRes", function (proxyRes, req) {
|
|
75
|
+
const pathName = get(req, ["originalUrl"], "").split("?")[0];
|
|
76
|
+
const checkForExcludeRoutes = excludeRoutes.some((path) => {
|
|
77
|
+
const matchFn = match(path, { decode: decodeURIComponent });
|
|
78
|
+
return matchFn(pathName);
|
|
79
|
+
});
|
|
80
|
+
const getCacheControl = get(proxyRes, ["headers", "cache-control"], "");
|
|
81
|
+
if (!checkForExcludeRoutes && getCacheControl.includes("public")) {
|
|
82
|
+
proxyRes.headers["cache-control"] = getCacheControl
|
|
83
|
+
.replace(/s-maxage=\d*/g, `s-maxage=${_sMaxAge}`);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
parseInt(_maxAge) > 0 &&
|
|
72
87
|
apiProxy.on("proxyRes", function (proxyRes, req) {
|
|
73
88
|
const pathName = get(req, ["originalUrl"], "").split("?")[0];
|
|
74
89
|
const checkForExcludeRoutes = excludeRoutes.some((path) => {
|
|
@@ -77,7 +92,8 @@ exports.upstreamQuintypeRoutes = function upstreamQuintypeRoutes(
|
|
|
77
92
|
});
|
|
78
93
|
const getCacheControl = get(proxyRes, ["headers", "cache-control"], "");
|
|
79
94
|
if (!checkForExcludeRoutes && getCacheControl.includes("public")) {
|
|
80
|
-
proxyRes.headers["cache-control"] = getCacheControl
|
|
95
|
+
proxyRes.headers["cache-control"] = getCacheControl
|
|
96
|
+
.replace(/max-age=\d*/g, `max-age=${_maxAge}`);
|
|
81
97
|
}
|
|
82
98
|
});
|
|
83
99
|
|
|
@@ -90,7 +106,7 @@ exports.upstreamQuintypeRoutes = function upstreamQuintypeRoutes(
|
|
|
90
106
|
.catch(() => res.status(503).send({ error: { message: "Config not loaded" } }));
|
|
91
107
|
});
|
|
92
108
|
|
|
93
|
-
// Mention the routes which don't want to override the s-maxage value
|
|
109
|
+
// Mention the routes which don't want to override the s-maxage value and max-age value
|
|
94
110
|
const excludeRoutes = [
|
|
95
111
|
"/qlitics.js",
|
|
96
112
|
"/api/v1/breaking-news",
|
|
@@ -288,6 +304,7 @@ function getWithConfig(app, route, handler, opts = {}) {
|
|
|
288
304
|
* @param {boolean|function} redirectToLowercaseSlugs If set or evaluates to true, then for every story-page request having capital latin letters in the slug, it responds with a 301 redirect to the lowercase slug URL. (default: true)
|
|
289
305
|
* @param {boolean|function} shouldEncodeAmpUri If set to true, then for every story-page request the slug will be encoded, in case of a vernacular slug this should be set to false. Receives path as param (default: true)
|
|
290
306
|
* @param {number} sMaxAge Overrides the s-maxage value, the default value is set to 900 seconds. We can set `isomorphicRoutesSmaxage: 900` under `publisher` in publisher.yml config file that comes from BlackKnight or pass sMaxAge as a param.
|
|
307
|
+
* @param {number} maxAge Overrides the max-age value, the default value is set to 15 seconds. We can set `isomorphicRoutesMaxage: 15` under `publisher` in publisher.yml config file that comes from BlackKnight or pass maxAge as a param.
|
|
291
308
|
* @param {(string|function)} fcmServerKey FCM serverKey is used for registering FCM Topic.
|
|
292
309
|
* @param {string} appLoadingPlaceholder This string gets injected into the app container when the page is loaded via service worker. Can be used to show skeleton layouts, animations or other progress indicators before it is replaced by the page content.
|
|
293
310
|
*/
|
|
@@ -331,6 +348,7 @@ exports.isomorphicRoutes = function isomorphicRoutes(
|
|
|
331
348
|
redirectToLowercaseSlugs = false,
|
|
332
349
|
shouldEncodeAmpUri,
|
|
333
350
|
sMaxAge = 900,
|
|
351
|
+
maxAge = 15,
|
|
334
352
|
appLoadingPlaceholder = "",
|
|
335
353
|
fcmServerKey = "",
|
|
336
354
|
webengageConfig = {},
|
|
@@ -340,6 +358,8 @@ exports.isomorphicRoutes = function isomorphicRoutes(
|
|
|
340
358
|
|
|
341
359
|
const _sMaxAge = parseInt(get(publisherConfig, ["publisher", "isomorphicRoutesSmaxage"], sMaxAge));
|
|
342
360
|
|
|
361
|
+
const _maxAge = parseInt(get(publisherConfig, ["publisher", "isomorphicRoutesMaxage"], maxAge));
|
|
362
|
+
|
|
343
363
|
pickComponent = makePickComponentSync(pickComponent);
|
|
344
364
|
loadData = wrapLoadDataWithMultiDomain(publisherConfig, loadData, 2);
|
|
345
365
|
loadErrorData = wrapLoadDataWithMultiDomain(publisherConfig, loadErrorData, 1);
|
|
@@ -422,6 +442,7 @@ exports.isomorphicRoutes = function isomorphicRoutes(
|
|
|
422
442
|
cdnProvider,
|
|
423
443
|
redirectToLowercaseSlugs,
|
|
424
444
|
sMaxAge: _sMaxAge,
|
|
445
|
+
maxAge: _maxAge,
|
|
425
446
|
networkOnly: true,
|
|
426
447
|
})
|
|
427
448
|
);
|
|
@@ -456,6 +477,7 @@ exports.isomorphicRoutes = function isomorphicRoutes(
|
|
|
456
477
|
cdnProvider,
|
|
457
478
|
redirectToLowercaseSlugs,
|
|
458
479
|
sMaxAge: _sMaxAge,
|
|
480
|
+
maxAge: _maxAge,
|
|
459
481
|
})
|
|
460
482
|
);
|
|
461
483
|
}
|
|
@@ -489,6 +511,7 @@ exports.isomorphicRoutes = function isomorphicRoutes(
|
|
|
489
511
|
oneSignalServiceWorkers,
|
|
490
512
|
publisherConfig,
|
|
491
513
|
sMaxAge: _sMaxAge,
|
|
514
|
+
maxAge: _maxAge,
|
|
492
515
|
},
|
|
493
516
|
route
|
|
494
517
|
)
|
|
@@ -517,11 +540,12 @@ exports.isomorphicRoutes = function isomorphicRoutes(
|
|
|
517
540
|
oneSignalServiceWorkers,
|
|
518
541
|
publisherConfig,
|
|
519
542
|
sMaxAge: _sMaxAge,
|
|
543
|
+
maxAge: _maxAge,
|
|
520
544
|
})
|
|
521
545
|
);
|
|
522
546
|
|
|
523
547
|
if (redirectRootLevelStories) {
|
|
524
|
-
app.get("/:storySlug", withConfig(redirectStory, { logError, cdnProvider, sMaxAge: _sMaxAge }));
|
|
548
|
+
app.get("/:storySlug", withConfig(redirectStory, { logError, cdnProvider, sMaxAge: _sMaxAge, maxAge: _maxAge }));
|
|
525
549
|
}
|
|
526
550
|
|
|
527
551
|
if (handleCustomRoute) {
|
|
@@ -534,6 +558,7 @@ exports.isomorphicRoutes = function isomorphicRoutes(
|
|
|
534
558
|
seo,
|
|
535
559
|
cdnProvider,
|
|
536
560
|
sMaxAge: _sMaxAge,
|
|
561
|
+
maxAge: _maxAge,
|
|
537
562
|
})
|
|
538
563
|
);
|
|
539
564
|
}
|
|
@@ -189,6 +189,79 @@ describe("Sketches Proxy", function () {
|
|
|
189
189
|
});
|
|
190
190
|
});
|
|
191
191
|
|
|
192
|
+
describe("Override the max-age cache header", function () {
|
|
193
|
+
function getClientStub(hostname) {
|
|
194
|
+
return {
|
|
195
|
+
getHostname: () => "demo.quintype.io",
|
|
196
|
+
getConfig: () =>
|
|
197
|
+
Promise.resolve({
|
|
198
|
+
foo: "bar",
|
|
199
|
+
"sketches-host": "https://www.foo.com",
|
|
200
|
+
}),
|
|
201
|
+
baseUrl: "https://www.foo.com",
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function buildApp(maxAge, { app = express() } = {}) {
|
|
205
|
+
upstreamQuintypeRoutes(app, {
|
|
206
|
+
config: {
|
|
207
|
+
sketches_host: `https://demo.quintype.io`,
|
|
208
|
+
},
|
|
209
|
+
getClient: getClientStub,
|
|
210
|
+
extraRoutes: ["/custom-route"],
|
|
211
|
+
forwardAmp: true,
|
|
212
|
+
forwardFavicon: true,
|
|
213
|
+
publisherConfig: {},
|
|
214
|
+
maxAge: maxAge,
|
|
215
|
+
});
|
|
216
|
+
return app;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
it("Override the max-age cache header when maxAge value is present", function (done) {
|
|
220
|
+
const maxAge = 3600;
|
|
221
|
+
supertest(buildApp(maxAge))
|
|
222
|
+
.get("/api/v1/config")
|
|
223
|
+
.expect(200)
|
|
224
|
+
.then((res) => {
|
|
225
|
+
const cacheControl = res.headers["cache-control"];
|
|
226
|
+
assert.equal(cacheControl, "public,max-age=3600,s-maxage=240,stale-while-revalidate=300,stale-if-error=7200");
|
|
227
|
+
})
|
|
228
|
+
.then(done);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("Does not override the max-age cache header if cacheability is Private", function (done) {
|
|
232
|
+
const maxAge = 15;
|
|
233
|
+
supertest(buildApp(maxAge))
|
|
234
|
+
.get("/api/auth/v1/users/me")
|
|
235
|
+
.then((res) => {
|
|
236
|
+
const cacheControl = res.headers["cache-control"];
|
|
237
|
+
assert.equal(cacheControl, "private,no-cache,no-store");
|
|
238
|
+
})
|
|
239
|
+
.then(done);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("Does not override the max-age cache header for Breaking News", function (done) {
|
|
243
|
+
const maxAge = 3600;
|
|
244
|
+
supertest(buildApp(maxAge))
|
|
245
|
+
.get("/api/v1/breaking-news")
|
|
246
|
+
.then((res) => {
|
|
247
|
+
const cacheControl = res.headers["cache-control"];
|
|
248
|
+
assert.equal(cacheControl, "public,max-age=15,s-maxage=240,stale-while-revalidate=300,stale-if-error=7200");
|
|
249
|
+
})
|
|
250
|
+
.then(done);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("if maxAge value is not present, do not override cache headers", function (done) {
|
|
254
|
+
supertest(buildApp())
|
|
255
|
+
.get("/api/v1/config")
|
|
256
|
+
.expect(200)
|
|
257
|
+
.then((res) => {
|
|
258
|
+
const cacheControl = res.headers["cache-control"];
|
|
259
|
+
assert.equal(cacheControl, "public,max-age=15,s-maxage=240,stale-while-revalidate=300,stale-if-error=7200");
|
|
260
|
+
})
|
|
261
|
+
.then(done);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
192
265
|
describe("sitemap requests", function () {
|
|
193
266
|
function buildApp({ isSitemapUrlEnabled }) {
|
|
194
267
|
const app = express();
|