From fe915905075115c471451bda2581a0087d34bc8f Mon Sep 17 00:00:00 2001 From: MaximSagan <39352647+MaximSagan@users.noreply.github.com> Date: Wed, 6 Nov 2024 03:25:50 +1000 Subject: [PATCH] feat: add `--base-dir` to cli options to specify base path (#837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add baseDir option, add --baseDir flag to cli options to specify base path * add path to logged URLs * Address feedback from @thornjad, add --base-dir flag * base-dir tests --------- Co-authored-by: Jesse Ditson Co-authored-by: Jesse Ditson Co-authored-by: max.sagan Co-authored-by: Eric Dubé --- README.md | 1 + bin/http-server | 14 ++++++++++---- lib/http-server.js | 1 + test/main.test.js | 31 +++++++++++++++++++++++++++++++ 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e2c00136..272a2f35 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ with the provided Dockerfile. | ------------- |-------------|-------------| |`-p` or `--port` |Port to use. Use `-p 0` to look for an open port, starting at 8080. It will also read from `process.env.PORT`. |8080 | |`-a` |Address to use |0.0.0.0| +|`--base-dir` | Base path to serve files from | `/` | |`-d` |Show directory listings |`true` | |`-i` | Display autoIndex | `true` | |`-g` or `--gzip` |When enabled it will serve `./public/some-file.js.gz` in place of `./public/some-file.js` when a gzipped version of the file exists and the request accepts gzip encoding. If brotli is also enabled, it will try to serve brotli first.|`false`| diff --git a/bin/http-server b/bin/http-server index 71e4e1ff..a5b79f2f 100755 --- a/bin/http-server +++ b/bin/http-server @@ -28,6 +28,7 @@ if (argv.h || argv.help) { ' -p --port Port to use. If 0, look for open port. [8080]', ' -a Address to use [0.0.0.0] or [::]', ' -d Show directory listings [true]', + ' --base-dir Base directory to serve files from [/]', ' -i Display autoIndex [true]', ' -g --gzip Serve gzip files when possible [false]', ' -b --brotli Serve brotli files when possible [false]', @@ -77,6 +78,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, + baseDir = argv['base-dir'], logger; var proxyOptionsBooleanProps = [ @@ -146,6 +148,7 @@ function listen(port) { cache: argv.c, timeout: argv.t, showDir: argv.d, + baseDir: baseDir, autoIndex: argv.i, gzip: argv.g || argv.gzip, brotli: argv.b || argv.brotli, @@ -220,7 +223,8 @@ function listen(port) { var server = httpServer.createServer(options); server.listen(port, host, function () { - var protocol = tls ? 'https://' : 'http://'; + var protocol = tls ? 'https://' : 'http://', + path = baseDir ? '/' + baseDir.replace(/^\//, '') : ''; logger.info([ chalk.yellow('Starting up http-server, serving '), @@ -239,7 +243,8 @@ function listen(port) { ([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('')) + ([chalk.yellow('Default File Extension: '), argv.e ? chalk.cyan(argv.e) : (argv.ext ? chalk.cyan(argv.ext) : chalk.red('none'))].join('')), + ([chalk.yellow('Base directory: '), baseDir ? chalk.cyan(baseDir) : chalk.cyan('/')].join('')) ].join('\n')); if (options.headers) { @@ -252,13 +257,14 @@ function listen(port) { logger.info(chalk.yellow('\nAvailable on:')); + if (argv.a && (host !== '::' || host !== '0.0.0.0')) { - logger.info(` ${protocol}${host}:${chalk.green(port.toString())}`); + logger.info(` ${protocol}${host}:${chalk.green(port.toString())}${path}`); } 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()))); + logger.info((' ' + protocol + details.address + ':' + chalk.green(port.toString()) + path)); } if (details.family === 'IPv6' && !details.address.startsWith("fe80") ) { // Ignoring Ipv6-Link Local addresses logger.info((' ' + protocol + details.address + ':' + chalk.green(port.toString()))); diff --git a/lib/http-server.js b/lib/http-server.js index dfe4c474..0e946ac7 100644 --- a/lib/http-server.js +++ b/lib/http-server.js @@ -127,6 +127,7 @@ function HttpServer(options) { before.push(httpServerCore({ root: this.root, + baseDir: options.baseDir, cache: this.cache, showDir: this.showDir, showDotfiles: this.showDotfiles, diff --git a/test/main.test.js b/test/main.test.js index ebff0563..3e3e4a63 100644 --- a/test/main.test.js +++ b/test/main.test.js @@ -291,6 +291,37 @@ test('http-server main', (t) => { } }); }), + + new Promise((resolve) => { + const server = httpServer.createServer({ + root, + baseDir: '/test' + }); + + server.listen(8084, async () => { + try { + await Promise.all([ + // should serve files at the specified baseDir + requestAsync("http://localhost:8084/test/file").then(async (res) => { + t.equal(res.statusCode, 200); + const fileData = await fsReadFile(path.join(root, 'file'), 'utf8'); + t.equal(res.body.trim(), fileData.trim()); + }).catch(err => t.fail(err.toString())), + + // should not serve files at the root + requestAsync("http://localhost:8084/file").then((res) => { + t.equal(res.statusCode, 403); + t.equal(res.body, ''); + }).catch(err => t.fail(err.toString())), + ]); + } catch (err) { + t.fail(err.toString()); + } finally { + server.close(); + resolve(); + } + }); + }), ]).then(() => t.end()) .catch(err => { t.fail(err.toString());