Skip to content

Commit 865db70

Browse files
author
Julie Goldberg
authored
feat: ability to have s-maxage cache control headers (#80)
* added a serverExpiresIn header * added missing comma in specifying cache-control options * use triple =, not double =, to follow standards * added tests for s-maxage being added to cache control * improved description of serverExpiresIn and but removed it from the example of the object definition, since it's an advanced feature * check for undefined before converting privacy to lowercase to avoid errors * removed superfluous info from Readme
1 parent 07fbaf4 commit 865db70

File tree

3 files changed

+81
-1
lines changed

3 files changed

+81
-1
lines changed

Readme.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,14 @@ fastify.listen(3000, (err) => {
106106
for a *cache-response-directive* as defined by RFC 2616.
107107
+ `expiresIn` (Default: `undefined`): a value, in seconds, for the *max-age* the
108108
resource may be cached. When this is set, and `privacy` is not set to `no-cache`,
109-
then `', max-age=<value>'` will be appended to the `cache-control` header.
109+
then `', max-age=<value>'` will be appended to the `cache-control` header.
110110
+ `cache` (Default: `abstract-cache.memclient`): an [abstract-cache][acache]
111111
protocol compliant cache object. Note: the plugin requires a cache instance to
112112
properly support the ETag mechanism. Therefore, if a falsy value is supplied
113113
the default will be used.
114114
+ `cacheSegment` (Default: `'fastify-caching'`): segment identifier to use when
115115
communicating with the cache.
116+
+ `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=<value>'` will be appended to the `cache-control` header.
116117

117118
[acache]: https://www.npmjs.com/package/abstract-cache
118119

plugin.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const abstractCache = require('abstract-cache')
88

99
const defaultOptions = {
1010
expiresIn: undefined,
11+
serverExpiresIn: undefined,
1112
privacy: undefined,
1213
cache: undefined,
1314
cacheSegment: 'fastify-caching'
@@ -69,6 +70,10 @@ function fastifyCachingPlugin (instance, options, next) {
6970
value = `${_options.privacy}, max-age=${_options.expiresIn}`
7071
}
7172

73+
if (_options.privacy !== undefined && _options.privacy.toLowerCase() === 'public' && _options.serverExpiresIn) {
74+
value += `, s-maxage=${_options.serverExpiresIn}`
75+
}
76+
7277
instance.addHook('onRequest', (req, res, next) => {
7378
res.header('Cache-control', value)
7479
next()

test/headers.test.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,80 @@ test('sets private with max-age header', (t) => {
108108
})
109109
})
110110

111+
test('sets public with max-age and s-maxage header', (t) => {
112+
t.plan(2)
113+
const instance = fastify()
114+
const opts = {
115+
privacy: plugin.privacy.PUBLIC,
116+
expiresIn: 300,
117+
serverExpiresIn: 12345
118+
}
119+
instance.register(plugin, opts)
120+
instance.get('/', (req, reply) => {
121+
reply.send({ hello: 'world' })
122+
})
123+
instance.listen(0, (err) => {
124+
if (err) t.threw(err)
125+
instance.server.unref()
126+
const portNum = instance.server.address().port
127+
const address = `http://127.0.0.1:${portNum}`
128+
129+
http.get(address, (res) => {
130+
t.ok(res.headers['cache-control'])
131+
t.equal(res.headers['cache-control'], 'public, max-age=300, s-maxage=12345')
132+
}).on('error', (err) => t.threw(err))
133+
})
134+
})
135+
136+
test('only sets max-age and ignores s-maxage with private header', (t) => {
137+
t.plan(2)
138+
const instance = fastify()
139+
const opts = {
140+
privacy: plugin.privacy.PRIVATE,
141+
expiresIn: 300,
142+
serverExpiresIn: 12345
143+
}
144+
instance.register(plugin, opts)
145+
instance.get('/', (req, reply) => {
146+
reply.send({ hello: 'world' })
147+
})
148+
instance.listen(0, (err) => {
149+
if (err) t.threw(err)
150+
instance.server.unref()
151+
const portNum = instance.server.address().port
152+
const address = `http://127.0.0.1:${portNum}`
153+
154+
http.get(address, (res) => {
155+
t.ok(res.headers['cache-control'])
156+
t.equal(res.headers['cache-control'], 'private, max-age=300')
157+
}).on('error', (err) => t.threw(err))
158+
})
159+
})
160+
161+
test('s-maxage is optional with public header', (t) => {
162+
t.plan(2)
163+
const instance = fastify()
164+
const opts = {
165+
privacy: plugin.privacy.PUBLIC,
166+
expiresIn: 300
167+
}
168+
instance.register(plugin, opts)
169+
instance.get('/', (req, reply) => {
170+
reply.send({ hello: 'world' })
171+
})
172+
instance.listen(0, (err) => {
173+
if (err) t.threw(err)
174+
instance.server.unref()
175+
const portNum = instance.server.address().port
176+
const address = `http://127.0.0.1:${portNum}`
177+
178+
http.get(address, (res) => {
179+
t.ok(res.headers['cache-control'])
180+
t.equal(res.headers['cache-control'], 'public, max-age=300')
181+
}).on('error', (err) => t.threw(err))
182+
})
183+
})
184+
111185
test('sets no-store with max-age header', (t) => {
112186
t.plan(2)
113187
const instance = fastify()

0 commit comments

Comments
 (0)