Skip to content

Commit 35ff346

Browse files
authored
Merge pull request #688 from yannickglt/http-proxy-options
add ability to pass proxyOptions
2 parents 8ca74b5 + fe000e2 commit 35ff346

File tree

7 files changed

+178
-2
lines changed

7 files changed

+178
-2
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ This will install `http-server` globally so that it may be run from the command
5858
|`-U` or `--utc` |Use UTC time format in log messages.| |
5959
|`--log-ip` |Enable logging of the client's IP address |`false` |
6060
|`-P` or `--proxy` |Proxies all requests which can't be resolved locally to the given url. e.g.: -P http://someurl.com | |
61+
|`--proxy-options` Pass proxy [options](https://github.com/http-party/node-http-proxy#options) using nested dotted objects. e.g.: --proxy-options.secure false
62+
6163
|`--username` |Username for basic authentication | |
6264
|`--password` |Password for basic authentication | |
6365
|`-S` or `--ssl` |Enable https.| |

bin/http-server

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ if (argv.h || argv.help) {
3939
' --log-ip Enable logging of the client\'s IP address',
4040
'',
4141
' -P --proxy Fallback proxy if the request cannot be resolved. e.g.: http://someurl.com',
42+
' --proxy-options Pass options to proxy using nested dotted objects. e.g.: --proxy-options.secure false',
4243
'',
4344
' --username Username for basic authentication [none]',
4445
' Can also be specified with the env variable NODE_HTTP_SERVER_USERNAME',
@@ -62,10 +63,24 @@ var port = argv.p || argv.port || parseInt(process.env.PORT, 10),
6263
host = argv.a || '0.0.0.0',
6364
ssl = argv.S || argv.ssl,
6465
proxy = argv.P || argv.proxy,
66+
proxyOptions = argv['proxy-options'],
6567
utc = argv.U || argv.utc,
6668
version = argv.v || argv.version,
6769
logger;
6870

71+
var proxyOptionsBooleanProps = [
72+
'ws', 'xfwd', 'secure', 'toProxy', 'prependPath', 'ignorePath', 'changeOrigin',
73+
'preserveHeaderKeyCase', 'followRedirects', 'selfHandleResponse'
74+
];
75+
76+
if (proxyOptions) {
77+
Object.keys(proxyOptions).forEach(function (key) {
78+
if (proxyOptionsBooleanProps.indexOf(key) > -1) {
79+
proxyOptions[key] = proxyOptions[key].toLowerCase() === 'true';
80+
}
81+
});
82+
}
83+
6984
if (!argv.s && !argv.silent) {
7085
logger = {
7186
info: console.log,
@@ -127,6 +142,7 @@ function listen(port) {
127142
ext: argv.e || argv.ext,
128143
logFn: logger.request,
129144
proxy: proxy,
145+
proxyOptions: proxyOptions,
130146
showDotfiles: argv.dotfiles,
131147
mimetypes: argv.mimetypes,
132148
username: argv.username || process.env.NODE_HTTP_SERVER_USERNAME,
@@ -198,7 +214,12 @@ function listen(port) {
198214
}
199215

200216
if (typeof proxy === 'string') {
201-
logger.info('Unhandled requests will be served from: ' + proxy);
217+
if (proxyOptions) {
218+
logger.info('Unhandled requests will be served from: ' + proxy + '. Options: ' + JSON.stringify(proxyOptions));
219+
}
220+
else {
221+
logger.info('Unhandled requests will be served from: ' + proxy);
222+
}
202223
}
203224

204225
logger.info('Hit CTRL-C to stop the server');

doc/http-server.1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ Enable logging of the client IP address.
8585
.BI \-P ", " \-\-proxy
8686
Fallback proxy if the request cannot be resolved.
8787

88+
.TP
89+
.BI \-\-proxy\-options
90+
Pass proxy options using nested dotted objects.
91+
8892
.TP
8993
.BI \-\-username " " \fIUSERNAME\fR
9094
Username for basic authentication.

lib/http-server.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ function HttpServer(options) {
141141
}));
142142

143143
if (typeof options.proxy === 'string') {
144-
var proxy = httpProxy.createProxyServer({});
144+
var proxyOptions = options.proxyOptions || {};
145+
var proxy = httpProxy.createProxyServer(proxyOptions);
145146
before.push(function (req, res) {
146147
proxy.web(req, res, {
147148
target: options.proxy,

test/fixtures/https/agent2-cert.pem

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIEIDCCAggCCQChRDh/XiBF+zANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQGEwJ1
3+
czETMBEGA1UECAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEeMBwGA1UE
4+
AwwVRHVtbXkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDYyMjIwMzEwNFoXDTMyMDIy
5+
OTIwMzEwNFowUDELMAkGA1UEBhMCdXMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAO
6+
BgNVBAcMB1NlYXR0bGUxGjAYBgNVBAMMEWR1bW15LmV4YW1wbGUuY29tMIIBIjAN
7+
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvSQq3d8AeZMTvtqZ13jWCckikyXJ
8+
SACvkGCQUCJqOceESbg6IHdRzQdoccE4P3sbvNsf9BlbdJKM+neCxabqKaU1PPje
9+
4P0tHT57t6yJrMuUh9NxEz3Bgh1srNHVS7saKvwHmcKm79jc+wxlioPmEQvQagjn
10+
y7oTkyLt0sn4LGxBjrcv2JoHOC9f1pxX7l47MaiN0/ctRau7Nr3PFn+pkB4Yf6Z0
11+
VyicVJbaUSz39Qo4HQWl1L2hiBP3CS1oKs2Yk0O1aOCMExWrhZQan+ZgHqL1rhgm
12+
kPpw2/zwwPt5Vf9CSakvHwg198EXuTTXtkzYduuIJAm8yp969iEIiG2xTwIDAQAB
13+
MA0GCSqGSIb3DQEBCwUAA4ICAQBnMSIo+kujkeXPh+iErFBmNtu/7EA+i/QnFPbN
14+
lSLngclYYBJAGQI+DhirJI8ghDi6vmlHB2THewDaOJXEKvC1czE8064wioIcA9HJ
15+
l3QJ3YYOFRctYdSHBU4TWdJbPgkLWDzYP5smjOfw8nDdr4WO/5jh9qRFcFpTFmQf
16+
DyU3xgWLsQnNK3qXLdJjWG75pEhHR+7TGo+Ob/RUho/1RX/P89Ux7/oVbzdKqqFu
17+
SErXAsjEIEFzWOM2uDOt6hrxDF6q+8/zudwQNEo422poEcTT9tDEFxMQ391CzZRi
18+
nozBm4igRn1f5S3YZzLI6VEUns0s76BNy2CzvFWn40DziTqNBExAMfFFj76wiMsX
19+
6fTIdcvkaTBa0S9SZB0vN99qahBdcG17rt4RssMHVRH1Wn7NXMwe476L0yXZ6gO7
20+
Z4uNAPxgaI3BRP75EPfslLutCLZ+BC4Zzu6MY0Salbpfl0Go462EhsKCxvYhE2Dg
21+
T477pICLfETZfA499Fd1tOaIsoLCrILAia/+Yd76uf94MuXUIqykDng/4H7xCc47
22+
BZhNFJiPC6XHaXzN7NYSEUNX9VOwY8ncxKwtP6TXga96PdMUy/p98KIM8RZlDoxB
23+
Xy9dcZBFNn/zrqjW7R0CCWCUriDIFSmEP0wDZ91YYa6BVuJMb5uL/USkTLpjZS4/
24+
HNGvug==
25+
-----END CERTIFICATE-----

test/fixtures/https/agent2-key.pem

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEpQIBAAKCAQEAvSQq3d8AeZMTvtqZ13jWCckikyXJSACvkGCQUCJqOceESbg6
3+
IHdRzQdoccE4P3sbvNsf9BlbdJKM+neCxabqKaU1PPje4P0tHT57t6yJrMuUh9Nx
4+
Ez3Bgh1srNHVS7saKvwHmcKm79jc+wxlioPmEQvQagjny7oTkyLt0sn4LGxBjrcv
5+
2JoHOC9f1pxX7l47MaiN0/ctRau7Nr3PFn+pkB4Yf6Z0VyicVJbaUSz39Qo4HQWl
6+
1L2hiBP3CS1oKs2Yk0O1aOCMExWrhZQan+ZgHqL1rhgmkPpw2/zwwPt5Vf9CSakv
7+
Hwg198EXuTTXtkzYduuIJAm8yp969iEIiG2xTwIDAQABAoIBAGPIw/C/qJF7HYyv
8+
6T+7GTiaa2o0IiehbP3/Y8NTFLWc49a8obXlHTvMr7Zr2I/tE+ojtIzkH9K1SjkN
9+
eelqsNj9tsOPDI6oIvftsflpxkxqLtclnt8m0oMhoObf4OaONDT/N8dP4SBiSdsM
10+
ZDmacnMFx5NZVWiup4sVf2CYexx7qks9FhyN2K5PArCQ4S9LHjFhSJVH4DSEpv7E
11+
Ykbp30rhpqV7wSwjgUsm8ZYvI2NOlmffzLSiPdt3vy2K5Q25S/MVEAicg83rfDgK
12+
6EluHjeygRI1xU6DJ0hU7tnU7zE9KURoHPUycO3BKzZnzUH26AA36I58Pu4fXWw/
13+
Cgmbv2ECgYEA+og9E4ziKCEi3p8gqjIfwTRgWZxDLjEzooB/K0UhEearn/xiX29A
14+
FiSzEHKfCB4uSrw5OENg2ckDs8uy08Qmxx7xFXL7AtufAl5fIYaWa0sNSqCaIk7p
15+
ebbUmPcaYhKiLzIEd1EYEL38sXVZ62wvSVMRSWvEMq44g1qnoRlDa/8CgYEAwUTt
16+
talYNwVmR9ZdkVEWm9ZxirdzoM6NaM6u4Tf34ygptpapdmIFSUhfq4iOiEnRGNg/
17+
tuNqhNCIb3LNpJbhRPEzqN7E7qiF/mp7AcJgbuxLZBm12QuLuJdG3nrisKPFXcY1
18+
lA4A7CFmNgH3E4THFfgwzyDXsBOxVLXleTqn+rECgYEA9up1P6J3dtOJuV2d5P/3
19+
ugRz/X173LfTSxJXw36jZDAy8D/feG19/RT4gnplcKvGNhQiVOhbOOnbw0U8n2fQ
20+
TCmbs+cZqyxnH/+AxNsPvvk+RVHZ93xMsY/XIldP4l65B8jFDA+Zp06IESI2mEeM
21+
pzi+bd1Phh+dRSCA2865W2MCgYEAlxYsgmQ1WyX0dFpHYU+zzfXRYzDQyrhOYc2Z
22+
duVK+yCto1iad7pfCY/zgmRJkI+sT7DV9kJIRjXDQuTLkEyHJF8vFGe6KhxCS8aw
23+
DIsI2g4NTd6vg1J8UryoIUqNpqsQoqNNxUVBQVdG0ReuMGsPO8R/W50AIFz0txVP
24+
o/rP0LECgYEA7e/mOwCnR+ovmS/CAksmos3oIqvkRkXNKpKe513FVmp3TpTU38ex
25+
cBkFNU3hEO31FyrX1hGIKp3N5mHYSQ1lyODHM6teHW0OLWWTwIe8rIGvR2jfRLe0
26+
bbkdj40atYVkfeFmpz9uHHG24CUYxJdPc360jbXTVp4i3q8zqgL5aMY=
27+
-----END RSA PRIVATE KEY-----

test/proxy-options.test.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
const test = require('tap').test
2+
const path = require('path')
3+
const fs = require('fs')
4+
const request = require('request')
5+
const httpServer = require('../lib/http-server')
6+
const promisify = require('util').promisify
7+
8+
const requestAsync = promisify(request)
9+
const fsReadFile = promisify(fs.readFile)
10+
11+
// Prevent errors from being swallowed
12+
process.on('uncaughtException', console.error)
13+
14+
const root = path.join(__dirname, 'fixtures', 'root')
15+
const httpsOpts = {
16+
key: path.join(__dirname, 'fixtures', 'https', 'agent2-key.pem'),
17+
cert: path.join(__dirname, 'fixtures', 'https', 'agent2-cert.pem')
18+
}
19+
20+
// Tests are grouped into those which can run together. The groups are given
21+
// their own port to run on and live inside a Promise. Tests are done when all
22+
// Promise test groups complete.
23+
test('proxy options', (t) => {
24+
new Promise((resolve) => {
25+
const server = httpServer.createServer({
26+
root,
27+
robots: true,
28+
headers: {
29+
'Access-Control-Allow-Origin': '*',
30+
'Access-Control-Allow-Credentials': 'true'
31+
},
32+
cors: true,
33+
corsHeaders: 'X-Test',
34+
ext: true,
35+
brotli: true,
36+
gzip: true
37+
})
38+
server.listen(8080, async () => {
39+
try {
40+
41+
// Another server proxies 8081 to 8080
42+
const proxyServer = httpServer.createServer({
43+
proxy: 'http://localhost:8080',
44+
root: path.join(__dirname, 'fixtures'),
45+
ssl: true,
46+
https: httpsOpts,
47+
proxyOptions: {
48+
secure: false
49+
}
50+
})
51+
52+
await new Promise((resolve) => {
53+
proxyServer.listen(8081, async () => {
54+
try {
55+
// Serve files from proxy root
56+
await requestAsync('https://localhost:8081/root/file', { rejectUnauthorized: false }).then(async (res) => {
57+
t.ok(res)
58+
t.equal(res.statusCode, 200)
59+
60+
// File content matches
61+
const fileData = await fsReadFile(path.join(root, 'file'), 'utf8')
62+
t.equal(res.body.trim(), fileData.trim(), 'proxied root file content matches')
63+
}).catch(err => t.fail(err.toString()))
64+
65+
// Proxy fallback
66+
await requestAsync('https://localhost:8081/file', { rejectUnauthorized: false }).then(async (res) => {
67+
t.ok(res)
68+
t.equal(res.statusCode, 200)
69+
70+
// File content matches
71+
const fileData = await fsReadFile(path.join(root, 'file'), 'utf8')
72+
t.equal(res.body.trim(), fileData.trim(), 'proxy fallback root file content matches')
73+
}).catch(err => t.fail(err.toString()))
74+
} catch (err) {
75+
t.fail(err.toString())
76+
} finally {
77+
proxyServer.close()
78+
resolve()
79+
}
80+
})
81+
})
82+
83+
} catch (err) {
84+
t.fail(err.toString())
85+
} finally {
86+
server.close()
87+
resolve()
88+
}
89+
})
90+
})
91+
.then(() => t.end())
92+
.catch(err => {
93+
t.fail(err.toString())
94+
t.end()
95+
})
96+
})

0 commit comments

Comments
 (0)