Skip to content

Commit

Permalink
feat: enable ETag header
Browse files Browse the repository at this point in the history
fix #80
  • Loading branch information
NicoPennec committed Apr 5, 2020
1 parent 2ca7fe6 commit ccf3e10
Show file tree
Hide file tree
Showing 6 changed files with 658 additions and 531 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,16 @@ Please note that after each invalidation, `routes` will be evaluated again. (See

This option is enable only for the nuxt "universal" mode.

### `etag` (optional) - object

- Default: [`render.etag`](https://nuxtjs.org/api/configuration-render#etag) value from your `nuxt.config.js`

Enable the etag cache header on sitemap (See [etag](https://nuxtjs.org/api/configuration-render#etag) docs for possible options).

To disable etag for sitemap set `etag: false`

This option is enable only for the nuxt "universal" mode.

### `exclude` (optional) - string array

- Default: `[]`
Expand Down
37 changes: 37 additions & 0 deletions lib/middleware.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const { gzipSync } = require('zlib')

const generateETag = require('etag')
const fresh = require('fresh')

const { createSitemap, createSitemapIndex } = require('./builder')
const { createRoutesCache } = require('./cache')
const logger = require('./logger')
Expand Down Expand Up @@ -66,6 +69,10 @@ function registerSitemap(options, globalCache, nuxtInstance) {
// Init sitemap
const routes = await cache.routes.get('routes')
const gzip = await createSitemap(options, routes, base, req).toGzip()
// Check cache headers
if (validHttpCache(gzip, options.etag, req, res)) {
return
}
// Send http response
res.setHeader('Content-Type', 'application/gzip')
res.end(gzip)
Expand All @@ -90,6 +97,10 @@ function registerSitemap(options, globalCache, nuxtInstance) {
// Init sitemap
const routes = await cache.routes.get('routes')
const xml = await createSitemap(options, routes, base, req).toXML()
// Check cache headers
if (validHttpCache(xml, options.etag, req, res)) {
return
}
// Send http response
res.setHeader('Content-Type', 'application/xml')
res.end(xml)
Expand Down Expand Up @@ -142,4 +153,30 @@ function registerSitemapIndex(options, globalCache, nuxtInstance, depth = 0) {
options.sitemaps.forEach((sitemapOptions) => registerSitemaps(sitemapOptions, globalCache, nuxtInstance, depth + 1))
}

/**
* Validate the freshness of HTTP cache using headers
*
* @param {Object} entity
* @param {Object} options
* @param {Request} req
* @param {Response} res
* @returns {boolean}
*/
function validHttpCache(entity, options, req, res) {
if (!options) {
return false
}
const { hash } = options
const etag = hash ? hash(entity, options) : generateETag(entity, options)
if (fresh(req.headers, { etag })) {
// Resource not modified
res.statusCode = 304
res.end()
return true
}
// Add ETag header
res.setHeader('ETag', etag)
return false
}

module.exports = { registerSitemaps, registerSitemap, registerSitemapIndex }
1 change: 1 addition & 0 deletions lib/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function setDefaultSitemapOptions(options, nuxtInstance) {
exclude: [],
routes: nuxtInstance.options.generate.routes || [],
cacheTime: 1000 * 60 * 15,
etag: nuxtInstance.options.render.etag,
filter: undefined,
gzip: false,
xmlNs: undefined,
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
"dependencies": {
"async-cache": "^1.1.0",
"consola": "^2.11.3",
"etag": "^1.8.1",
"fresh": "^0.5.2",
"fs-extra": "^8.1.0",
"is-https": "^1.0.0",
"lodash.unionby": "^4.8.0",
Expand Down
65 changes: 64 additions & 1 deletion test/module.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ config.dev = false
config.sitemap = {}

const url = (path) => `http://localhost:3000${path}`
const get = (path) => request(url(path))
const get = (path, options = null) => request(url(path), options)
const getGzip = (path) => request({ url: url(path), encoding: null })

const startServer = async (config) => {
Expand Down Expand Up @@ -172,6 +172,69 @@ describe('sitemap - advanced configuration', () => {
})

describe('custom options', () => {
test('etag enabled', async () => {
nuxt = await startServer({
...config,
sitemap: {
gzip: true,
},
})

const requestOptions = {
simple: false,
resolveWithFullResponse: true,
}

// 1st call
let response = await get('/sitemap.xml', requestOptions)
expect(response.statusCode).toEqual(200)
expect(response.headers.etag).not.toBeUndefined()
// 2nd call
response = await get('/sitemap.xml', {
headers: {
'If-None-Match': response.headers.etag,
},
...requestOptions,
})
expect(response.statusCode).toEqual(304)

// 1st call
response = await get('/sitemap.xml.gz', requestOptions)
expect(response.statusCode).toEqual(200)
expect(response.headers.etag).not.toBeUndefined()
// 2nd call
response = await get('/sitemap.xml.gz', {
headers: {
'If-None-Match': response.headers.etag,
},
...requestOptions,
})
expect(response.statusCode).toEqual(304)
})

test('etag disabled', async () => {
nuxt = await startServer({
...config,
sitemap: {
etag: false,
gzip: true,
},
})

const requestOptions = {
simple: false,
resolveWithFullResponse: true,
}

let response = await get('/sitemap.xml', requestOptions)
expect(response.statusCode).toEqual(200)
expect(response.headers.etag).toBeUndefined()

response = await get('/sitemap.xml.gz', requestOptions)
expect(response.statusCode).toEqual(200)
expect(response.headers.etag).toBeUndefined()
})

test('gzip enabled', async () => {
nuxt = await startServer({
...config,
Expand Down
Loading

0 comments on commit ccf3e10

Please sign in to comment.