From 604e8bcdfd2da40737e37fb39a2816997b691c6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benedikt=20M=C3=BCll?= Date: Thu, 14 Nov 2024 13:11:26 +0100 Subject: [PATCH 01/10] added proxy config for complex proxy rules --- bin/http-server | 16 ++++++++++++++++ lib/http-server.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/bin/http-server b/bin/http-server index a5b79f2f..ae72e9fe 100755 --- a/bin/http-server +++ b/bin/http-server @@ -51,6 +51,7 @@ if (argv.h || argv.help) { '', ' -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', + ' --proxy-config Pass in .json configuration file. e.g.: ./path/to/config.json', '', ' --username Username for basic authentication [none]', ' Can also be specified with the env variable NODE_HTTP_SERVER_USERNAME', @@ -76,6 +77,7 @@ var port = argv.p || argv.port || parseInt(process.env.PORT, 10), sslPassphrase = process.env.NODE_HTTP_SERVER_SSL_PASSPHRASE, proxy = argv.P || argv.proxy, proxyOptions = argv['proxy-options'], + proxyConfig = argv['proxy-config'], utc = argv.U || argv.utc, version = argv.v || argv.version, baseDir = argv['base-dir'], @@ -157,6 +159,7 @@ function listen(port) { logFn: logger.request, proxy: proxy, proxyOptions: proxyOptions, + proxyConfig: proxyConfig, showDotfiles: argv.dotfiles, mimetypes: argv.mimetypes, username: argv.username || process.env.NODE_HTTP_SERVER_USERNAME, @@ -199,6 +202,19 @@ function listen(port) { } } + if (proxyConfig) { + try { + proxyConfig = JSON.parse(fs.readFileSync(proxyConfig)); + } + catch (err) { + logger.info(chalk.red('Error: Invalid proxy config file')); + process.exit(1); + } + // Proxy file overrides cli config + proxy = undefined; + proxyOptions = undefined; + } + if (tls) { options.https = { cert: argv.C || argv.cert || 'cert.pem', diff --git a/lib/http-server.js b/lib/http-server.js index 0e946ac7..5340b8f8 100644 --- a/lib/http-server.js +++ b/lib/http-server.js @@ -158,6 +158,37 @@ function HttpServer(options) { }); } + if (typeof options.proxyConfig === 'object') { + var proxy = httpProxy.createProxyServer(); + before.push(function (req, res) { + var matchOptions = {}; + + for (var key in Object.keys(options.proxyConfig)) { + var regex = new RegExp(key); + if (regex.test(req.url)) { + var matchConfig = options.proxyConfig[key]; + + Object.entries(matchConfig.pathRewrites).forEach(rewrite => { + req.url = req.url.replace(new RegExp(rewrite[0]), rewrite[1]); + }); + + var configEntries = Object.entries(matchConfig); + configEntries.forEach(entry => matchOptions.options[entry[0]] = matchOptions.options[entry[1]]); + break; + } + } + + proxy.web(req, res, matchOptions, function (err, req, res) { + if (options.logFn) { + options.logFn(req, res, { + message: err.message, + status: res.statusCode }); + } + res.emit('next'); + }); + }); + } + var serverOptions = { before: before, headers: this.headers, From 349461620d87bfe86a25c0f2bdeadbc06ae6f792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benedikt=20M=C3=BCll?= Date: Thu, 14 Nov 2024 13:44:21 +0100 Subject: [PATCH 02/10] fixed wrong proxy config value assignment and filtered match config --- lib/http-server.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/http-server.js b/lib/http-server.js index 5340b8f8..0fa1fe40 100644 --- a/lib/http-server.js +++ b/lib/http-server.js @@ -168,12 +168,12 @@ function HttpServer(options) { if (regex.test(req.url)) { var matchConfig = options.proxyConfig[key]; - Object.entries(matchConfig.pathRewrites).forEach(rewrite => { + Object.entries(matchConfig.pathRewrite).forEach(rewrite => { req.url = req.url.replace(new RegExp(rewrite[0]), rewrite[1]); }); - var configEntries = Object.entries(matchConfig); - configEntries.forEach(entry => matchOptions.options[entry[0]] = matchOptions.options[entry[1]]); + var configEntries = Object.entries(matchConfig).filter(entry => entry[0] !== "pathRewrite"); + configEntries.forEach(entry => matchOptions.options[entry[0]] = entry[1]); break; } } From faa51e03a95e92586f953b5e983f3b0251029efc Mon Sep 17 00:00:00 2001 From: I3ene Date: Sun, 2 Mar 2025 11:36:31 +0100 Subject: [PATCH 03/10] added basic documentation for proxy-config --- README.md | 5 +++-- doc/http-server.1 | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 272a2f35..4d7efa43 100644 --- a/README.md +++ b/README.md @@ -76,11 +76,12 @@ with the provided Dockerfile. |`-U` or `--utc` |Use UTC time format in log messages.| | |`--log-ip` |Enable logging of the client's IP address |`false` | |`-P` or `--proxy` |Proxies all requests which can't be resolved locally to the given url. e.g.: -P http://someurl.com | | -|`--proxy-options` |Pass proxy [options](https://github.com/http-party/node-http-proxy#options) using nested dotted objects. e.g.: --proxy-options.secure false | +|`--proxy-options` |Pass proxy [options](https://github.com/http-party/node-http-proxy#options) using nested dotted objects. e.g.: --proxy-options.secure false | | +|`--proxy-config` |Pass in `.json` configuration file. e.g.: `./path/to/config.json` | | |`--username` |Username for basic authentication | | |`--password` |Password for basic authentication | | |`-S`, `--tls` or `--ssl` |Enable secure request serving with TLS/SSL (HTTPS)|`false`| -|`-C` or `--cert` |Path to ssl cert file |`cert.pem` | +|`-C` or `--cert` |Path to ssl cert file |`cert.pem` | |`-K` or `--key` |Path to ssl key file |`key.pem` | |`-r` or `--robots` | Automatically provide a /robots.txt (The content of which defaults to `User-agent: *\nDisallow: /`) | `false` | |`--no-dotfiles` |Do not show dotfiles| | diff --git a/doc/http-server.1 b/doc/http-server.1 index 1337c5a2..5ac7f5e8 100644 --- a/doc/http-server.1 +++ b/doc/http-server.1 @@ -93,6 +93,10 @@ Fallback proxy if the request cannot be resolved. .BI \-\-proxy\-options Pass proxy options using nested dotted objects. +.TP +.BI \-\-proxy\-config +Pass in .json configuration file. + .TP .BI \-\-username " " \fIUSERNAME\fR Username for basic authentication. From 79bd9521a795b5763c6ba9ef8ed3ecbef78decd1 Mon Sep 17 00:00:00 2001 From: I3ene Date: Sun, 2 Mar 2025 13:19:14 +0100 Subject: [PATCH 04/10] fixed proxy config handling --- lib/http-server.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/http-server.js b/lib/http-server.js index 0fa1fe40..a43dbe30 100644 --- a/lib/http-server.js +++ b/lib/http-server.js @@ -163,17 +163,22 @@ function HttpServer(options) { before.push(function (req, res) { var matchOptions = {}; - for (var key in Object.keys(options.proxyConfig)) { - var regex = new RegExp(key); + for (var key of Object.keys(options.proxyConfig)) { + // Parse simplified regex to real working regex + // TODO: Add single path element regex (e.g. '/some/$/path' -> '/some/[^/]*/path') + var regexString = key.replaceAll('*', '.*'); + var regex = new RegExp(regexString); if (regex.test(req.url)) { var matchConfig = options.proxyConfig[key]; - Object.entries(matchConfig.pathRewrite).forEach(rewrite => { - req.url = req.url.replace(new RegExp(rewrite[0]), rewrite[1]); - }); + if (matchConfig.pathRewrite) { + Object.entries(matchConfig.pathRewrite).forEach(rewrite => { + req.url = req.url.replace(new RegExp(rewrite[0]), rewrite[1]); + }); + } var configEntries = Object.entries(matchConfig).filter(entry => entry[0] !== "pathRewrite"); - configEntries.forEach(entry => matchOptions.options[entry[0]] = entry[1]); + configEntries.forEach(entry => matchOptions[entry[0]] = entry[1]); break; } } From 8cc06b4d6e0e7e7cb6ce952309d22135a5582c58 Mon Sep 17 00:00:00 2001 From: I3ene Date: Sun, 2 Mar 2025 13:32:53 +0100 Subject: [PATCH 05/10] added first basic proxy config test --- test/proxy-config.test.js | 107 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 test/proxy-config.test.js diff --git a/test/proxy-config.test.js b/test/proxy-config.test.js new file mode 100644 index 00000000..6eb23ef8 --- /dev/null +++ b/test/proxy-config.test.js @@ -0,0 +1,107 @@ +const test = require('tap').test +const path = require('path') +const fs = require('fs') +const request = require('request') +const httpServer = require('../lib/http-server') +const promisify = require('util').promisify + +const requestAsync = promisify(request) +const fsReadFile = promisify(fs.readFile) + +// Prevent errors from being swallowed +process.on('uncaughtException', console.error) + +const root = path.join(__dirname, 'fixtures', 'root') +const httpsOpts = { + key: path.join(__dirname, 'fixtures', 'https', 'agent2-key.pem'), + cert: path.join(__dirname, 'fixtures', 'https', 'agent2-cert.pem') +} + +const proxyConfigTest = { + "/rewrite/*": { + "target": "http://localhost:8082", + "pathRewrite": { + "^/rewrite": "" + } + }, + "*": { + "target": "http://localhost:8082", + "changeOrigin": true, + "secure": false + } +} + +// Tests are grouped into those which can run together. The groups are given +// their own port to run on and live inside a Promise. Tests are done when all +// Promise test groups complete. +test('proxy config', (t) => { + new Promise((resolve) => { + const server = httpServer.createServer({ + root, + robots: true, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Credentials': 'true' + }, + cors: true, + corsHeaders: 'X-Test', + ext: true, + brotli: true, + gzip: true + }) + // TODO #723 we should use portfinder + server.listen(8082, async () => { + try { + + // Another server proxies 8083 to 8082 + const proxyServer = httpServer.createServer({ + //tls: true, + //https: httpsOpts, + proxyConfig: proxyConfigTest + }) + + await new Promise((resolve) => { + proxyServer.listen(8083, async () => { + try { + // Serve files from proxy root + await requestAsync('http://localhost:8083/file', { rejectUnauthorized: false }).then(async (res) => { + t.ok(res) + t.equal(res.statusCode, 200) + + // File content matches + const fileData = await fsReadFile(path.join(root, 'file'), 'utf8') + t.equal(res.body.trim(), fileData.trim(), 'proxied file content matches') + }).catch(err => t.fail(err.toString())) + + // Serve files from proxy with rewrite + await requestAsync('http://localhost:8083/rewrite/file', { rejectUnauthorized: false }).then(async (res) => { + t.ok(res) + t.equal(res.statusCode, 200) + + // File content matches + const fileData = await fsReadFile(path.join(root, 'file'), 'utf8') + t.equal(res.body.trim(), fileData.trim(), 'proxied file content matches') + }).catch(err => t.fail(err.toString())) + } catch (err) { + t.fail(err.toString()) + } finally { + proxyServer.close() + resolve() + } + }) + }) + + } catch (err) { + t.fail(err.toString()) + } finally { + server.close() + resolve() + } + }) + }) + .then(() => t.end()) + .catch(err => { + t.fail(err.toString()) + t.end() + }) +}) From 9cb9a826ec21b677c3e32cd1bae89492601a94df Mon Sep 17 00:00:00 2001 From: I3ene Date: Sun, 9 Nov 2025 11:47:33 +0100 Subject: [PATCH 06/10] added minmatch for glob matching --- lib/http-server.js | 71 ++++++------ package-lock.json | 281 +++++++++++++++++++++++++++------------------ package.json | 1 + 3 files changed, 205 insertions(+), 148 deletions(-) diff --git a/lib/http-server.js b/lib/http-server.js index a43dbe30..2035cd65 100644 --- a/lib/http-server.js +++ b/lib/http-server.js @@ -7,6 +7,7 @@ var fs = require('fs'), httpProxy = require('http-proxy'), corser = require('corser'), secureCompare = require('secure-compare'); +var { minimatch } = require('minimatch'); // // Remark: backwards compatibility for previous @@ -125,6 +126,40 @@ function HttpServer(options) { }); } + if (typeof options.proxyConfig === 'object') { + var proxy = httpProxy.createProxyServer(); + before.push(function (req, res, next) { + for (var key of Object.keys(options.proxyConfig)) { + if (!minimatch(req.url, key)) continue; + req.proxy ??= {}; + var matchConfig = options.proxyConfig[key]; + + if (matchConfig.pathRewrite) { + Object.entries(matchConfig.pathRewrite).forEach(rewrite => { + req.url = req.url.replace(new RegExp(rewrite[0]), rewrite[1]); + }); + } + + var configEntries = Object.entries(matchConfig).filter(entry => entry[0] !== "pathRewrite"); + configEntries.forEach(entry => req.proxy[entry[0]] = entry[1]); + break; + } + + if (req.proxy) { + proxy.web(req, res, req.proxy, function (err, req, res) { + if (options.logFn) { + options.logFn(req, res, { + message: err.message, + status: res.statusCode }); + } + res.emit('next'); + }); + } else { + next(); + } + }); + } + before.push(httpServerCore({ root: this.root, baseDir: options.baseDir, @@ -158,42 +193,6 @@ function HttpServer(options) { }); } - if (typeof options.proxyConfig === 'object') { - var proxy = httpProxy.createProxyServer(); - before.push(function (req, res) { - var matchOptions = {}; - - for (var key of Object.keys(options.proxyConfig)) { - // Parse simplified regex to real working regex - // TODO: Add single path element regex (e.g. '/some/$/path' -> '/some/[^/]*/path') - var regexString = key.replaceAll('*', '.*'); - var regex = new RegExp(regexString); - if (regex.test(req.url)) { - var matchConfig = options.proxyConfig[key]; - - if (matchConfig.pathRewrite) { - Object.entries(matchConfig.pathRewrite).forEach(rewrite => { - req.url = req.url.replace(new RegExp(rewrite[0]), rewrite[1]); - }); - } - - var configEntries = Object.entries(matchConfig).filter(entry => entry[0] !== "pathRewrite"); - configEntries.forEach(entry => matchOptions[entry[0]] = entry[1]); - break; - } - } - - proxy.web(req, res, matchOptions, function (err, req, res) { - if (options.logFn) { - options.logFn(req, res, { - message: err.message, - status: res.statusCode }); - } - res.emit('next'); - }); - }); - } - var serverOptions = { before: before, headers: this.headers, diff --git a/package-lock.json b/package-lock.json index c81f8332..99f2d861 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "html-encoding-sniffer": "^3.0.0", "http-proxy": "^1.18.1", "mime": "^1.6.0", + "minimatch": "^10.1.1", "minimist": "^1.2.6", "opener": "^1.5.1", "portfinder": "^1.0.28", @@ -35,7 +36,7 @@ "tap": "^21.0.1" }, "engines": { - "node": ">=12.16" + "node": ">=16.20.2" } }, "node_modules/@alcalzone/ansi-tokenize": { @@ -125,6 +126,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -222,6 +224,19 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/eslintrc/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -240,6 +255,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -898,6 +934,7 @@ "resolved": "https://registry.npmjs.org/@tapjs/core/-/core-4.0.0.tgz", "integrity": "sha512-COWMNbGBjf0qbsbKw+2911rrt+oXXOkIXpoMpIsz0/UN2rxqAAvDyrriObVfc4v+O2auabnWfdrxwNm3Vy01yw==", "dev": true, + "peer": true, "dependencies": { "@tapjs/processinfo": "^3.1.8", "@tapjs/stack": "4.0.0", @@ -1812,9 +1849,11 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2424,8 +2463,10 @@ }, "node_modules/concat-map": { "version": "0.0.1", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" }, "node_modules/concat-stream": { "version": "1.6.2", @@ -2857,6 +2898,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", "dev": true, + "peer": true, "dependencies": { "ajv": "^5.3.0", "babel-code-frame": "^6.22.0", @@ -3054,6 +3096,19 @@ "node": ">=4" } }, + "node_modules/eslint-find-rules/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint-find-rules/node_modules/p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -3429,6 +3484,19 @@ "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", "dev": true }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/eslint/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3844,6 +3912,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/flat-cache/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/flat-cache/node_modules/rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", @@ -4103,30 +4184,6 @@ "node": ">= 6" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -5362,15 +5419,18 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": "*" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -6386,6 +6446,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dev": true, + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -7211,6 +7272,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha512-Ajr4IcMXq/2QmMkEmSvxqfLN5zGmJ92gHXAeOXq1OekoH2rfDNsgdDoL2f7QaRCy7G/E6TpxBVdRuNraMztGHw==", "dev": true, + "peer": true, "dependencies": { "co": "^4.6.0", "fast-deep-equal": "^1.0.0", @@ -7624,15 +7686,6 @@ "node": "20 || >=22" } }, - "node_modules/tshy/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/tshy/node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -7645,21 +7698,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/tshy/node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/tshy/node_modules/mkdirp": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", @@ -7773,6 +7811,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7785,8 +7824,7 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "peer": true + "dev": true }, "node_modules/union": { "version": "0.5.0", @@ -8276,6 +8314,7 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", "dev": true, + "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -8463,7 +8502,8 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -8523,6 +8563,15 @@ "esprima": "^4.0.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -8537,6 +8586,19 @@ } } }, + "@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==" + }, + "@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "requires": { + "@isaacs/balanced-match": "^4.0.1" + } + }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -9004,6 +9066,7 @@ "resolved": "https://registry.npmjs.org/@tapjs/core/-/core-4.0.0.tgz", "integrity": "sha512-COWMNbGBjf0qbsbKw+2911rrt+oXXOkIXpoMpIsz0/UN2rxqAAvDyrriObVfc4v+O2auabnWfdrxwNm3Vy01yw==", "dev": true, + "peer": true, "requires": { "@tapjs/processinfo": "^3.1.8", "@tapjs/stack": "4.0.0", @@ -9660,8 +9723,9 @@ } }, "brace-expansion": { - "version": "1.1.11", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -10094,7 +10158,8 @@ }, "concat-map": { "version": "0.0.1", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, "concat-stream": { @@ -10430,6 +10495,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", "dev": true, + "peer": true, "requires": { "ajv": "^5.3.0", "babel-code-frame": "^6.22.0", @@ -10592,6 +10658,15 @@ "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", "dev": true }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -10763,6 +10838,15 @@ "path-exists": "^3.0.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -11211,6 +11295,15 @@ "path-is-absolute": "^1.0.0" } }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", @@ -11365,26 +11458,6 @@ "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } } }, "glob-parent": { @@ -12303,12 +12376,11 @@ "dev": true }, "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "requires": { - "brace-expansion": "^1.1.7" + "@isaacs/brace-expansion": "^5.0.0" } }, "minimist": { @@ -13079,6 +13151,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dev": true, + "peer": true, "requires": { "loose-envify": "^1.1.0" } @@ -13707,6 +13780,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha512-Ajr4IcMXq/2QmMkEmSvxqfLN5zGmJ92gHXAeOXq1OekoH2rfDNsgdDoL2f7QaRCy7G/E6TpxBVdRuNraMztGHw==", "dev": true, + "peer": true, "requires": { "co": "^4.6.0", "fast-deep-equal": "^1.0.0", @@ -14031,30 +14105,12 @@ "walk-up-path": "^4.0.0" }, "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, "chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true }, - "minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, "mkdirp": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", @@ -14139,14 +14195,14 @@ "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", - "dev": true + "dev": true, + "peer": true }, "undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "peer": true + "dev": true }, "union": { "version": "0.5.0", @@ -14522,7 +14578,8 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", - "dev": true + "dev": true, + "peer": true }, "yaml-types": { "version": "0.4.0", diff --git a/package.json b/package.json index d4c408a0..983b362c 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "html-encoding-sniffer": "^3.0.0", "http-proxy": "^1.18.1", "mime": "^1.6.0", + "minimatch": "^10.1.1", "minimist": "^1.2.6", "opener": "^1.5.1", "portfinder": "^1.0.28", From f1b05ef21c6c76a883757cd5e6d9be14ecb7e183 Mon Sep 17 00:00:00 2001 From: I3ene Date: Sun, 9 Nov 2025 11:48:30 +0100 Subject: [PATCH 07/10] fixed proxy config handling --- bin/http-server | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/bin/http-server b/bin/http-server index ae72e9fe..5a53d0c4 100755 --- a/bin/http-server +++ b/bin/http-server @@ -204,10 +204,18 @@ function listen(port) { if (proxyConfig) { try { - proxyConfig = JSON.parse(fs.readFileSync(proxyConfig)); + if (fs.existsSync(proxyConfig)) { + proxyConfig = fs.readFileSync(proxyConfig, 'utf8'); + } + if (typeof proxyConfig === 'string') { + proxyConfig = JSON.parse(proxyConfig); + } + if (typeof proxyConfig !== 'object') { + throw new Error('Invalid proxy config'); + } } catch (err) { - logger.info(chalk.red('Error: Invalid proxy config file')); + logger.info(chalk.red('Error: Invalid proxy config or file')); process.exit(1); } // Proxy file overrides cli config From 2d9b449670d79b0321dd302c07e187e40938ba1f Mon Sep 17 00:00:00 2001 From: I3ene Date: Sun, 9 Nov 2025 11:49:07 +0100 Subject: [PATCH 08/10] added proxy logging --- bin/http-server | 6 ++++++ lib/http-server.js | 3 +++ 2 files changed, 9 insertions(+) diff --git a/bin/http-server b/bin/http-server index 5a53d0c4..92d98a86 100755 --- a/bin/http-server +++ b/bin/http-server @@ -111,6 +111,12 @@ if (!argv.s && !argv.silent) { chalk.red(error.status.toString()), chalk.red(error.message) ); } + else if (req.proxy) { + logger.info( + '[%s] %s "%s" (%s)-> "%s"', + date, ip, chalk.cyan(req.url), chalk.magenta('Proxy'), chalk.cyan(req.proxy.target) + ); + } else { logger.info( '[%s] %s "%s %s" "%s"', diff --git a/lib/http-server.js b/lib/http-server.js index 2035cd65..8428c22f 100644 --- a/lib/http-server.js +++ b/lib/http-server.js @@ -146,6 +146,9 @@ function HttpServer(options) { } if (req.proxy) { + if (options.logFn) { + options.logFn(req, res); + } proxy.web(req, res, req.proxy, function (err, req, res) { if (options.logFn) { options.logFn(req, res, { From b3294d3146bf6dcb04791248e73657388ef65574 Mon Sep 17 00:00:00 2001 From: I3ene Date: Sun, 9 Nov 2025 11:49:18 +0100 Subject: [PATCH 09/10] fixed proxy config test --- test/proxy-config.test.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/test/proxy-config.test.js b/test/proxy-config.test.js index 6eb23ef8..05db6166 100644 --- a/test/proxy-config.test.js +++ b/test/proxy-config.test.js @@ -18,16 +18,11 @@ const httpsOpts = { } const proxyConfigTest = { - "/rewrite/*": { + "/rewrite/**": { "target": "http://localhost:8082", "pathRewrite": { "^/rewrite": "" } - }, - "*": { - "target": "http://localhost:8082", - "changeOrigin": true, - "secure": false } } @@ -55,6 +50,7 @@ test('proxy config', (t) => { // Another server proxies 8083 to 8082 const proxyServer = httpServer.createServer({ + root, //tls: true, //https: httpsOpts, proxyConfig: proxyConfigTest @@ -70,7 +66,7 @@ test('proxy config', (t) => { // File content matches const fileData = await fsReadFile(path.join(root, 'file'), 'utf8') - t.equal(res.body.trim(), fileData.trim(), 'proxied file content matches') + t.equal(res.body.trim(), fileData.trim(), 'none proxied file content matches') }).catch(err => t.fail(err.toString())) // Serve files from proxy with rewrite From a8fc02b433fbbe24f0aecef4cc69908026ad5b5d Mon Sep 17 00:00:00 2001 From: I3ene Date: Sun, 16 Nov 2025 16:12:33 +0100 Subject: [PATCH 10/10] handled proxy all and config with hinting --- bin/http-server | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/bin/http-server b/bin/http-server index 953fb7d0..de063051 100755 --- a/bin/http-server +++ b/bin/http-server @@ -258,7 +258,17 @@ function listen(port) { proxyOptions = undefined; } - // TODO: handle this in respect of proxyConfig + if (proxyAll && proxyConfig) { + logger.info(chalk.red('Error: --proxy-all cannot be used with --proxy-config')); + logger.info( + '%s\n%s\n%s', + chalk.yellow('Hint: Use'), + chalk.cyan('"/**": {\n "target": "your-proxy"\n}'), + chalk.yellow('in the proxy config to achieve the same effect.') + ); + process.exit(1); + } + if (proxyAll && !proxy) { logger.info(chalk.red('Error: --proxy-all requires --proxy to be set')); process.exit(1);