Utility functions.

@class robohydra
 
Serves a static file with correct headers and such, writing the
result in the given response object.

@method serveStaticFile
@for robohydra
@param {String} filePath The full path of the file to be served.
@param {Response} res Response object to write the response to.
@param {Object} opts
@param {Object} opts.fs The 'fs' module (or a mock) to be used
when reading from the filesystem.
@param {Object} opts.mime The 'mime' module (or a mock) to be
used when figuring out the MIME type for a file.
@param {Object} opts.headers An object with the incoming
request headers (for caching and such).
@param {Array} opts.indexFiles An array with the filenames to
be used as index if the given file path is a directory (by
default, "index.html", "index.htm", "home.html" and
"home.htm").
function serveStaticFile(path, res, opts) {
        opts = opts || {};
        var fsObject   = opts.fs      || fs;
        var mimeObject = opts.mime    || mime;
        var headers    = opts.headers || {};
        var indexFiles = opts.indexFiles || ['index.html', 'index.htm',
                                             'home.html',  'home.htm'];

        fsObject.stat(path, function(err, stats) {
            if (!err) {
                if (stats.isFile()) {
                    var mtime = stats.mtime,
                        modifiedSince = headers['if-modified-since'];

                    // Check modification date, if available
                    if (!modifiedSince || new Date(modifiedSince) < mtime) {
                        fsObject.readFile(path, function(err, data) {
                            res.headers["content-type"]  = mimeObject.lookup(path);
                            res.headers["last-modified"] = mtime.toUTCString();
                            res.send(data);
                        });

                        // 304 - Not Modified
                    } else {
                        res.statusCode = 304;
                        res.end();
                    }
                    // If it's a directory, try to serve one of the index files
                } else if (stats.isDirectory()) {
                    for (var i = 0, len = indexFiles.length; i < len; i++) {
                        var indexFilePath = path + "/" + indexFiles[i];
                        try {
                            // This will throw an exception if the file is
                            // not there
                            fsObject.statSync(indexFilePath);
                            serveStaticFile(indexFilePath, res, opts);
                            return;
                        } catch (e) {
                            // Ignore if the file is not there; otherwise...
                            if (e.code !== 'ENOENT') {
                                throw e;
                            }
                        }
                    }

                    res.statusCode = 403;
                    res.send("Directory listing forbidden");
                }

                // 404 - Not Found
            } else {
                res.statusCode = 404;
                res.send("Not Found");
            }
        });
    }