From a93ca81dea3f5058894f5436b268433f011dc47f Mon Sep 17 00:00:00 2001 From: Julie Goldberg Date: Thu, 11 Nov 2021 18:27:49 -0800 Subject: [PATCH 1/7] added a serverExpiresIn header --- Readme.md | 4 +++- plugin.js | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 0155122..a24d34a 100644 --- a/Readme.md +++ b/Readme.md @@ -97,6 +97,7 @@ fastify.listen(3000, (err) => { { privacy: 'value', expiresIn: 300, + serverExpiresIn: 2592000, cache: {get, set}, cacheSegment: 'segment-name' } @@ -106,7 +107,8 @@ fastify.listen(3000, (err) => { for a *cache-response-directive* as defined by RFC 2616. + `expiresIn` (Default: `undefined`): a value, in seconds, for the *max-age* the resource may be cached. When this is set, and `privacy` is not set to `no-cache`, -then `', max-age='` will be appended to the `cache-control` header. +then `', max-age='` will be appended to the `cache-control` header. (300 seconds = 5 minutes) ++ `serverExpiresIn` (Default: `undefined`): a value, in seconds, for the *max-age* the resource may be cached on the server. When this is set, and `privacy` is set to `public`, then `', s-maxage='` will be appended to the `cache-control` header. (2,592,000 seconds = 30 days) + `cache` (Default: `abstract-cache.memclient`): an [abstract-cache][acache] protocol compliant cache object. Note: the plugin requires a cache instance to properly support the ETag mechanism. Therefore, if a falsy value is supplied diff --git a/plugin.js b/plugin.js index ccd8619..d3f49ef 100644 --- a/plugin.js +++ b/plugin.js @@ -8,6 +8,7 @@ const abstractCache = require('abstract-cache') const defaultOptions = { expiresIn: undefined, + serverExpiresIn: undefined, privacy: undefined, cache: undefined, cacheSegment: 'fastify-caching' @@ -69,6 +70,10 @@ function fastifyCachingPlugin (instance, options, next) { value = `${_options.privacy}, max-age=${_options.expiresIn}` } + if (_options.privacy.toLowerCase() == 'public' && _options.serverExpiresIn) { + value += `s-maxage=${_options.serverExpiresIn}` + } + instance.addHook('onRequest', (req, res, next) => { res.header('Cache-control', value) next() From f8b462ccecd0fcb0f7090ded847e7faabd4e789e Mon Sep 17 00:00:00 2001 From: Julie Goldberg Date: Mon, 15 Nov 2021 15:53:41 -0800 Subject: [PATCH 2/7] added missing comma in specifying cache-control options --- plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.js b/plugin.js index d3f49ef..8c3ee48 100644 --- a/plugin.js +++ b/plugin.js @@ -71,7 +71,7 @@ function fastifyCachingPlugin (instance, options, next) { } if (_options.privacy.toLowerCase() == 'public' && _options.serverExpiresIn) { - value += `s-maxage=${_options.serverExpiresIn}` + value += `, s-maxage=${_options.serverExpiresIn}` } instance.addHook('onRequest', (req, res, next) => { From b2b797f5f23157062965316f9a06ebc807dad3dd Mon Sep 17 00:00:00 2001 From: Julie Goldberg Date: Tue, 16 Nov 2021 17:31:13 -0800 Subject: [PATCH 3/7] use triple =, not double =, to follow standards --- plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.js b/plugin.js index 8c3ee48..1645d67 100644 --- a/plugin.js +++ b/plugin.js @@ -70,7 +70,7 @@ function fastifyCachingPlugin (instance, options, next) { value = `${_options.privacy}, max-age=${_options.expiresIn}` } - if (_options.privacy.toLowerCase() == 'public' && _options.serverExpiresIn) { + if (_options.privacy.toLowerCase() === 'public' && _options.serverExpiresIn) { value += `, s-maxage=${_options.serverExpiresIn}` } From 2f4883ec6c0578d38e8a3c5f03c8bcdd6368ffcd Mon Sep 17 00:00:00 2001 From: Julie Goldberg Date: Tue, 16 Nov 2021 17:31:31 -0800 Subject: [PATCH 4/7] added tests for s-maxage being added to cache control --- test/headers.test.js | 74 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/test/headers.test.js b/test/headers.test.js index d8fa0b3..80b3d0b 100644 --- a/test/headers.test.js +++ b/test/headers.test.js @@ -108,6 +108,80 @@ test('sets private with max-age header', (t) => { }) }) +test('sets public with max-age and s-maxage header', (t) => { + t.plan(2) + const instance = fastify() + const opts = { + privacy: plugin.privacy.PUBLIC, + expiresIn: 300, + serverExpiresIn: 12345 + } + instance.register(plugin, opts) + instance.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + instance.listen(0, (err) => { + if (err) t.threw(err) + instance.server.unref() + const portNum = instance.server.address().port + const address = `http://127.0.0.1:${portNum}` + + http.get(address, (res) => { + t.ok(res.headers['cache-control']) + t.equal(res.headers['cache-control'], 'public, max-age=300, s-maxage=12345') + }).on('error', (err) => t.threw(err)) + }) +}) + +test('only sets max-age and ignores s-maxage with private header', (t) => { + t.plan(2) + const instance = fastify() + const opts = { + privacy: plugin.privacy.PRIVATE, + expiresIn: 300, + serverExpiresIn: 12345 + } + instance.register(plugin, opts) + instance.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + instance.listen(0, (err) => { + if (err) t.threw(err) + instance.server.unref() + const portNum = instance.server.address().port + const address = `http://127.0.0.1:${portNum}` + + http.get(address, (res) => { + t.ok(res.headers['cache-control']) + t.equal(res.headers['cache-control'], 'private, max-age=300') + }).on('error', (err) => t.threw(err)) + }) +}) + +test('s-maxage is optional with public header', (t) => { + t.plan(2) + const instance = fastify() + const opts = { + privacy: plugin.privacy.PUBLIC, + expiresIn: 300 + } + instance.register(plugin, opts) + instance.get('/', (req, reply) => { + reply.send({ hello: 'world' }) + }) + instance.listen(0, (err) => { + if (err) t.threw(err) + instance.server.unref() + const portNum = instance.server.address().port + const address = `http://127.0.0.1:${portNum}` + + http.get(address, (res) => { + t.ok(res.headers['cache-control']) + t.equal(res.headers['cache-control'], 'public, max-age=300') + }).on('error', (err) => t.threw(err)) + }) +}) + test('sets no-store with max-age header', (t) => { t.plan(2) const instance = fastify() From 7b734fe917e5f29a7e30e750618520b8a394f514 Mon Sep 17 00:00:00 2001 From: Julie Goldberg Date: Wed, 17 Nov 2021 10:51:34 -0800 Subject: [PATCH 5/7] improved description of serverExpiresIn and but removed it from the example of the object definition, since it's an advanced feature --- Readme.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index a24d34a..768ef2e 100644 --- a/Readme.md +++ b/Readme.md @@ -97,7 +97,6 @@ fastify.listen(3000, (err) => { { privacy: 'value', expiresIn: 300, - serverExpiresIn: 2592000, cache: {get, set}, cacheSegment: 'segment-name' } @@ -108,13 +107,13 @@ for a *cache-response-directive* as defined by RFC 2616. + `expiresIn` (Default: `undefined`): a value, in seconds, for the *max-age* the resource may be cached. When this is set, and `privacy` is not set to `no-cache`, then `', max-age='` will be appended to the `cache-control` header. (300 seconds = 5 minutes) -+ `serverExpiresIn` (Default: `undefined`): a value, in seconds, for the *max-age* the resource may be cached on the server. When this is set, and `privacy` is set to `public`, then `', s-maxage='` will be appended to the `cache-control` header. (2,592,000 seconds = 30 days) + `cache` (Default: `abstract-cache.memclient`): an [abstract-cache][acache] protocol compliant cache object. Note: the plugin requires a cache instance to properly support the ETag mechanism. Therefore, if a falsy value is supplied the default will be used. + `cacheSegment` (Default: `'fastify-caching'`): segment identifier to use when communicating with the cache. ++ `serverExpiresIn` (Default: `undefined`): a value, in seconds, for the length of time the resource is fresh and may be held in a shared cache (e.g. a CDN). Shared caches will ignore max-age when this is specified, though browsers will continue to use max-age. Should be used with expiresIn, not in place of it. When this is set, and `privacy` is set to `public`, then `', s-maxage='` will be appended to the `cache-control` header. [acache]: https://www.npmjs.com/package/abstract-cache From f31053ed8c668611f185848870dc4a15def39374 Mon Sep 17 00:00:00 2001 From: Julie Goldberg Date: Wed, 17 Nov 2021 10:56:42 -0800 Subject: [PATCH 6/7] check for undefined before converting privacy to lowercase to avoid errors --- plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.js b/plugin.js index 1645d67..2621ddc 100644 --- a/plugin.js +++ b/plugin.js @@ -70,7 +70,7 @@ function fastifyCachingPlugin (instance, options, next) { value = `${_options.privacy}, max-age=${_options.expiresIn}` } - if (_options.privacy.toLowerCase() === 'public' && _options.serverExpiresIn) { + if (_options.privacy !== undefined && _options.privacy.toLowerCase() === 'public' && _options.serverExpiresIn) { value += `, s-maxage=${_options.serverExpiresIn}` } From 8c3381660847de99737f0254ef36f9925426980c Mon Sep 17 00:00:00 2001 From: Julie Goldberg Date: Wed, 17 Nov 2021 11:18:20 -0800 Subject: [PATCH 7/7] removed superfluous info from Readme --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 768ef2e..32aa416 100644 --- a/Readme.md +++ b/Readme.md @@ -106,7 +106,7 @@ fastify.listen(3000, (err) => { for a *cache-response-directive* as defined by RFC 2616. + `expiresIn` (Default: `undefined`): a value, in seconds, for the *max-age* the resource may be cached. When this is set, and `privacy` is not set to `no-cache`, -then `', max-age='` will be appended to the `cache-control` header. (300 seconds = 5 minutes) +then `', max-age='` will be appended to the `cache-control` header. + `cache` (Default: `abstract-cache.memclient`): an [abstract-cache][acache] protocol compliant cache object. Note: the plugin requires a cache instance to properly support the ETag mechanism. Therefore, if a falsy value is supplied