| #!/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(); |
| }); |