mach
Version:
HTTP for JavaScript
144 lines (121 loc) • 4.82 kB
JavaScript
;
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;