static-server
Advanced tools
Comparing version 1.0.2 to 2.0.0
#!/usr/bin/env node | ||
require('../server.js'); | ||
const DEFAULT_PORT = 9080; | ||
const DEFAULT_INDEX = 'index.html'; | ||
const DEFAULT_FOLLOW_SYMLINKS = false; | ||
const DEFAULT_DEBUG = false; | ||
var path = require("path"); | ||
var fsize = require('file-size'); | ||
var program = require('commander'); | ||
var chalk = require('chalk'); | ||
var pkg = require(path.join(__dirname, '..', 'package.json')); | ||
var StaticServer = require('../server.js'); | ||
var server; | ||
initTerminateHandlers(); | ||
program | ||
.version(pkg.name + '@' + pkg.version) | ||
.usage('[options] <root_path>') | ||
.option('-p, --port <n>', 'the port to listen to for incoming HTTP connections', DEFAULT_PORT) | ||
.option('-i, --index <filename>', 'the default index file if not specified', DEFAULT_INDEX) | ||
.option('-f, --follow-symlink', 'follow links, otherwise fail with file not found', DEFAULT_FOLLOW_SYMLINKS) | ||
.option('-d, --debug', 'enable to show error messages', DEFAULT_DEBUG) | ||
.parse(process.argv); | ||
; | ||
// overrides | ||
program.rootPath = program.args[0] || process.cwd(); | ||
program.name = pkg.name; | ||
server = new StaticServer(program); | ||
server.start(function () { | ||
console.log(chalk.blue('*'), 'Static server successfully started.'); | ||
console.log(chalk.blue('*'), 'Serving files at:', chalk.cyan('http://localhost:' + program.port)); | ||
console.log(chalk.blue('*'), 'Press', chalk.yellow.bold('Ctrl+C'), 'to shutdown.'); | ||
return server; | ||
}); | ||
server.on('request', function (req, res) { | ||
console.log(chalk.gray('<--'), chalk.blue('[' + req.method + ']'), req.path); | ||
}); | ||
server.on('symbolicLink', function (link, file) { | ||
console.log(chalk.cyan('---'), '"' + path.relative(server.rootPath, link) + '"', chalk.magenta('>'), '"' + path.relative(server.rootPath, file) + '"'); | ||
}); | ||
server.on('response', function (req, res, err, file, stat) { | ||
var relFile; | ||
var nrmFile; | ||
if (res.status >= 400) { | ||
console.log(chalk.gray('-->'), chalk.red(res.status), req.path, '(' + req.elapsedTime + ')'); | ||
} else if (file) { | ||
relFile = path.relative(server.rootPath, file); | ||
nrmFile = path.normalize(req.path.substring(1)); | ||
console.log(chalk.gray('-->'), chalk.green(res.status, StaticServer.STATUS_CODES[res.status]), req.path + (nrmFile !== relFile ? (' ' + chalk.dim('(' + relFile + ')')) : ''), fsize(stat.size).human(), '(' + req.elapsedTime + ')'); | ||
} else { | ||
console.log(chalk.gray('-->'), chalk.green.dim(res.status, StaticServer.STATUS_CODES[res.status]), req.path, '(' + req.elapsedTime + ')'); | ||
} | ||
if (err && server.debug) { | ||
console.error(err.stack || err.message || err); | ||
} | ||
}); | ||
/** | ||
Prepare the 'exit' handler for the program termination | ||
*/ | ||
function initTerminateHandlers() { | ||
var readLine; | ||
if (process.platform === "win32"){ | ||
readLine = require("readline"); | ||
readLine.createInterface ({ | ||
input: process.stdin, | ||
output: process.stdout | ||
}).on("SIGINT", function () { | ||
process.emit("SIGINT"); | ||
}); | ||
} | ||
// handle INTERRUPT (CTRL+C) and TERM/KILL signals | ||
process.on('exit', function () { | ||
if (server) { | ||
console.log(chalk.blue('*'), 'Shutting down server'); | ||
server.stop(); | ||
} | ||
console.log(); // extra blank line | ||
}); | ||
process.on('SIGINT', function () { | ||
console.log(chalk.blue.bold('!'), chalk.yellow.bold('SIGINT'), 'detected'); | ||
process.exit(); | ||
}); | ||
process.on('SIGTERM', function () { | ||
console.log(chalk.blue.bold('!'), chalk.yellow.bold('SIGTERM'), 'detected'); | ||
process.exit(0); | ||
}); | ||
} | ||
{ | ||
"name": "static-server", | ||
"description": "A simple http server to serve static resource files from a local directory.", | ||
"version": "1.0.2", | ||
"version": "2.0.0", | ||
"author": "Eduardo Bohrer <nbluisrs@gmail.com>", | ||
@@ -20,2 +20,7 @@ "keywords": [ | ||
}, | ||
"scripts": { | ||
"test": "mocha", | ||
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha", | ||
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly" | ||
}, | ||
"engines": { | ||
@@ -33,3 +38,9 @@ "node": ">= 0.10.0" | ||
"url": "http://creativecommons.org/licenses/MIT/" | ||
}, | ||
"devDependencies": { | ||
"istanbul": "^0.3.0", | ||
"mocha": "^1.21.4", | ||
"should": "^4.0.4", | ||
"supertest": "^0.15.0" | ||
} | ||
} |
[![Build Status](https://secure.travis-ci.org/nbluis/static-server.svg?branch=master)](http://travis-ci.org/nbluis/static-server) | ||
#Node static server | ||
# Node static server | ||
A simple http server to serve static resource files from a local directory. | ||
@@ -10,10 +10,62 @@ | ||
* Go to the folder you want to serve | ||
* Run the server `static-server .` | ||
* Run the server `static-server` | ||
##FAQ | ||
## Options | ||
-h, --help output usage information | ||
-V, --version output the version number | ||
-p, --port <n> the port to listen to for incoming HTTP connections | ||
-i, --index <filename> the default index file if not specified | ||
-f, --follow-symlink follow links, otherwise fail with file not found | ||
-d, --debug enable to show error messages | ||
## Using as a node module | ||
The server may be used as a dependency HTTP server. | ||
### Example | ||
```javascript | ||
var StaticServer = require('static-server'); | ||
var server = new StaticServer({ | ||
rootPath: '.', // required, the root of the server file tree | ||
name: 'my-http-server', // optional, will set "X-Powered-by" HTTP header | ||
port: 1337, // optional, defaults to a random port | ||
host: '10.0.0.100', // optional, defaults to any interface | ||
followSymlink: true, // optional, defaults to a 404 error | ||
index: 'foo.html' // optional, defaults to 'index.html' | ||
}); | ||
server.start(function () { | ||
console.log('Server listening to', server.port); | ||
}); | ||
server.on('request', function (req, res) { | ||
// req.path is the URL resource (file name) from server.rootPath | ||
// req.elapsedTime returns a string of the request's elapsed time | ||
}); | ||
server.on('symbolicLink', function (link, file) { | ||
// link is the source of the reference | ||
// file is the link reference | ||
console.log('File', link, 'is a link to', file); | ||
}); | ||
server.on('response', function (req, res, err, stat, file) { | ||
// res.status is the response status sent to the client | ||
// res.headers are the headers sent | ||
// err is any error message thrown | ||
// file the file being served (may be null) | ||
// stat the stat of the file being served (is null if file is null) | ||
// NOTE: the response has already been sent at this point | ||
}); | ||
``` | ||
## FAQ | ||
* _Can I use this project in production environments?_ **Obviously not.** | ||
* _Can this server run php, ruby, python or any other cgi script?_ **Absolutely not.** | ||
* _Is this server ready to receive thousands of requests?_ **I hope not.** | ||
* _Can this server run php, ruby, python or any other cgi script?_ **No.** | ||
* _Is this server ready to receive thousands of requests?_ **Preferably not.** | ||
## License | ||
[The MIT License (MIT)](http://creativecommons.org/licenses/MIT/) |
454
server.js
const DEFAULT_PORT = 9080; | ||
const DEFAULT_INDEX = 'index.html'; | ||
const DEFAULT_FOLLOW_SYMLINKS = false; | ||
const DEFAULT_DEBUG = false; | ||
const DEFAULT_STATUS_OK = 200; | ||
const DEFAULT_STATUS_NOT_MODIFIED = 304; | ||
const DEFAULT_STATUS_ERR = 500; | ||
const DEFAULT_STATUS_FORBIDDEN = 403; | ||
const DEFAULT_STATUS_FILE_NOT_FOUND = 404; | ||
const DEFAULT_STATUS_INVALID_METHOD = 405; | ||
const DEFAULT_STATUS_REQUEST_RANGE_NOT_SATISFIABLE = 416; | ||
const HTTP_STATUS_OK = 200; | ||
const HTTP_STATUS_PARTIAL_CONTENT = 206; | ||
const HTTP_STATUS_NOT_MODIFIED = 304; | ||
const HTTP_STATUS_ERR = 500; | ||
const HTTP_STATUS_BAD_REQUEST = 400; | ||
const HTTP_STATUS_FORBIDDEN = 403; | ||
const HTTP_STATUS_NOT_FOUND = 404; | ||
const HTTP_STATUS_INVALID_METHOD = 405; | ||
const HTTP_STATUS_REQUEST_RANGE_NOT_SATISFIABLE = 416; | ||
const VALID_HTTP_METHODS = ['GET', 'HEAD']; | ||
const RANGE_REQUEST_HEADER_TEST = /^bytes=/; | ||
const RANGE_REQUEST_HEADER_PATTERN = /\d*-\d*/g; | ||
const TIME_MS_PRECISION = 3; | ||
const MULTIPART_SEPARATOR = '--MULTIPARTSEPERATORaufielqbghgzwr'; | ||
var util = require('util'); | ||
var http = require("http"); | ||
var url = require("url"); | ||
var mime = require('mime'); | ||
var path = require("path"); | ||
var fs = require("fs"); | ||
var fsize = require('file-size'); | ||
var chalk = require('chalk'); | ||
var slice = Array.prototype.slice; | ||
var program = require('commander'); | ||
var pkg = require(path.join(__dirname, 'package.json')); | ||
var server; | ||
const NEWLINE = '\n'; | ||
program | ||
.version(pkg.name + '@' + pkg.version) | ||
.usage('[options] <root_path>') | ||
.option('-p, --port <n>', 'the port to listen to for incoming HTTP connections', DEFAULT_PORT) | ||
.option('-i, --index <filename>', 'the default index file if not specified', DEFAULT_INDEX) | ||
.option('-f, --follow-symlink', 'follow links, otherwise fail with file not found', DEFAULT_FOLLOW_SYMLINKS) | ||
.option('-d, --debug', 'enable to show error messages', DEFAULT_DEBUG) | ||
.parse(process.argv); | ||
var EventEmitter = require('events').EventEmitter; | ||
var util = require('util'); | ||
var http = require('http'); | ||
var url = require('url'); | ||
var mime = require('mime'); | ||
var path = require('path'); | ||
var fs = require('fs'); | ||
var slice = Array.prototype.slice; | ||
program.rootPath = program.args[0] || process.cwd(); | ||
initTerminateHandlers(); | ||
createServer(); | ||
/** | ||
Exposes the StaticServer class | ||
*/ | ||
module.exports = StaticServer; | ||
/** | ||
* Create the server | ||
*/ | ||
function createServer() { | ||
server = http.createServer(function(req, res) { | ||
Create a new instance of StaticServer class | ||
Options are : | ||
- name the server name, what will be sent as "X-Powered-by" | ||
- host the host interface where the server will listen to. If not specified, | ||
the server will listen on any networking interfaces | ||
- port the listening port number | ||
- rootPath the serving root path. Any file above that path will be denied | ||
- followSymlink true to follow any symbolic link, false to forbid | ||
- index the default index file to server for a directory (default 'index.html') | ||
@param options {Object} | ||
*/ | ||
function StaticServer(options) { | ||
options = options || {}; | ||
if (!options.rootPath) { | ||
throw new Error('Root path not specified'); | ||
} | ||
this.name = options.name; | ||
this.host = options.host; | ||
this.port = options.port; | ||
this.rootPath = path.resolve(options.rootPath); | ||
this.followSymlink = !!options.followSymlink; | ||
this.index = options.index || DEFAULT_INDEX; | ||
Object.defineProperty(this, '_socket', { | ||
configurable: true, | ||
enumerable: false, | ||
writable: true, | ||
value: null | ||
}); | ||
} | ||
util.inherits(StaticServer, EventEmitter); | ||
/** | ||
Expose the http.STATUS_CODES object | ||
*/ | ||
StaticServer.STATUS_CODES = http.STATUS_CODES; | ||
/** | ||
Start listening on the given host:port | ||
@param callback {Function} the function to call once the server is ready | ||
*/ | ||
StaticServer.prototype.start = function start(callback) { | ||
this._socket = http.createServer(requestHandler(this)).listen(this.port, this.host, callback); | ||
} | ||
/** | ||
Stop listening | ||
*/ | ||
StaticServer.prototype.stop = function stop() { | ||
if (this._socket) { | ||
this._socket.close(); | ||
this._socket = null; | ||
} | ||
} | ||
/** | ||
Return the server's request handler function | ||
@param server {StaticServer} server instance | ||
@return {Function} | ||
*/ | ||
function requestHandler(server) { | ||
return function handler(req, res) { | ||
var uri = req.path = url.parse(req.url).pathname; | ||
var filename = path.join(program.rootPath, uri); | ||
var filename = path.join(server.rootPath, uri); | ||
var timestamp = process.hrtime(); | ||
// add a property to get the elapsed time since the request was issued | ||
Object.defineProperty(res, 'elapsedTime', { | ||
Object.defineProperty(req, 'elapsedTime', { | ||
get: function getElapsedTime() { | ||
@@ -65,17 +127,18 @@ var elapsed = process.hrtime(timestamp); | ||
res.headers = { | ||
'X-Powered-By': pkg.name | ||
}; | ||
res.headers = {}; | ||
if (server.name) { | ||
res.headers['X-Powered-By'] = server.name; | ||
} | ||
console.log(chalk.gray('<--'), chalk.blue('[' + req.method + ']'), uri); | ||
server.emit('request', req); | ||
if (VALID_HTTP_METHODS.indexOf(req.method) === -1) { | ||
return sendError(req, res, null, DEFAULT_STATUS_INVALID_METHOD); | ||
} else if (!validPath(filename)) { | ||
return sendError(req, res, null, DEFAULT_STATUS_FORBIDDEN); | ||
return sendError(server, req, res, null, HTTP_STATUS_INVALID_METHOD); | ||
} else if (!validPath(server.rootPath, filename)) { | ||
return sendError(server, req, res, null, HTTP_STATUS_FORBIDDEN); | ||
} | ||
getFileStats(filename, path.join(filename, program.index), function (err, stat, file, index) { | ||
getFileStats(server, [filename, path.join(filename, server.index)], function (err, stat, file, index) { | ||
if (err) { | ||
sendError(req, res, null, DEFAULT_STATUS_FILE_NOT_FOUND); | ||
sendError(server, req, res, null, HTTP_STATUS_NOT_FOUND); | ||
} else if (stat.isDirectory()) { | ||
@@ -85,64 +148,20 @@ // | ||
// | ||
sendError(req, res, null, DEFAULT_STATUS_FORBIDDEN); | ||
sendError(server, req, res, null, HTTP_STATUS_FORBIDDEN); | ||
} else { | ||
sendFile(req, res, stat, file); | ||
sendFile(server, req, res, stat, file); | ||
} | ||
}); | ||
}); | ||
server.listen(program.port); | ||
console.log(chalk.blue('*'), 'Static server successfully started.'); | ||
console.log(chalk.blue('*'), 'Listening on port:', chalk.cyan(program.port)); | ||
console.log(chalk.blue('*'), 'Press', chalk.yellow.bold('Ctrl+C'), 'to shutdown.'); | ||
return server; | ||
}; | ||
} | ||
/** | ||
* Prepare the 'exit' handler for the program termination | ||
*/ | ||
function initTerminateHandlers() { | ||
var readLine; | ||
if (process.platform === "win32"){ | ||
readLine = require("readline"); | ||
readLine.createInterface ({ | ||
input: process.stdin, | ||
output: process.stdout | ||
}).on("SIGINT", function () { | ||
process.emit("SIGINT"); | ||
}); | ||
} | ||
// handle INTERRUPT (CTRL+C) and TERM/KILL signals | ||
process.on('exit', function () { | ||
if (server) { | ||
console.log(chalk.blue('*'), 'Shutting down server'); | ||
server.close(); | ||
} | ||
console.log(); // extra blank line | ||
}); | ||
process.on('SIGINT', function () { | ||
console.log(chalk.blue.bold('!'), chalk.yellow.bold('SIGINT'), 'detected'); | ||
process.exit(); | ||
}); | ||
process.on('SIGTERM', function () { | ||
console.log(chalk.blue.bold('!'), chalk.yellow.bold('SIGTERM'), 'detected'); | ||
process.exit(0); | ||
}); | ||
} | ||
/** | ||
Check that path is valid so we don't access invalid resources | ||
@param rootPath {String} the server root path | ||
@param file {String} the path to validate | ||
*/ | ||
function validPath(file) { | ||
var resolvedPath = path.resolve(program.rootPath, file); | ||
var rootPath = path.resolve(program.rootPath); | ||
function validPath(rootPath, file) { | ||
var resolvedPath = path.resolve(rootPath, file); | ||
@@ -158,5 +177,3 @@ // only if we are still in the rootPath of the static site | ||
getFile('file1', callback); | ||
getFile('file1', 'file2', ..., callback); | ||
getFile(['file1', 'file2'], callback); | ||
getFile(server, ['file1', 'file2'], callback); | ||
@@ -167,6 +184,7 @@ The callback function receives four arguments; an error if any, a stats object, | ||
@param files {Array|String} a file, or list of files | ||
@param server {StaticServer} the StaticServer instance | ||
@param files {Array} list of files | ||
@param callback {Function} a callback function | ||
*/ | ||
function getFileStats(files, callback) { | ||
function getFileStats(server, files, callback) { | ||
var dirFound; | ||
@@ -176,9 +194,2 @@ var dirStat; | ||
if (arguments.length > 2) { | ||
files = slice.call(arguments, 0, arguments.length - 1); | ||
callback = arguments[arguments.length - 1]; | ||
} else if (!Array.isArray(file)) { | ||
files = [files]; | ||
} | ||
function checkNext(err, index) { | ||
@@ -200,9 +211,9 @@ if (files.length) { | ||
} else if (stat.isSymbolicLink()) { | ||
if (program.followSymlink) { | ||
fs.readlink(file, function (err, link) { | ||
if (server.followSymlink) { | ||
fs.readlink(file, function (err, fileRef) { | ||
if (err) { | ||
checkNext(err, index); | ||
} else { | ||
console.log(chalk.cyan('---'), '"' + path.relative(program.rootPath, file) + '"', chalk.magenta('>'), '"' + path.relative(program.rootPath, link) + '"'); | ||
next(link, index); | ||
server.emit('symbolicLInk', fileRef, link); | ||
next(fileRef, index); | ||
} | ||
@@ -237,3 +248,3 @@ }); | ||
*/ | ||
function validateClientCache(req, res, stat) { | ||
function validateClientCache(server, req, res, stat) { | ||
var mtime = stat.mtime.getTime(); | ||
@@ -262,3 +273,3 @@ var clientETag = req.headers['if-none-match']; | ||
res.status = DEFAULT_STATUS_NOT_MODIFIED; | ||
res.status = HTTP_STATUS_NOT_MODIFIED; | ||
@@ -268,3 +279,3 @@ res.writeHead(res.status, res.headers); | ||
console.log(chalk.gray('-->'), chalk.green.dim(res.status, http.STATUS_CODES[res.status]), req.path, '(' + res.elapsedTime + ')'); | ||
server.emit('response', req, res); | ||
@@ -277,3 +288,63 @@ return true; | ||
function parseRanges(req, size) { | ||
var ranges; | ||
var start; | ||
var end; | ||
var i; | ||
var originalSize = size; | ||
// support range headers | ||
if (req.headers.range) { | ||
// 'bytes=100-200,300-400' --> ['100-200','300-400'] | ||
if (!RANGE_REQUEST_HEADER_TEST.test(req.headers.range)) { | ||
return sendError(req, res, null, HTTP_STATUS_BAD_REQUEST, 'Invalid Range Headers: ' + req.headers.range); | ||
} | ||
ranges = req.headers.range.match(RANGE_REQUEST_HEADER_PATTERN); | ||
size = 0; | ||
if (!ranges) { | ||
return sendError(server, req, res, null, HTTP_STATUS_BAD_REQUEST, 'Invalid Range Headers: ' + req.headers.range); | ||
} | ||
i = ranges.length; | ||
while (--i >= 0) { | ||
// 100-200 --> [100, 200] = bytes 100 to 200 | ||
// -200 --> [null, 200] = last 100 bytes | ||
// 100- --> [100, null] = bytes 100 to end | ||
range = ranges[i].split('-'); | ||
start = range[0] ? Number(range[0]) : null; | ||
end = range[1] ? Number(range[1]) : null; | ||
// check if requested range is valid: | ||
// - check it is within file range | ||
// - check that start is smaller than end, if both are set | ||
if ((start > originalSize) || (end > originalSize) || ((start && end) && start > end)) { | ||
res.headers['Content-Range'] = 'bytes=0-' + originalSize; | ||
return sendError(server, req, res, null, DEFAULT_STATUS_REQUEST_RANGE_NOT_SATISFIABLE); | ||
} | ||
// update size | ||
if (start !== null && end !== null) { | ||
size += (end - start); | ||
ranges[i] = { start: start, end: end + 1 }; | ||
} else if (start !== null) { | ||
size += (originalSize - start); | ||
ranges[i] = { start: start, end: originalSize + 1 }; | ||
} else if (end !== null) { | ||
size += end; | ||
ranges[i] = { start: originalSize - end, end: originalSize }; | ||
} | ||
} | ||
} | ||
return { | ||
ranges: ranges, | ||
size: size | ||
}; | ||
} | ||
/** | ||
@@ -284,9 +355,11 @@ Send error back to the client. If `status` is not specified, a value | ||
@param req {Object} the request object | ||
@param res {Object} the response object | ||
@param status {Number} the status (default 500) | ||
@param message {String} the status message (optional) | ||
@param server {StaticServer} the server instance | ||
@param req {Object} the request object | ||
@param res {Object} the response object | ||
@param err {Object} an Error object, if any | ||
@param status {Number} the status (default 500) | ||
@param message {String} the status message (optional) | ||
*/ | ||
function sendError(req, res, error, status, message) { | ||
status = status || res.status || DEFAULT_STATUS_ERR; | ||
function sendError(server, req, res, err, status, message) { | ||
status = status || res.status || HTTP_STATUS_ERR | ||
message = message || http.STATUS_CODES[status]; | ||
@@ -302,3 +375,3 @@ | ||
'Content-MD5', | ||
'Content-Range', | ||
// 'Content-Range', // Error 416 SHOULD contain this header | ||
'Etag', | ||
@@ -311,2 +384,3 @@ 'Expires', | ||
res.status = status; | ||
res.headers['Content-Type'] = mime.lookup('text'); | ||
@@ -319,8 +393,3 @@ | ||
console.log(chalk.gray('-->'), chalk.red(status, message), req.path, '(' + res.elapsedTime + ')'); | ||
if (error && program.debug) { | ||
console.error(error.stack || error.message || error); | ||
} | ||
server.emit('response', req, res, err); | ||
} | ||
@@ -334,34 +403,17 @@ | ||
@param req {Object} the request object | ||
@param res {Object} the response object | ||
@param stat {Object} the actual file stat | ||
@param file {String} the absolute file path | ||
@param server {StaticServer} the server instance | ||
@param req {Object} the request object | ||
@param res {Object} the response object | ||
@param stat {Object} the actual file stat | ||
@param file {String} the absolute file path | ||
*/ | ||
function sendFile(req, res, stat, file) { | ||
function sendFile(server, req, res, stat, file) { | ||
var headersSent = false; | ||
var relFile; | ||
var nrmFile; | ||
var range, start, end; | ||
var streamOptions = {flags: 'r'}; | ||
var size = stat.size; | ||
var contentParts = parseRanges(req, stat.size); | ||
var streamOptions = { flags: 'r' }; | ||
var contentType = mime.lookup(file); | ||
var rangeIndex = 0; | ||
// support range headers | ||
if (req.headers.range) { | ||
range = req.headers.range.split('-').map(Number); | ||
start = range[0]; | ||
end = range[1]; | ||
// check if requested range is within file range | ||
if ((start < 0) || (end < 0) || (start > stat.size) || (end > stat.size)) { | ||
return sendError(req, res, null, DEFAULT_STATUS_REQUEST_RANGE_NOT_SATISFIABLE); | ||
} | ||
res.headers['Content-Range'] = req.headers.range; | ||
// update filestream options | ||
streamOptions.start = start; | ||
streamOptions.end = end; | ||
// update size | ||
size = end - start; | ||
if (!contentParts) { | ||
return; // ranges failed, abort | ||
} | ||
@@ -372,34 +424,66 @@ | ||
res.headers['Last-Modified'] = new Date(stat.mtime).toUTCString(); | ||
res.headers['Content-Type'] = mime.lookup(file); | ||
res.headers['Content-Length'] = size; | ||
if (contentParts.ranges && contentParts.ranges.length > 1) { | ||
res.headers['Content-Type'] = 'multipart/byteranges; boundary=' + MULTIPART_SEPARATOR; | ||
} else { | ||
res.headers['Content-Type'] = contentType; | ||
res.headers['Content-Length'] = contentParts.size; | ||
if (contentParts.ranges) { | ||
res.headers['Content-Range'] = req.headers.range; | ||
} | ||
} | ||
// return only headers if request method is HEAD | ||
if (req.method === 'HEAD') { | ||
res.status = DEFAULT_STATUS_OK; | ||
res.writeHead(DEFAULT_STATUS_OK, res.headers); | ||
res.status = HTTP_STATUS_OK; | ||
res.writeHead(HTTP_STATUS_OK, res.headers); | ||
res.end(); | ||
console.log(chalk.gray('-->'), chalk.green(res.status, http.STATUS_CODES[res.status]), req.path + (nrmFile !== relFile ? (' ' + chalk.dim('(' + relFile + ')')) : ''), fsize(size).human(), '(' + res.elapsedTime + ')'); | ||
return; | ||
} | ||
server.emit('response', req, res, null, file, stat); | ||
} else if (!validateClientCache(server, req, res, stat, file)) { | ||
if (validateClientCache(req, res, stat, file)) { | ||
return; // abort | ||
(function sendNext() { | ||
var range; | ||
if (contentParts.ranges) { | ||
range = contentParts.ranges[rangeIndex++]; | ||
streamOptions.start = range.start; | ||
streamOptions.end = range.end; | ||
} | ||
fs.createReadStream(file, streamOptions) | ||
.on('close', function () { | ||
// close response when there are no ranges defined | ||
// or when the last range has been read | ||
if (!range || (rangeIndex >= contentParts.ranges.length)) { | ||
res.end(); | ||
server.emit('response', req, res, null, file, stat); | ||
} else { | ||
setImmediate(sendNext); | ||
} | ||
}).on('open', function (fd) { | ||
if (!headersSent) { | ||
if (range) { | ||
res.status = HTTP_STATUS_PARTIAL_CONTENT; | ||
} else { | ||
res.status = HTTP_STATUS_OK; | ||
} | ||
res.writeHead(res.status, res.headers); | ||
headersSent = true; | ||
} | ||
if (range && contentParts.ranges.length > 1) { | ||
res.write(MULTIPART_SEPARATOR + NEWLINE + | ||
'Content-Type: ' + contentType + NEWLINE + | ||
'Content-Range: ' + (range.start || '') + '-' + (range.end || '') + NEWLINE + NEWLINE); | ||
} | ||
}).on('error', function (err) { | ||
sendError(server, req, res, err); | ||
}).on('data', function (chunk) { | ||
res.write(chunk); | ||
}); | ||
})(); | ||
} | ||
relFile = path.relative(program.rootPath, file); | ||
nrmFile = path.normalize(req.path.substring(1)); | ||
fs.createReadStream(file, streamOptions).on('close', function () { | ||
res.end(); | ||
console.log(chalk.gray('-->'), chalk.green(res.status, http.STATUS_CODES[res.status]), req.path + (nrmFile !== relFile ? (' ' + chalk.dim('(' + relFile + ')')) : ''), fsize(size).human(), '(' + res.elapsedTime + ')'); | ||
}).on('error', function (err) { | ||
sendError(req, res, err); | ||
}).on('data', function (chunk) { | ||
if (!headersSent) { | ||
res.status = DEFAULT_STATUS_OK; | ||
res.writeHead(DEFAULT_STATUS_OK, res.headers); | ||
headersSent = true; | ||
} | ||
res.write(chunk); | ||
}); | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
33709
15
562
71
4
3
4