diff --git a/README.md b/README.md index 81e6428..1218f6c 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,8 @@ Allows you to set custom headers (and overwrite the default ones) for certain pa } ``` +If you define the `ETag` header for a path, the handler will automatically reply with status code `304` for that path if a request comes in with a matching `If-None-Match` header. + **NOTE:** The paths can only contain globs that are matched using [minimatch](https://github.com/isaacs/minimatch). ### directoryListing (Boolean|Array) diff --git a/src/index.js b/src/index.js index d3c304b..e21e5ea 100644 --- a/src/index.js +++ b/src/index.js @@ -640,6 +640,19 @@ module.exports = async (request, response, config = {}, methods = {}) => { const stream = await handlers.createReadStream(absolutePath); const headers = await getHeaders(config.headers, current, absolutePath, stats); + // We need to check for `headers.ETag` being truthy first, otherwise it will + // match `undefined` being equal to `undefined`, which is true. + // + // Checking for `undefined` and `null` is also important, because `Range` can be `0`. + // + // eslint-disable-next-line no-eq-null + if (request.headers.range == null && headers.ETag && headers.ETag === request.headers['if-none-match']) { + response.statusCode = 304; + response.end(); + + return; + } + response.writeHead(response.statusCode || 200, headers); stream.pipe(response); }; diff --git a/test/integration.js b/test/integration.js index 10e92e4..79291a7 100644 --- a/test/integration.js +++ b/test/integration.js @@ -1031,3 +1031,41 @@ test('modify config in `createReadStream` handler', async t => { t.deepEqual(output, header.value); }); +test('automatically handle ETag headers for normal files', async t => { + const name = 'object.json'; + const related = path.join(fixturesFull, name); + const content = await fs.readJSON(related); + const value = 'd2ijdjoi29f3h3232'; + + const url = await getUrl({ + headers: [{ + source: '**', + headers: [{ + key: 'ETag', + value + }] + }] + }); + + const response = await fetch(`${url}/${name}`); + const {headers} = response; + + const type = headers.get('content-type'); + const eTag = headers.get('etag'); + + t.is(type, 'application/json; charset=utf-8'); + t.is(eTag, value); + + const text = await response.text(); + const spec = JSON.parse(text); + + t.deepEqual(spec, content); + + const cacheResponse = await fetch(`${url}/${name}`, { + headers: { + 'if-none-match': value + } + }); + + t.is(cacheResponse.status, 304); +});