UNPKG

mach

Version:
156 lines (133 loc) 4.9 kB
var fs = require('fs'); var mach = require('../index'); var Promise = require('../utils/Promise'); var getFileStats = require('../utils/getFileStats'); var generateETag = require('../utils/generateETag'); var generateIndex = require('../utils/generateIndex'); var joinPaths = require('../utils/joinPaths'); mach.extend( require('../extensions/server') ); /** * A middleware for serving files efficiently from the file system according * to the path specified in the `pathname` variable. * * Options may be any of the following: * * - root The path to the root directory to serve files from * - index An array of file names to try and serve when the * request targets a directory (e.g. ["index.html", "index.htm"]). * May simply be truthy to use ["index.html"] * - autoIndex Set this true to automatically generate an index page * listing a directory's contents when the request targets * a directory with no index file * - useLastModified Set this true to include the Last-Modified header * based on the mtime of the file. Defaults to true * - useETag Set this true to include the ETag header based on * the MD5 checksum of the file. Defaults to false * * Alternatively, options may be a file path to the root directory. * * If a matching file cannot be found, the request is forwarded to the * downstream app. Otherwise, the file is streamed through to the response. * * Examples: * * // Use the root directory name directly. * app.use(mach.file, '/public'); * * // Serve static files out of /public, and automatically * // serve an index.htm from any directory that has one. * app.use(mach.file, { * root: '/public', * index: 'index.htm', * useETag: true * }); * * // Serve static files out of /public, and automatically * // serve an index.html from any directory that has one. * // Also, automatically generate a directory listing for * // any directory without an index.html file. * app.use(mach.file, { * root: '/public', * index: true, * autoIndex: true * }); * * This function may also be used outside of the context of a middleware * stack to create a standalone app. * * var app = mach.file('/public'); * mach.serve(app); */ function file(app, options) { // Allow mach.file(path|options) if (typeof app === 'string' || typeof app === 'object') { options = app; app = null; } options = options || {}; // Allow mach.file(path) and app.use(mach.file, path) if (typeof options === 'string') options = { root: options }; var root = options.root; if (typeof root !== 'string' || !fs.existsSync(root) || !fs.statSync(root).isDirectory()) throw new Error('Invalid root directory: ' + root); var index = options.index || []; if (index) { if (typeof index === 'string') { index = [ index ]; } else if (!Array.isArray(index)) { index = [ 'index.html' ]; } } var useLastModified = ('useLastModified' in options) ? !!options.useLastModified : true; var useETag = !!options.useETag; function sendFile(conn, path, stats) { conn.file({ path: path, size: stats.size }); if (useLastModified) conn.response.headers['Last-Modified'] = stats.mtime.toUTCString(); if (useETag) { return generateETag(path).then(function (etag) { conn.response.headers.ETag = etag; }); } } return function (conn) { if (conn.method !== 'GET' && conn.method !== 'HEAD') return conn.call(app); var pathname = conn.pathname; // Reject paths that contain "..". if (pathname.indexOf('..') !== -1) return conn.text(403, 'Forbidden'); var path = joinPaths(root, pathname); return getFileStats(path).then(function (stats) { if (stats && stats.isFile()) return sendFile(conn, path, stats); if (!stats || !stats.isDirectory()) return conn.call(app); // Try to serve one of the index files. var indexPaths = index.map(function (indexPath) { return joinPaths(path, indexPath); }); return Promise.all(indexPaths.map(getFileStats)).then(function (stats) { for (var i = 0, len = stats.length; i < len; ++i) if (stats[i]) return sendFile(conn, indexPaths[i], stats[i]); if (!options.autoIndex) return conn.call(app); // Redirect /images => /images/ if (!(/\/$/).test(pathname)) return conn.redirect(pathname + '/'); // Automatically generate and serve an index file. return generateIndex(root, pathname, conn.basename).then(function (html) { conn.html(html); }); }); }); }; } module.exports = file;