diff --git a/bin/http-server b/bin/http-server index 7c597fa8..2b2e15f5 100755 --- a/bin/http-server +++ b/bin/http-server @@ -1,7 +1,6 @@ #!/usr/bin/env node 'use strict'; - var chalk = require('chalk'), os = require('os'), httpServer = require('../lib/http-server'), @@ -24,7 +23,7 @@ if (argv.h || argv.help) { 'usage: http-server [path] [options]', '', 'options:', - ' -p --port Port to use. If 0, look for open port. [8080]', + ' -p --port Port to use. If 0, look for open port. [8080]. If --https-redirect, http and (main) https port [8080,443]', ' -a Address to use [0.0.0.0]', ' -d Show directory listings [true]', ' -i Display autoIndex [true]', @@ -44,17 +43,18 @@ if (argv.h || argv.help) { ' -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', + ' -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', + ' -R --https-redirect Auto redirects http -> https', '', ' --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)', + ' -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', @@ -73,6 +73,7 @@ var port = argv.p || argv.port || parseInt(process.env.PORT, 10), proxyOptions = argv['proxy-options'], utc = argv.U || argv.utc, version = argv.v || argv.version, + redirect = !!argv.R || !!argv['https-redirect'], logger; var proxyOptionsBooleanProps = [ @@ -125,18 +126,56 @@ if (version) { process.exit(); } -if (!port) { - portfinder.basePort = 8080; - portfinder.getPort(function (err, port) { - if (err) { throw err; } - listen(port); - }); +if (port === true) { + logger.info(chalk.red('Error: --port needs a value')); + process.exit(1); + return; +} + +if (tls && redirect) { + var ports = []; + if (!port) { + ports = [8080,443]; + } + else if (typeof port === 'string') { + ports = (port.toString() || '').split(',').map(function (s) { return parseInt(s, 10) }); + } + else { + logger.info(chalk.red('Error: Cannot parse "--port ' + port + '" of type ' + typeof port + '. Port has to be in the format [httpPort,httpsPort]\nExample: --port 8080,443')); + process.exit(1); + } + + if (ports.length === 2) { + if (isNaN(ports[0]) || isNaN(ports[1])) { + logger.info(chalk.red('Error parsing "--port ' + port + '". [' + ports + '] is not of type [number,number]')); + process.exit(1); + } + } + else { + logger.info(chalk.red('Error: Cannot parse "--port ' + port + '"\nExample: --port 8080,443')); + process.exit(1); + } + + listen(ports[1]); + listen(ports[0], ports[1]); +} +else if (!tls && redirect) { + logger.info(chalk.red('Error: Can\'t redirect to https when the main server runs on http!')); } else { - listen(port); + if (!port) { + portfinder.basePort = 8080; + portfinder.getPort(function (err, port) { + if (err) { throw err; } + listen(port); + }); + } + else { + listen(port); + } } -function listen(port) { +function listen(port, redirectPort) { var options = { root: argv._[0], cache: argv.c, @@ -150,12 +189,15 @@ function listen(port) { logFn: logger.request, proxy: proxy, proxyOptions: proxyOptions, + httpsPort: redirectPort, showDotfiles: argv.dotfiles, mimetypes: argv.mimetypes, username: argv.username || process.env.NODE_HTTP_SERVER_USERNAME, password: argv.password || process.env.NODE_HTTP_SERVER_PASSWORD }; + var isTLSServer = (tls && !redirectPort); + if (argv.cors) { options.cors = true; if (typeof argv.cors === 'string') { @@ -173,7 +215,7 @@ function listen(port) { } } - if (tls) { + if (isTLSServer) { options.https = { cert: argv.C || argv.cert || 'cert.pem', key: argv.K || argv.key || 'key.pem', @@ -197,12 +239,14 @@ function listen(port) { var server = httpServer.createServer(options); server.listen(port, host, function () { - var protocol = tls ? 'https://' : 'http://'; + + var protocol = isTLSServer ? 'https://' : 'http://'; logger.info([ chalk.yellow('Starting up http-server, serving '), chalk.cyan(server.root), - tls ? (chalk.yellow(' through') + chalk.cyan(' https')) : '' + isTLSServer ? (chalk.yellow(' through') + chalk.cyan(' https')) : '', + chalk.yellow('\nAvailable on:') ].join('')); logger.info([chalk.yellow('\nhttp-server version: '), chalk.cyan(require('../package.json').version)].join('')); @@ -242,6 +286,10 @@ function listen(port) { } } + if (redirectPort) { + logger.info('[Redirect listener]'); + } + logger.info('Hit CTRL-C to stop the server'); if (argv.o) { const openHost = host === '0.0.0.0' ? '127.0.0.1' : host; diff --git a/lib/http-server.js b/lib/http-server.js index dfe4c474..8d9cdf66 100644 --- a/lib/http-server.js +++ b/lib/http-server.js @@ -48,8 +48,8 @@ function HttpServer(options) { this.cache = ( // eslint-disable-next-line no-nested-ternary options.cache === undefined ? 3600 : - // -1 is a special case to turn off caching. - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#Preventing_caching + // -1 is a special case to turn off caching. + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#Preventing_caching options.cache === -1 ? 'no-cache, no-store, must-revalidate' : options.cache // in seconds. ); @@ -68,6 +68,15 @@ function HttpServer(options) { var before = options.before ? options.before.slice() : []; + if (options.httpsPort) { + before.push(function (req, res) { + var hostname = req.headers.host.match(/:/g) ? req.headers.host.slice(0, req.headers.host.indexOf(':')) : req.headers.host; + var httpsUrl = 'https://' + hostname + ':' + options.httpsPort + req.url; + res.writeHead(301, { Location: httpsUrl }); + res.end(); + }); + } + if (options.logFn) { before.push(function (req, res) { options.logFn(req, res); @@ -150,7 +159,8 @@ function HttpServer(options) { if (options.logFn) { options.logFn(req, res, { message: err.message, - status: res.statusCode }); + status: res.statusCode + }); } res.emit('next'); }); diff --git a/package.json b/package.json index fd752674..a3dba2d6 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,10 @@ { "name": "Jade Michael Thornton", "email": "jademichael@jmthornton.net" + }, + { + "name": "Henrik Storch", + "email": "thisisthefoxe@gmail.com" } ], "dependencies": {