Skip to content

Commit

Permalink
add ability to pass proxyOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
yannickglt committed Sep 15, 2021
1 parent 29208b7 commit f52c96a
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 3 deletions.
4 changes: 3 additions & 1 deletion README.md
Expand Up @@ -58,7 +58,9 @@ This will install `http-server` globally so that it may be run from the command
|`-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 | |
|`--username` |Username for basic authentication | |
|`--proxy-options` Pass proxy [options](https://github.com/http-party/node-http-proxy#options) using nested dotted objects. e.g.: --proxy-options.secure false

`--username` |Username for basic authentication | |
|`--password` |Password for basic authentication | |
|`-S` or `--ssl` |Enable https.| |
|`-C` or `--cert` |Path to ssl cert file |`cert.pem` |
Expand Down
23 changes: 22 additions & 1 deletion bin/http-server
Expand Up @@ -39,6 +39,7 @@ if (argv.h || argv.help) {
' --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',
'',
' --username Username for basic authentication [none]',
' Can also be specified with the env variable NODE_HTTP_SERVER_USERNAME',
Expand All @@ -62,10 +63,24 @@ var port = argv.p || argv.port || parseInt(process.env.PORT, 10),
host = argv.a || '0.0.0.0',
ssl = argv.S || argv.ssl,
proxy = argv.P || argv.proxy,
proxyOptions = argv['proxy-options'],
utc = argv.U || argv.utc,
version = argv.v || argv.version,
logger;

var proxyOptionsBooleanProps = [
'ws', 'xfwd', 'secure', 'toProxy', 'prependPath', 'ignorePath', 'changeOrigin',
'preserveHeaderKeyCase', 'followRedirects', 'selfHandleResponse'
];

if (proxyOptions) {
Object.keys(proxyOptions).forEach(function (key) {
if (proxyOptionsBooleanProps.indexOf(key) > -1) {
proxyOptions[key] = proxyOptions[key] === 'true';
}
});
}

if (!argv.s && !argv.silent) {
logger = {
info: console.log,
Expand Down Expand Up @@ -127,6 +142,7 @@ function listen(port) {
ext: argv.e || argv.ext,
logFn: logger.request,
proxy: proxy,
proxyOptions: proxyOptions,
showDotfiles: argv.dotfiles,
mimetypes: argv.mimetypes,
username: argv.username || process.env.NODE_HTTP_SERVER_USERNAME,
Expand Down Expand Up @@ -196,7 +212,12 @@ function listen(port) {
}

if (typeof proxy === 'string') {
logger.info('Unhandled requests will be served from: ' + proxy);
if (proxyOptions) {
logger.info('Unhandled requests will be served from: ' + proxy + '. Options: ' + JSON.stringify(proxyOptions));
}
else {
logger.info('Unhandled requests will be served from: ' + proxy);
}
}

logger.info('Hit CTRL-C to stop the server');
Expand Down
4 changes: 4 additions & 0 deletions doc/http-server.1
Expand Up @@ -85,6 +85,10 @@ Enable logging of the client IP address.
.BI \-P ", " \-\-proxy
Fallback proxy if the request cannot be resolved.

.TP
.BI \-\-proxy\-options
Pass proxy options using nested dotted objects.

.TP
.BI \-\-username " " \fIUSERNAME\fR
Username for basic authentication.
Expand Down
3 changes: 2 additions & 1 deletion lib/http-server.js
Expand Up @@ -141,7 +141,8 @@ function HttpServer(options) {
}));

if (typeof options.proxy === 'string') {
var proxy = httpProxy.createProxyServer({});
var proxyOptions = options.proxyOptions || {};
var proxy = httpProxy.createProxyServer(proxyOptions);
before.push(function (req, res) {
proxy.web(req, res, {
target: options.proxy,
Expand Down
25 changes: 25 additions & 0 deletions test/fixtures/https/agent2-cert.pem
@@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEIDCCAggCCQChRDh/XiBF+zANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQGEwJ1
czETMBEGA1UECAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEeMBwGA1UE
AwwVRHVtbXkgSW50ZXJtZWRpYXRlIENBMB4XDTE4MDYyMjIwMzEwNFoXDTMyMDIy
OTIwMzEwNFowUDELMAkGA1UEBhMCdXMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAO
BgNVBAcMB1NlYXR0bGUxGjAYBgNVBAMMEWR1bW15LmV4YW1wbGUuY29tMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvSQq3d8AeZMTvtqZ13jWCckikyXJ
SACvkGCQUCJqOceESbg6IHdRzQdoccE4P3sbvNsf9BlbdJKM+neCxabqKaU1PPje
4P0tHT57t6yJrMuUh9NxEz3Bgh1srNHVS7saKvwHmcKm79jc+wxlioPmEQvQagjn
y7oTkyLt0sn4LGxBjrcv2JoHOC9f1pxX7l47MaiN0/ctRau7Nr3PFn+pkB4Yf6Z0
VyicVJbaUSz39Qo4HQWl1L2hiBP3CS1oKs2Yk0O1aOCMExWrhZQan+ZgHqL1rhgm
kPpw2/zwwPt5Vf9CSakvHwg198EXuTTXtkzYduuIJAm8yp969iEIiG2xTwIDAQAB
MA0GCSqGSIb3DQEBCwUAA4ICAQBnMSIo+kujkeXPh+iErFBmNtu/7EA+i/QnFPbN
lSLngclYYBJAGQI+DhirJI8ghDi6vmlHB2THewDaOJXEKvC1czE8064wioIcA9HJ
l3QJ3YYOFRctYdSHBU4TWdJbPgkLWDzYP5smjOfw8nDdr4WO/5jh9qRFcFpTFmQf
DyU3xgWLsQnNK3qXLdJjWG75pEhHR+7TGo+Ob/RUho/1RX/P89Ux7/oVbzdKqqFu
SErXAsjEIEFzWOM2uDOt6hrxDF6q+8/zudwQNEo422poEcTT9tDEFxMQ391CzZRi
nozBm4igRn1f5S3YZzLI6VEUns0s76BNy2CzvFWn40DziTqNBExAMfFFj76wiMsX
6fTIdcvkaTBa0S9SZB0vN99qahBdcG17rt4RssMHVRH1Wn7NXMwe476L0yXZ6gO7
Z4uNAPxgaI3BRP75EPfslLutCLZ+BC4Zzu6MY0Salbpfl0Go462EhsKCxvYhE2Dg
T477pICLfETZfA499Fd1tOaIsoLCrILAia/+Yd76uf94MuXUIqykDng/4H7xCc47
BZhNFJiPC6XHaXzN7NYSEUNX9VOwY8ncxKwtP6TXga96PdMUy/p98KIM8RZlDoxB
Xy9dcZBFNn/zrqjW7R0CCWCUriDIFSmEP0wDZ91YYa6BVuJMb5uL/USkTLpjZS4/
HNGvug==
-----END CERTIFICATE-----
27 changes: 27 additions & 0 deletions test/fixtures/https/agent2-key.pem
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAvSQq3d8AeZMTvtqZ13jWCckikyXJSACvkGCQUCJqOceESbg6
IHdRzQdoccE4P3sbvNsf9BlbdJKM+neCxabqKaU1PPje4P0tHT57t6yJrMuUh9Nx
Ez3Bgh1srNHVS7saKvwHmcKm79jc+wxlioPmEQvQagjny7oTkyLt0sn4LGxBjrcv
2JoHOC9f1pxX7l47MaiN0/ctRau7Nr3PFn+pkB4Yf6Z0VyicVJbaUSz39Qo4HQWl
1L2hiBP3CS1oKs2Yk0O1aOCMExWrhZQan+ZgHqL1rhgmkPpw2/zwwPt5Vf9CSakv
Hwg198EXuTTXtkzYduuIJAm8yp969iEIiG2xTwIDAQABAoIBAGPIw/C/qJF7HYyv
6T+7GTiaa2o0IiehbP3/Y8NTFLWc49a8obXlHTvMr7Zr2I/tE+ojtIzkH9K1SjkN
eelqsNj9tsOPDI6oIvftsflpxkxqLtclnt8m0oMhoObf4OaONDT/N8dP4SBiSdsM
ZDmacnMFx5NZVWiup4sVf2CYexx7qks9FhyN2K5PArCQ4S9LHjFhSJVH4DSEpv7E
Ykbp30rhpqV7wSwjgUsm8ZYvI2NOlmffzLSiPdt3vy2K5Q25S/MVEAicg83rfDgK
6EluHjeygRI1xU6DJ0hU7tnU7zE9KURoHPUycO3BKzZnzUH26AA36I58Pu4fXWw/
Cgmbv2ECgYEA+og9E4ziKCEi3p8gqjIfwTRgWZxDLjEzooB/K0UhEearn/xiX29A
FiSzEHKfCB4uSrw5OENg2ckDs8uy08Qmxx7xFXL7AtufAl5fIYaWa0sNSqCaIk7p
ebbUmPcaYhKiLzIEd1EYEL38sXVZ62wvSVMRSWvEMq44g1qnoRlDa/8CgYEAwUTt
talYNwVmR9ZdkVEWm9ZxirdzoM6NaM6u4Tf34ygptpapdmIFSUhfq4iOiEnRGNg/
tuNqhNCIb3LNpJbhRPEzqN7E7qiF/mp7AcJgbuxLZBm12QuLuJdG3nrisKPFXcY1
lA4A7CFmNgH3E4THFfgwzyDXsBOxVLXleTqn+rECgYEA9up1P6J3dtOJuV2d5P/3
ugRz/X173LfTSxJXw36jZDAy8D/feG19/RT4gnplcKvGNhQiVOhbOOnbw0U8n2fQ
TCmbs+cZqyxnH/+AxNsPvvk+RVHZ93xMsY/XIldP4l65B8jFDA+Zp06IESI2mEeM
pzi+bd1Phh+dRSCA2865W2MCgYEAlxYsgmQ1WyX0dFpHYU+zzfXRYzDQyrhOYc2Z
duVK+yCto1iad7pfCY/zgmRJkI+sT7DV9kJIRjXDQuTLkEyHJF8vFGe6KhxCS8aw
DIsI2g4NTd6vg1J8UryoIUqNpqsQoqNNxUVBQVdG0ReuMGsPO8R/W50AIFz0txVP
o/rP0LECgYEA7e/mOwCnR+ovmS/CAksmos3oIqvkRkXNKpKe513FVmp3TpTU38ex
cBkFNU3hEO31FyrX1hGIKp3N5mHYSQ1lyODHM6teHW0OLWWTwIe8rIGvR2jfRLe0
bbkdj40atYVkfeFmpz9uHHG24CUYxJdPc360jbXTVp4i3q8zqgL5aMY=
-----END RSA PRIVATE KEY-----
96 changes: 96 additions & 0 deletions test/proxy-options.test.js
@@ -0,0 +1,96 @@
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')
}

// 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 options', (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
})
server.listen(8080, async () => {
try {

// Another server proxies 8081 to 8080
const proxyServer = httpServer.createServer({
proxy: 'http://localhost:8080',
root: path.join(__dirname, 'fixtures'),
ssl: true,
https: httpsOpts,
proxyOptions: {
secure: false
}
})

await new Promise((resolve) => {
proxyServer.listen(8081, async () => {
try {
// Serve files from proxy root
await requestAsync('https://localhost:8081/root/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 root file content matches')
}).catch(err => t.fail(err.toString()))

// Proxy fallback
await requestAsync('https://localhost:8081/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(), 'proxy fallback root 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()
})
})

0 comments on commit f52c96a

Please sign in to comment.