Skip to content

Commit 8dbde7d

Browse files
authored
added proxy config for complex proxy rules (#907)
* added proxy config for complex proxy rules * fixed wrong proxy config value assignment and filtered match config * added basic documentation for proxy-config * fixed proxy config handling * added first basic proxy config test * added minmatch for glob matching * fixed proxy config handling * added proxy logging * fixed proxy config test * handled proxy all and config with hinting * fixed proxyAll request handling
1 parent 89c2cef commit 8dbde7d

File tree

7 files changed

+307
-61
lines changed

7 files changed

+307
-61
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,14 @@ with the provided Dockerfile.
8080
|`-U` or `--utc` |Use UTC time format in log messages.| |
8181
|`--log-ip` |Enable logging of the client's IP address |`false` |
8282
|`-P` or `--proxy` |Proxies all requests which can't be resolved locally to the given url. e.g.: -P http://someurl.com | |
83+
|`--proxy-options` |Pass proxy [options](https://github.com/http-party/node-http-proxy#options) using nested dotted objects. e.g.: --proxy-options.secure false | |
84+
|`--proxy-config` |Pass in `.json` configuration file or stringified JSON. e.g.: `./path/to/config.json` | |
8385
|`--proxy-all` |Forward every request to the proxy target instead of serving local files|`false`|
8486
|`--proxy-options` |Pass proxy [options](https://github.com/http-party/node-http-proxy#options) using nested dotted objects. e.g.: --proxy-options.secure false |
8587
|`--user` or `--username` |Username for basic authentication | |
8688
|`--password` |Password for basic authentication | |
8789
|`-S`, `--tls` or `--ssl` |Enable secure request serving with TLS/SSL (HTTPS)|`false`|
88-
|`-C` or `--cert` |Path to ssl cert file |`cert.pem` |
90+
|`-C` or `--cert` |Path to ssl cert file |`cert.pem` |
8991
|`-K` or `--key` |Path to ssl key file |`key.pem` |
9092
|`-r` or `--robots` | Automatically provide a /robots.txt (The content of which defaults to `User-agent: *\nDisallow: /`) | `false` |
9193
|`--no-dotfiles` |Do not show dotfiles| |

bin/http-server

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ if (argv.h || argv.help) {
6767
' -P --proxy Fallback proxy if the request cannot be resolved. e.g.: http://someurl.com',
6868
' --proxy-all Send every request to the proxy target instead of serving local files',
6969
' --proxy-options Pass options to proxy using nested dotted objects. e.g.: --proxy-options.secure false',
70+
' --proxy-config Pass in .json configuration file. e.g.: ./path/to/config.json',
7071
' --websocket Enable websocket proxy',
7172
'',
7273
' --user --username Username for basic authentication [none]',
@@ -93,6 +94,7 @@ var port = argv.p || argv.port || parseInt(process.env.PORT, 10),
9394
sslPassphrase = process.env.NODE_HTTP_SERVER_SSL_PASSPHRASE,
9495
proxy = argv.P || argv.proxy,
9596
proxyOptions = argv['proxy-options'],
97+
proxyConfig = argv['proxy-config'],
9698
websocket = argv.websocket,
9799
proxyAll = Boolean(argv['proxy-all']),
98100
utc = argv.U || argv.utc,
@@ -127,6 +129,12 @@ if (!argv.s && !argv.silent) {
127129
date, ip, chalk.red(req.method), chalk.red(req.url),
128130
chalk.red(error.status.toString()), chalk.red(error.message)
129131
);
132+
}
133+
else if (req.proxy) {
134+
logger.info(
135+
'[%s] %s "%s" (%s)-> "%s"',
136+
date, ip, chalk.cyan(req.url), chalk.magenta('Proxy'), chalk.cyan(req.proxy.target)
137+
);
130138
} else {
131139
logger.info(
132140
'[%s] %s "%s %s" "%s"',
@@ -174,6 +182,7 @@ function listen(port) {
174182
logFn: logger.request,
175183
proxy: proxy,
176184
proxyOptions: proxyOptions,
185+
proxyConfig: proxyConfig,
177186
proxyAll: proxyAll,
178187
showDotfiles: argv.dotfiles,
179188
mimetypes: argv.mimetypes,
@@ -236,6 +245,38 @@ function listen(port) {
236245
}
237246
}
238247

248+
if (proxyConfig) {
249+
try {
250+
if (fs.existsSync(proxyConfig)) {
251+
proxyConfig = fs.readFileSync(proxyConfig, 'utf8');
252+
}
253+
if (typeof proxyConfig === 'string') {
254+
proxyConfig = JSON.parse(proxyConfig);
255+
}
256+
if (typeof proxyConfig !== 'object') {
257+
throw new Error('Invalid proxy config');
258+
}
259+
}
260+
catch (err) {
261+
logger.info(chalk.red('Error: Invalid proxy config or file'));
262+
process.exit(1);
263+
}
264+
// Proxy file overrides cli config
265+
proxy = undefined;
266+
proxyOptions = undefined;
267+
}
268+
269+
if (proxyAll && proxyConfig) {
270+
logger.info(chalk.red('Error: --proxy-all cannot be used with --proxy-config'));
271+
logger.info(
272+
'%s\n%s\n%s',
273+
chalk.yellow('Hint: Use'),
274+
chalk.cyan('"/**": {\n "target": "your-proxy"\n}'),
275+
chalk.yellow('in the proxy config to achieve the same effect.')
276+
);
277+
process.exit(1);
278+
}
279+
239280
if (proxyAll && !proxy) {
240281
logger.info(chalk.red('Error: --proxy-all requires --proxy to be set'));
241282
process.exit(1);

doc/http-server.1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ Requires \-\-proxy.
123123
.BI \-\-proxy\-options
124124
Pass proxy options using nested dotted objects.
125125

126+
.TP
127+
.BI \-\-proxy\-config
128+
Pass in .json configuration file.
129+
126130
.TP
127131
.BI \-\-user ", " \-\-username " " \fIUSERNAME\fR
128132
Username for basic authentication.

lib/http-server.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ var fs = require('fs'),
77
httpProxy = require('http-proxy'),
88
corser = require('corser'),
99
secureCompare = require('secure-compare');
10+
var { minimatch } = require('minimatch');
1011

1112
//
1213
// Remark: backwards compatibility for previous
@@ -154,6 +155,43 @@ function HttpServer(options) {
154155
});
155156
}
156157

158+
if (typeof options.proxyConfig === 'object') {
159+
var proxy = httpProxy.createProxyServer();
160+
before.push(function (req, res, next) {
161+
for (var key of Object.keys(options.proxyConfig)) {
162+
if (!minimatch(req.url, key)) continue;
163+
req.proxy ??= {};
164+
var matchConfig = options.proxyConfig[key];
165+
166+
if (matchConfig.pathRewrite) {
167+
Object.entries(matchConfig.pathRewrite).forEach(rewrite => {
168+
req.url = req.url.replace(new RegExp(rewrite[0]), rewrite[1]);
169+
});
170+
}
171+
172+
var configEntries = Object.entries(matchConfig).filter(entry => entry[0] !== "pathRewrite");
173+
configEntries.forEach(entry => req.proxy[entry[0]] = entry[1]);
174+
break;
175+
}
176+
177+
if (req.proxy) {
178+
if (options.logFn) {
179+
options.logFn(req, res);
180+
}
181+
proxy.web(req, res, req.proxy, function (err, req, res) {
182+
if (options.logFn) {
183+
options.logFn(req, res, {
184+
message: err.message,
185+
status: res.statusCode });
186+
}
187+
res.emit('next');
188+
});
189+
} else {
190+
next();
191+
}
192+
});
193+
}
194+
157195
if (!proxyAll) {
158196
before.push(httpServerCore({
159197
root: this.root,

0 commit comments

Comments
 (0)