From 08b1ee4b99aa91963b63c97a63dc26c189efdd84 Mon Sep 17 00:00:00 2001 From: Nathan Woltman Date: Tue, 19 Dec 2017 23:56:05 -0500 Subject: [PATCH] Support almost all of the same options as serve-static --- README.md | 46 +++++++++++++++++++++++++ index.js | 25 ++++++++++++-- package.json | 1 + test/static.test.js | 82 +++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 149 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2a2c8f33..12610ee3 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,52 @@ fastify.get('/another/path', function (req, reply) { ``` +### Options + +#### `root` (required) + +The absolute path of the directory that contains the files to serve. +The file to serve will be determined by combining `req.url` with the +provided root directory. + +#### `prefix` + +Default: `'/'` + +A URL path prefix used to create a virtual mount path for the static directory. + +#### `page404Path`, `page403Path`, `page500Path` + +The absolute path to an HTML file to send as a response for the corresponding +error status code. A generic error page is sent by default. + +#### `setHeaders` + +Default: `undefined` + +A function to set custom headers on the response. Alterations to the headers +must be done synchronously. The function is called as `fn(res, path, stat)`, +where the arguments are: + +- `res` The response object. +- `path` The path of the file that is being sent. +- `stat` The stat object of the file that is being sent. + +#### `send` Options + +The following options are also supported and will be passed directly to the +[`send`](https://www.npmjs.com/package/send) module: + +- [`acceptRanges`](https://www.npmjs.com/package/send#acceptranges) +- [`cacheControl`](https://www.npmjs.com/package/send#cachecontrol) +- [`dotfiles`](https://www.npmjs.com/package/send#dotfiles) +- [`etag`](https://www.npmjs.com/package/send#etag) +- [`extensions`](https://www.npmjs.com/package/send#extensions) +- [`immutable`](https://www.npmjs.com/package/send#immutable) +- [`index`](https://www.npmjs.com/package/send#index) +- [`lastModified`](https://www.npmjs.com/package/send#lastmodified) +- [`maxAge`](https://www.npmjs.com/package/send#maxage) + ## License Licensed under [MIT](./LICENSE) diff --git a/index.js b/index.js index 54766ea4..19e57ffd 100644 --- a/index.js +++ b/index.js @@ -20,7 +20,24 @@ function fastifyStatic (fastify, opts, next) { }) if (error !== undefined) return next(error) - const root = opts.root + const setHeaders = opts.setHeaders + + if (setHeaders !== undefined && typeof setHeaders !== 'function') { + return next(new TypeError('The `setHeaders` option must be a function')) + } + + const sendOptions = { + root: opts.root, + acceptRanges: opts.acceptRanges, + cacheControl: opts.cacheControl, + dotfiles: opts.dotfiles, + etag: opts.etag, + extensions: opts.extensions, + immutable: opts.immutable, + index: opts.index, + lastModified: opts.lastModified, + maxAge: opts.maxAge + } const page500 = opts.page500Path || DEFAULT_500_PAGE const page403 = opts.page403Path || DEFAULT_403_PAGE const page404 = opts.page404Path || DEFAULT_404_PAGE @@ -41,7 +58,11 @@ function fastifyStatic (fastify, opts, next) { const serve500 = servePathWithStatusCodeWrapper(page500, 500) function pumpSendToReply (req, reply, pathname) { - const sendStream = send(req, pathname, { root }) + const sendStream = send(req, pathname, sendOptions) + + if (setHeaders !== undefined) { + sendStream.on('headers', setHeaders) + } sendStream.on('error', function (err) { if (err.statusCode === 404) return serve404(req, reply.res) diff --git a/package.json b/package.json index 4b8e48e4..e7429e16 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "coveralls": "^3.0.0", "fastify": "^0.35.0", "pre-commit": "^1.2.2", + "proxyquire": "^1.8.0", "request": "^2.81.0", "snazzy": "^7.0.0", "standard": "^10.0.3", diff --git a/test/static.test.js b/test/static.test.js index 40f4b01f..43cf1182 100644 --- a/test/static.test.js +++ b/test/static.test.js @@ -264,7 +264,7 @@ t.test('register /static/', t => { }) }) -t.test('send', t => { +t.test('sendFile', t => { t.plan(2) const pluginOptions = { @@ -283,7 +283,7 @@ t.test('send', t => { fastify.server.unref() - t.test('reply.send()', t => { + t.test('reply.sendFile()', t => { t.plan(3 + GENERIC_RESPONSE_CHECK_COUNT) request.get({ method: 'GET', @@ -306,8 +306,75 @@ t.test('prefix default', t => { t.doesNotThrow(() => fastify.register(fastifyStatic, pluginOptions)) }) -t.test('errors', t => { +t.test('send options', t => { t.plan(11) + const pluginOptions = { + root: path.join(__dirname, '/static'), + acceptRanges: 'acceptRanges', + cacheControl: 'cacheControl', + dotfiles: 'dotfiles', + etag: 'etag', + extensions: 'extensions', + immutable: 'immutable', + index: 'index', + lastModified: 'lastModified', + maxAge: 'maxAge' + } + const fastify = require('fastify')({logger: false}) + const fastifyStatic = require('proxyquire')('../', { + send: function sendStub (req, pathName, options) { + t.strictEqual(pathName, '/index.html') + t.strictEqual(options.root, path.join(__dirname, '/static')) + t.strictEqual(options.acceptRanges, 'acceptRanges') + t.strictEqual(options.cacheControl, 'cacheControl') + t.strictEqual(options.dotfiles, 'dotfiles') + t.strictEqual(options.etag, 'etag') + t.strictEqual(options.extensions, 'extensions') + t.strictEqual(options.immutable, 'immutable') + t.strictEqual(options.index, 'index') + t.strictEqual(options.lastModified, 'lastModified') + t.strictEqual(options.maxAge, 'maxAge') + return { on: () => {}, pipe: () => {} } + } + }) + fastify.register(fastifyStatic, pluginOptions) + fastify.inject({url: '/index.html'}) +}) + +t.test('setHeaders option', t => { + t.plan(6 + GENERIC_RESPONSE_CHECK_COUNT) + + const pluginOptions = { + root: path.join(__dirname, 'static'), + setHeaders: function (res, pathName) { + t.strictEqual(pathName, path.join(__dirname, 'static/index.html')) + res.setHeader('X-Test-Header', 'test') + } + } + const fastify = require('fastify')() + fastify.register(fastifyStatic, pluginOptions) + + fastify.listen(0, err => { + t.error(err) + + fastify.server.unref() + + request.get({ + method: 'GET', + uri: 'http://localhost:' + fastify.server.address().port + '/index.html', + followRedirect: false + }, (err, response, body) => { + t.error(err) + t.strictEqual(response.statusCode, 200) + t.strictEqual(response.headers['x-test-header'], 'test') + t.strictEqual(body, indexContent) + genericResponseChecks(t, response) + }) + }) +}) + +t.test('errors', t => { + t.plan(12) t.test('no root', t => { t.plan(1) @@ -407,4 +474,13 @@ t.test('errors', t => { t.equal(err.constructor, Error) }) }) + + t.test('setHeaders is not a function', t => { + t.plan(1) + const pluginOptions = { root: __dirname, setHeaders: 'headers' } + const fastify = require('fastify')({logger: false}) + fastify.register(fastifyStatic, pluginOptions, err => { + t.equal(err.constructor, TypeError) + }) + }) })