279 lines
9.2 KiB
Plaintext
279 lines
9.2 KiB
Plaintext
|
#!/usr/bin/env node
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
var chalk = require('chalk'),
|
||
|
os = require('os'),
|
||
|
httpServer = require('../lib/http-server'),
|
||
|
portfinder = require('portfinder'),
|
||
|
opener = require('opener'),
|
||
|
|
||
|
fs = require('fs'),
|
||
|
url = require('url');
|
||
|
var argv = require('minimist')(process.argv.slice(2), {
|
||
|
alias: {
|
||
|
tls: 'ssl'
|
||
|
}
|
||
|
});
|
||
|
var ifaces = os.networkInterfaces();
|
||
|
|
||
|
process.title = 'http-server';
|
||
|
|
||
|
if (argv.h || argv.help) {
|
||
|
console.log([
|
||
|
'usage: http-server [path] [options]',
|
||
|
'',
|
||
|
'options:',
|
||
|
' -p --port Port to use. If 0, look for open port. [8080]',
|
||
|
' -a Address to use [0.0.0.0]',
|
||
|
' -d Show directory listings [true]',
|
||
|
' -i Display autoIndex [true]',
|
||
|
' -g --gzip Serve gzip files when possible [false]',
|
||
|
' -b --brotli Serve brotli files when possible [false]',
|
||
|
' If both brotli and gzip are enabled, brotli takes precedence',
|
||
|
' -e --ext Default file extension if none supplied [none]',
|
||
|
' -s --silent Suppress log messages from output',
|
||
|
' --cors[=headers] Enable CORS via the "Access-Control-Allow-Origin" header',
|
||
|
' Optionally provide CORS headers list separated by commas',
|
||
|
' -o [path] Open browser window after starting the server.',
|
||
|
' Optionally provide a URL path to open the browser window to.',
|
||
|
' -c Cache time (max-age) in seconds [3600], e.g. -c10 for 10 seconds.',
|
||
|
' To disable caching, use -c-1.',
|
||
|
' -t Connections timeout in seconds [120], e.g. -t60 for 1 minute.',
|
||
|
' To disable timeout, use -t0',
|
||
|
' -U --utc Use UTC time format in log messages.',
|
||
|
' --log-ip Enable logging of the client\'s IP address',
|
||
|
'',
|
||
|
' -P --proxy Fallback proxy if the request cannot be resolved. e.g.: http://someurl.com',
|
||
|
' --proxy-options Pass options to proxy using nested dotted objects. e.g.: --proxy-options.secure false',
|
||
|
'',
|
||
|
' --username Username for basic authentication [none]',
|
||
|
' Can also be specified with the env variable NODE_HTTP_SERVER_USERNAME',
|
||
|
' --password Password for basic authentication [none]',
|
||
|
' Can also be specified with the env variable NODE_HTTP_SERVER_PASSWORD',
|
||
|
'',
|
||
|
' -S --tls --ssl Enable secure request serving with TLS/SSL (HTTPS)',
|
||
|
' -C --cert Path to TLS cert file (default: cert.pem)',
|
||
|
' -K --key Path to TLS key file (default: key.pem)',
|
||
|
'',
|
||
|
' -r --robots Respond to /robots.txt [User-agent: *\\nDisallow: /]',
|
||
|
' --no-dotfiles Do not show dotfiles',
|
||
|
' --mimetypes Path to a .types file for custom mimetype definition',
|
||
|
' -h --help Print this list and exit.',
|
||
|
' -v --version Print the version and exit.'
|
||
|
].join('\n'));
|
||
|
process.exit();
|
||
|
}
|
||
|
|
||
|
var port = argv.p || argv.port || parseInt(process.env.PORT, 10),
|
||
|
host = argv.a || '0.0.0.0',
|
||
|
tls = argv.S || argv.tls,
|
||
|
sslPassphrase = process.env.NODE_HTTP_SERVER_SSL_PASSPHRASE,
|
||
|
proxy = argv.P || argv.proxy,
|
||
|
proxyOptions = argv['proxy-options'],
|
||
|
utc = argv.U || argv.utc,
|
||
|
version = argv.v || argv.version,
|
||
|
logger;
|
||
|
|
||
|
var proxyOptionsBooleanProps = [
|
||
|
'ws', 'xfwd', 'secure', 'toProxy', 'prependPath', 'ignorePath', 'changeOrigin',
|
||
|
'preserveHeaderKeyCase', 'followRedirects', 'selfHandleResponse'
|
||
|
];
|
||
|
|
||
|
if (proxyOptions) {
|
||
|
Object.keys(proxyOptions).forEach(function (key) {
|
||
|
if (proxyOptionsBooleanProps.indexOf(key) > -1) {
|
||
|
proxyOptions[key] = proxyOptions[key].toLowerCase() === 'true';
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (!argv.s && !argv.silent) {
|
||
|
logger = {
|
||
|
info: console.log,
|
||
|
request: function (req, res, error) {
|
||
|
var date = utc ? new Date().toUTCString() : new Date();
|
||
|
var ip = argv['log-ip']
|
||
|
? req.headers['x-forwarded-for'] || '' + req.connection.remoteAddress
|
||
|
: '';
|
||
|
if (error) {
|
||
|
logger.info(
|
||
|
'[%s] %s "%s %s" Error (%s): "%s"',
|
||
|
date, ip, chalk.red(req.method), chalk.red(req.url),
|
||
|
chalk.red(error.status.toString()), chalk.red(error.message)
|
||
|
);
|
||
|
}
|
||
|
else {
|
||
|
logger.info(
|
||
|
'[%s] %s "%s %s" "%s"',
|
||
|
date, ip, chalk.cyan(req.method), chalk.cyan(req.url),
|
||
|
req.headers['user-agent']
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
else if (chalk) {
|
||
|
logger = {
|
||
|
info: function () {},
|
||
|
request: function () {}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if (version) {
|
||
|
logger.info('v' + require('../package.json').version);
|
||
|
process.exit();
|
||
|
}
|
||
|
|
||
|
if (!port) {
|
||
|
portfinder.basePort = 8080;
|
||
|
portfinder.getPort(function (err, port) {
|
||
|
if (err) { throw err; }
|
||
|
listen(port);
|
||
|
});
|
||
|
}
|
||
|
else {
|
||
|
listen(port);
|
||
|
}
|
||
|
|
||
|
function listen(port) {
|
||
|
var options = {
|
||
|
root: argv._[0],
|
||
|
cache: argv.c,
|
||
|
timeout: argv.t,
|
||
|
showDir: argv.d,
|
||
|
autoIndex: argv.i,
|
||
|
gzip: argv.g || argv.gzip,
|
||
|
brotli: argv.b || argv.brotli,
|
||
|
robots: argv.r || argv.robots,
|
||
|
ext: argv.e || argv.ext,
|
||
|
logFn: logger.request,
|
||
|
proxy: proxy,
|
||
|
proxyOptions: proxyOptions,
|
||
|
showDotfiles: argv.dotfiles,
|
||
|
mimetypes: argv.mimetypes,
|
||
|
username: argv.username || process.env.NODE_HTTP_SERVER_USERNAME,
|
||
|
password: argv.password || process.env.NODE_HTTP_SERVER_PASSWORD
|
||
|
};
|
||
|
|
||
|
if (argv.cors) {
|
||
|
options.cors = true;
|
||
|
if (typeof argv.cors === 'string') {
|
||
|
options.corsHeaders = argv.cors;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (proxy) {
|
||
|
try {
|
||
|
new url.URL(proxy)
|
||
|
}
|
||
|
catch (err) {
|
||
|
logger.info(chalk.red('Error: Invalid proxy url'));
|
||
|
process.exit(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (tls) {
|
||
|
options.https = {
|
||
|
cert: argv.C || argv.cert || 'cert.pem',
|
||
|
key: argv.K || argv.key || 'key.pem',
|
||
|
passphrase: sslPassphrase,
|
||
|
};
|
||
|
try {
|
||
|
fs.lstatSync(options.https.cert);
|
||
|
}
|
||
|
catch (err) {
|
||
|
logger.info(chalk.red('Error: Could not find certificate ' + options.https.cert));
|
||
|
process.exit(1);
|
||
|
}
|
||
|
try {
|
||
|
fs.lstatSync(options.https.key);
|
||
|
}
|
||
|
catch (err) {
|
||
|
logger.info(chalk.red('Error: Could not find private key ' + options.https.key));
|
||
|
process.exit(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var server = httpServer.createServer(options);
|
||
|
server.listen(port, host, function () {
|
||
|
var protocol = tls ? 'https://' : 'http://';
|
||
|
|
||
|
logger.info([
|
||
|
chalk.yellow('Starting up http-server, serving '),
|
||
|
chalk.cyan(server.root),
|
||
|
tls ? (chalk.yellow(' through') + chalk.cyan(' https')) : ''
|
||
|
].join(''));
|
||
|
|
||
|
logger.info([chalk.yellow('\nhttp-server version: '), chalk.cyan(require('../package.json').version)].join(''));
|
||
|
|
||
|
logger.info([
|
||
|
chalk.yellow('\nhttp-server settings: '),
|
||
|
([chalk.yellow('CORS: '), argv.cors ? chalk.cyan(argv.cors) : chalk.red('disabled')].join('')),
|
||
|
([chalk.yellow('Cache: '), argv.c ? (argv.c === '-1' ? chalk.red('disabled') : chalk.cyan(argv.c + ' seconds')) : chalk.cyan('3600 seconds')].join('')),
|
||
|
([chalk.yellow('Connection Timeout: '), argv.t === '0' ? chalk.red('disabled') : (argv.t ? chalk.cyan(argv.t + ' seconds') : chalk.cyan('120 seconds'))].join('')),
|
||
|
([chalk.yellow('Directory Listings: '), argv.d ? chalk.red('not visible') : chalk.cyan('visible')].join('')),
|
||
|
([chalk.yellow('AutoIndex: '), argv.i ? chalk.red('not visible') : chalk.cyan('visible')].join('')),
|
||
|
([chalk.yellow('Serve GZIP Files: '), argv.g || argv.gzip ? chalk.cyan('true') : chalk.red('false')].join('')),
|
||
|
([chalk.yellow('Serve Brotli Files: '), argv.b || argv.brotli ? chalk.cyan('true') : chalk.red('false')].join('')),
|
||
|
([chalk.yellow('Default File Extension: '), argv.e ? chalk.cyan(argv.e) : (argv.ext ? chalk.cyan(argv.ext) : chalk.red('none'))].join(''))
|
||
|
].join('\n'));
|
||
|
|
||
|
logger.info(chalk.yellow('\nAvailable on:'));
|
||
|
|
||
|
if (argv.a && host !== '0.0.0.0') {
|
||
|
logger.info(` ${protocol}${host}:${chalk.green(port.toString())}`);
|
||
|
} else {
|
||
|
Object.keys(ifaces).forEach(function (dev) {
|
||
|
ifaces[dev].forEach(function (details) {
|
||
|
if (details.family === 'IPv4') {
|
||
|
logger.info((' ' + protocol + details.address + ':' + chalk.green(port.toString())));
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (typeof proxy === 'string') {
|
||
|
if (proxyOptions) {
|
||
|
logger.info('Unhandled requests will be served from: ' + proxy + '. Options: ' + JSON.stringify(proxyOptions));
|
||
|
}
|
||
|
else {
|
||
|
logger.info('Unhandled requests will be served from: ' + proxy);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
logger.info('Hit CTRL-C to stop the server');
|
||
|
if (argv.o) {
|
||
|
const openHost = host === '0.0.0.0' ? '127.0.0.1' : host;
|
||
|
let openUrl = `${protocol}${openHost}:${port}`;
|
||
|
if (typeof argv.o === 'string') {
|
||
|
openUrl += argv.o[0] === '/' ? argv.o : '/' + argv.o;
|
||
|
}
|
||
|
logger.info('Open: ' + openUrl);
|
||
|
opener(openUrl);
|
||
|
}
|
||
|
|
||
|
// Spacing before logs
|
||
|
if (!argv.s) logger.info();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (process.platform === 'win32') {
|
||
|
require('readline').createInterface({
|
||
|
input: process.stdin,
|
||
|
output: process.stdout
|
||
|
}).on('SIGINT', function () {
|
||
|
process.emit('SIGINT');
|
||
|
});
|
||
|
}
|
||
|
|
||
|
process.on('SIGINT', function () {
|
||
|
logger.info(chalk.red('http-server stopped.'));
|
||
|
process.exit();
|
||
|
});
|
||
|
|
||
|
process.on('SIGTERM', function () {
|
||
|
logger.info(chalk.red('http-server stopped.'));
|
||
|
process.exit();
|
||
|
});
|