fs.stat(path, function(err, stat) { // ENOENT = file/directory does not exist if (err) { return ('ENOENT' == err.code) ? next() : next(err); } else if (stat.isDirectory()) { return next({isDirectory: true}); } // mime type var type = mime.lookup(path); var opts = {}; if (ranges) { ranges = utils.parseRange(stat.size, ranges); if (ranges) { // TODO: stream options // TODO: multiple support opts.start = ranges[0].start; opts.end = ranges[0].end; res.statusCode = 206; res.setHeader('Content-Range', 'bytes ' + opts.start + '-' + opts.end + '/' + stat.size); } else { return next({error: 'http', reason: 'invalid-range'}); } } else { res.setHeader('Content-Length', stat.size); if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', 'public, max-age=' + (maxAge / 1000)); if (!res.getHeader('Last-Modified')) res.setHeader('Last-Modified', stat.mtime.toUTCString()); if (!res.getHeader('ETag')) res.setHeader('ETag', utils.etag(stat)); // conditional GET support if (utils.conditionalGET(req)) { if (!utils.modified(req, res)) { return utils.notModified(res); } } } // header fields if (!res.getHeader('content-type')) { var charset = mime.charsets.lookup(type); res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : '')); } res.setHeader('Accept-Ranges', 'bytes'); // transfer if (req.method == 'HEAD') return res.end(); // stream var stream = fs.createReadStream(path, opts); req.emit('sendFile', stream); stream.pipe(res); });
/** * Sets the headers for a resource request. * * @param {Request} req the request object * @param {Response} res the response object * @param {Number} maxAge the max time to cache the resource in seconds * @param {Date} lastModified the time at which the resource was modified * @param {String} contentType the content type of the resource * @param {Number} len the length of the resource in bytes */ function setResourceHeaders(req, res, maxAge, lastModified, contentType, len) { var opts = {sendBody: true}; var ranges = req.headers.range; setHeader(res, 'Date', new Date().toUTCString()); setHeader(res, 'Cache-Control', maxAge); setHeader(res, 'Last-Modified', lastModified.toUTCString()); setHeader(res, 'Content-Type', contentType); setHeader(res, 'Accept-Ranges', 'bytes'); setHeader(res, 'ETag', connectUtils.etag({ size: len, mtime: lastModified })); // conditional GET support if (connectUtils.conditionalGET(req)) { if (!connectUtils.modified(req, res)) { connectUtils.notModified(res); opts.sendBody = false; return opts; } } // we have a Range request if (ranges) { ranges = connectUtils.parseRange(len, ranges); // valid if (ranges) { opts.start = ranges[0].start; opts.end = ranges[0].end; // unsatisfiable range if (opts.start > len - 1) { res.setHeader('Content-Range', 'bytes */' + len); var err = new RainError('The specified range is invalid', RainError.ERROR_HTTP, 416); routerUtils.handleError(err, request, response); opts.sendBody = false; return opts; } // limit last-byte-pos to current length if (opts.end > len - 1) { opts.end = len - 1; } // Content-Range len = opts.end - opts.start + 1; res.statusCode = 206; res.setHeader('Content-Range', 'bytes ' + opts.start + '-' + opts.end + '/' + len); } } res.setHeader('Content-Length', len); // transfer if (req.method === 'HEAD') { res.end(); opts.sendBody = false; return opts; } return opts; }