Skip to content

Commit

Permalink
Add cacheControl to control caching in CDN (#252)
Browse files Browse the repository at this point in the history
* Add cacheControl option with tests and docs

* Normalise cacheControl option

* Don't coerce cacheControl to number
  • Loading branch information
brettwillis authored May 24, 2023
1 parent 6526df6 commit 8f219bc
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 11 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ You can use it as is without passing any option or you can configure it as expla
* `exposedHeaders`: Configures the **Access-Control-Expose-Headers** CORS header. Expects a comma-delimited string (ex: `'Content-Range,X-Content-Range'`) or an array (ex: `['Content-Range', 'X-Content-Range']`). If not specified, no custom headers are exposed.
* `credentials`: Configures the **Access-Control-Allow-Credentials** CORS header. Set to `true` to pass the header, otherwise it is omitted.
* `maxAge`: Configures the **Access-Control-Max-Age** CORS header. In seconds. Set to an integer to pass the header, otherwise it is omitted.
* `cacheControl`: Configures the **Cache-Control** header for CORS preflight responses. Set to an integer to pass the header as `Cache-Control: max-age=${cacheControl}`, or set to a string to pass the header as `Cache-Control: ${cacheControl}` (fully define the header value), otherwise the header is omitted.
* `preflightContinue`: Pass the CORS preflight response to the route handler (default: `false`).
* `optionsSuccessStatus`: Provides a status code to use for successful `OPTIONS` requests, since some legacy browsers (IE11, various SmartTVs) choke on `204`.
* `preflight`: if needed you can entirely disable preflight by passing `false` here (default: `true`).
Expand Down
14 changes: 14 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,21 @@ function handleCorsOptionsCallbackDelegator (optionsResolver, fastify, req, repl
})
}

/**
* @param {import('./types').FastifyCorsOptions} opts
*/
function normalizeCorsOptions (opts) {
const corsOptions = Object.assign({}, defaultOptions, opts)
if (Array.isArray(opts.origin) && opts.origin.indexOf('*') !== -1) {
corsOptions.origin = '*'
}
if (Number.isInteger(corsOptions.cacheControl)) {
// integer numbers are formatted this way
corsOptions.cacheControl = `max-age=${corsOptions.cacheControl}`
} else if (typeof corsOptions.cacheControl !== 'string') {
// strings are applied directly and any other value is ignored
corsOptions.cacheControl = null
}
return corsOptions
}

Expand Down Expand Up @@ -235,6 +245,10 @@ function addPreflightHeaders (req, reply, corsOptions) {
if (corsOptions.maxAge !== null) {
reply.header('Access-Control-Max-Age', String(corsOptions.maxAge))
}

if (corsOptions.cacheControl) {
reply.header('Cache-Control', corsOptions.cacheControl)
}
}

function resolveOriginWrapper (fastify, origin) {
Expand Down
53 changes: 47 additions & 6 deletions test/cors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ test('Should add cors headers (custom values)', t => {
credentials: true,
exposedHeaders: ['foo', 'bar'],
allowedHeaders: ['baz', 'woo'],
maxAge: 123
maxAge: 123,
cacheControl: 321
})

fastify.get('/', (req, reply) => {
Expand All @@ -65,6 +66,7 @@ test('Should add cors headers (custom values)', t => {
'access-control-allow-methods': 'GET',
'access-control-allow-headers': 'baz, woo',
'access-control-max-age': '123',
'cache-control': 'max-age=321',
'content-length': '0'
})
})
Expand Down Expand Up @@ -96,14 +98,16 @@ test('Should support dynamic config (callback)', t => {
credentials: true,
exposedHeaders: ['foo', 'bar'],
allowedHeaders: ['baz', 'woo'],
maxAge: 123
maxAge: 123,
cacheControl: 456
}, {
origin: 'sample.com',
methods: 'GET',
credentials: true,
exposedHeaders: ['zoo', 'bar'],
allowedHeaders: ['baz', 'foo'],
maxAge: 321
maxAge: 321,
cacheControl: '456'
}]

const fastify = Fastify()
Expand Down Expand Up @@ -164,6 +168,7 @@ test('Should support dynamic config (callback)', t => {
'access-control-allow-methods': 'GET',
'access-control-allow-headers': 'baz, foo',
'access-control-max-age': '321',
'cache-control': '456',
'content-length': '0'
})
})
Expand All @@ -182,22 +187,32 @@ test('Should support dynamic config (callback)', t => {
})

test('Should support dynamic config (Promise)', t => {
t.plan(16)
t.plan(23)

const configs = [{
origin: 'example.com',
methods: 'GET',
credentials: true,
exposedHeaders: ['foo', 'bar'],
allowedHeaders: ['baz', 'woo'],
maxAge: 123
maxAge: 123,
cacheControl: 456
}, {
origin: 'sample.com',
methods: 'GET',
credentials: true,
exposedHeaders: ['zoo', 'bar'],
allowedHeaders: ['baz', 'foo'],
maxAge: 321
maxAge: 321,
cacheControl: true // Invalid value should be ignored
}, {
origin: 'sample.com',
methods: 'GET',
credentials: true,
exposedHeaders: ['zoo', 'bar'],
allowedHeaders: ['baz', 'foo'],
maxAge: 321,
cacheControl: 'public, max-age=456'
}]

const fastify = Fastify()
Expand Down Expand Up @@ -238,6 +253,31 @@ test('Should support dynamic config (Promise)', t => {
})
})

fastify.inject({
method: 'OPTIONS',
url: '/',
headers: {
'access-control-request-method': 'GET',
origin: 'sample.com'
}
}, (err, res) => {
t.error(err)
delete res.headers.date
t.equal(res.statusCode, 204)
t.equal(res.payload, '')
t.match(res.headers, {
'access-control-allow-origin': 'sample.com',
vary: 'Origin',
'access-control-allow-credentials': 'true',
'access-control-expose-headers': 'zoo, bar',
'access-control-allow-methods': 'GET',
'access-control-allow-headers': 'baz, foo',
'access-control-max-age': '321',
'content-length': '0'
})
t.equal(res.headers['cache-control'], undefined, 'cache-control omitted (invalid value)')
})

fastify.inject({
method: 'OPTIONS',
url: '/',
Expand All @@ -258,6 +298,7 @@ test('Should support dynamic config (Promise)', t => {
'access-control-allow-methods': 'GET',
'access-control-allow-headers': 'baz, foo',
'access-control-max-age': '321',
'cache-control': 'public, max-age=456', // cache-control included (custom string)
'content-length': '0'
})
})
Expand Down
7 changes: 7 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ declare namespace fastifyCors {
* Set to an integer to pass the header, otherwise it is omitted.
*/
maxAge?: number;
/**
* Configures the Cache-Control header for CORS preflight responses.
* Set to an integer to pass the header as `Cache-Control: max-age=${cacheControl}`,
* or set to a string to pass the header as `Cache-Control: ${cacheControl}` (fully define
* the header value), otherwise the header is omitted.
*/
cacheControl?: number | string;
/**
* Pass the CORS preflight response to the route handler (default: false).
*/
Expand Down
33 changes: 28 additions & 5 deletions types/index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fastify from 'fastify'
import fastify, { FastifyRequest } from 'fastify'
import { expectType } from 'tsd'
import fastifyCors, {
FastifyCorsOptions,
FastifyCorsOptionsDelegate,
FastifyCorsOptionsDelegatePromise,
FastifyPluginOptionsDelegate,
Expand All @@ -18,6 +19,7 @@ app.register(fastifyCors, {
credentials: true,
exposedHeaders: 'authorization',
maxAge: 13000,
cacheControl: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
Expand All @@ -31,6 +33,7 @@ app.register(fastifyCors, {
credentials: true,
exposedHeaders: ['authorization'],
maxAge: 13000,
cacheControl: 'public, max-age=3500',
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
Expand All @@ -44,6 +47,7 @@ app.register(fastifyCors, {
credentials: true,
exposedHeaders: ['authorization'],
maxAge: 13000,
cacheControl: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
Expand All @@ -57,6 +61,7 @@ app.register(fastifyCors, {
credentials: true,
exposedHeaders: ['authorization'],
maxAge: 13000,
cacheControl: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
Expand All @@ -70,6 +75,7 @@ app.register(fastifyCors, {
credentials: true,
exposedHeaders: ['authorization'],
maxAge: 13000,
cacheControl: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
Expand All @@ -83,6 +89,7 @@ app.register(fastifyCors, {
credentials: true,
exposedHeaders: ['authorization'],
maxAge: 13000,
cacheControl: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
Expand All @@ -104,6 +111,7 @@ app.register(fastifyCors, {
credentials: true,
exposedHeaders: ['authorization'],
maxAge: 13000,
cacheControl: 13000,
optionsSuccessStatus: 200,
preflight: false,
strictPreflight: false
Expand All @@ -120,6 +128,7 @@ appHttp2.register(fastifyCors, {
credentials: true,
exposedHeaders: 'authorization',
maxAge: 13000,
cacheControl: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
Expand All @@ -133,6 +142,7 @@ appHttp2.register(fastifyCors, {
credentials: true,
exposedHeaders: ['authorization'],
maxAge: 13000,
cacheControl: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
Expand All @@ -146,6 +156,7 @@ appHttp2.register(fastifyCors, {
credentials: true,
exposedHeaders: ['authorization'],
maxAge: 13000,
cacheControl: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
Expand All @@ -159,6 +170,7 @@ appHttp2.register(fastifyCors, {
credentials: true,
exposedHeaders: ['authorization'],
maxAge: 13000,
cacheControl: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
Expand All @@ -172,6 +184,7 @@ appHttp2.register(fastifyCors, {
credentials: true,
exposedHeaders: ['authorization'],
maxAge: 13000,
cacheControl: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
Expand All @@ -185,6 +198,7 @@ appHttp2.register(fastifyCors, {
credentials: true,
exposedHeaders: ['authorization'],
maxAge: 13000,
cacheControl: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
Expand All @@ -204,6 +218,7 @@ appHttp2.register(fastifyCors, {
credentials: true,
exposedHeaders: ['authorization'],
maxAge: 13000,
cacheControl: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
Expand All @@ -218,6 +233,7 @@ appHttp2.register(fastifyCors, (): FastifyCorsOptionsDelegate => (req, cb) => {
credentials: true,
exposedHeaders: ['authorization'],
maxAge: 13000,
cacheControl: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
Expand All @@ -233,6 +249,7 @@ appHttp2.register(fastifyCors, (): FastifyCorsOptionsDelegatePromise => (req) =>
credentials: true,
exposedHeaders: ['authorization'],
maxAge: 13000,
cacheControl: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
Expand All @@ -248,6 +265,7 @@ const delegate: FastifyPluginOptionsDelegate<FastifyCorsOptionsDelegatePromise>
credentials: true,
exposedHeaders: ['authorization'],
maxAge: 13000,
cacheControl: 13000,
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
Expand Down Expand Up @@ -276,32 +294,37 @@ appHttp2.register(fastifyCors, {

appHttp2.register(fastifyCors, {
hook: 'preParsing',
delegator: () => {
return {
delegator: (req, cb) => {
if (req.url.startsWith('/some-value')) {
cb(new Error())
}
cb(null, {
origin: [/\*/, /something/],
allowedHeaders: ['authorization', 'content-type'],
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
credentials: true,
exposedHeaders: ['authorization'],
maxAge: 13000,
cacheControl: 12000,
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
strictPreflight: false
}
})
}
})

appHttp2.register(fastifyCors, {
hook: 'preParsing',
delegator: () => {
delegator: async (req: FastifyRequest): Promise<FastifyCorsOptions> => {
return {
origin: [/\*/, /something/],
allowedHeaders: ['authorization', 'content-type'],
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
credentials: true,
exposedHeaders: ['authorization'],
maxAge: 13000,
cacheControl: 'public, max-age=3500',
preflightContinue: false,
optionsSuccessStatus: 200,
preflight: false,
Expand Down

0 comments on commit 8f219bc

Please sign in to comment.