front-express-http-proxy
Advanced tools
Comparing version 0.11.0-2 to 1.6.2-1
373
index.js
'use strict'; | ||
// * Breaks proxying into a series of discrete steps, many of which can be swapped out by authors. | ||
// * Uses Promises to support async. | ||
// * Uses a quasi-Global called Container to tidy up the argument passing between the major work-flow steps. | ||
var ScopeContainer = require('./lib/scopeContainer'); | ||
var assert = require('assert'); | ||
var url = require('url'); | ||
var http = require('http'); | ||
var https = require('https'); | ||
var getRawBody = require('raw-body'); | ||
var zlib = require('zlib'); | ||
var debug = require('debug')('express-http-proxy'); | ||
var buildProxyReq = require('./app/steps/buildProxyReq'); | ||
var copyProxyResHeadersToUserRes = require('./app/steps/copyProxyResHeadersToUserRes'); | ||
var decorateProxyReqBody = require('./app/steps/decorateProxyReqBody'); | ||
var decorateProxyReqOpts = require('./app/steps/decorateProxyReqOpts'); | ||
var decorateUserRes = require('./app/steps/decorateUserRes'); | ||
var decorateUserResHeaders = require('./app/steps/decorateUserResHeaders'); | ||
var filterUserRequest = require('./app/steps/filterUserRequest'); | ||
var handleProxyErrors = require('./app/steps/handleProxyErrors'); | ||
var maybeSkipToNextHandler = require('./app/steps/maybeSkipToNextHandler'); | ||
var prepareProxyReq = require('./app/steps/prepareProxyReq'); | ||
var resolveProxyHost = require('./app/steps/resolveProxyHost'); | ||
var resolveProxyReqPath = require('./app/steps/resolveProxyReqPath'); | ||
var sendProxyRequest = require('./app/steps/sendProxyRequest'); | ||
var sendUserRes = require('./app/steps/sendUserRes'); | ||
function unset(val) { | ||
return (typeof val === 'undefined' || val === '' || val === null); | ||
} | ||
module.exports = function proxy(host, options) { | ||
module.exports = function proxy(host, userOptions) { | ||
assert(host, 'Host should not be empty'); | ||
options = options || {}; | ||
var parsedHost; | ||
/** | ||
* Function :: intercept(targetResponse, data, res, req, function(err, json, sent)); | ||
*/ | ||
var intercept = options.intercept; | ||
var decorateRequest = options.decorateRequest; | ||
var forwardPath = options.forwardPath || defaultForwardPath; | ||
var resolveProxyPathAsync = options.forwardPathAsync || defaultForwardPathAsync(forwardPath); | ||
var filter = options.filter || defaultFilter; | ||
var limit = options.limit || '1mb'; | ||
var preserveReqSession = options.preserveReqSession; | ||
var memoizeHost = unset(options.memoizeHost) ? true : options.memoizeHost; | ||
var rejectUnauthorized = !options.skipCertificateValidation; | ||
return function handleProxy(req, res, next) { | ||
if (!filter(req, res)) { return next(); } | ||
var resolvePath = resolveProxyPathAsync(req, res); | ||
var parseBody = maybeParseBody(req, limit); | ||
var prepareRequest = Promise.all([resolvePath, parseBody]); | ||
prepareRequest.then(function(results) { | ||
var path = results[0]; | ||
var bodyContent = results[1]; | ||
sendProxyRequest(req, res, next, path, bodyContent); | ||
}); | ||
}; | ||
debug('[start proxy] ' + req.path); | ||
var container = new ScopeContainer(req, res, next, host, userOptions); | ||
function sendProxyRequest(req, res, next, path, bodyContent) { | ||
parsedHost = (memoizeHost && parsedHost) ? parsedHost : parseHost(host, req, options); | ||
filterUserRequest(container) | ||
.then(buildProxyReq) | ||
.then(resolveProxyHost) | ||
.then(decorateProxyReqOpts) | ||
.then(resolveProxyReqPath) | ||
.then(decorateProxyReqBody) | ||
.then(prepareProxyReq) | ||
.then(sendProxyRequest) | ||
.then(maybeSkipToNextHandler) | ||
.then(copyProxyResHeadersToUserRes) | ||
.then(decorateUserResHeaders) | ||
.then(decorateUserRes) | ||
.then(sendUserRes) | ||
.catch(function (err) { | ||
// I sometimes reject without an error to shortcircuit the remaining | ||
// steps and return control to the host application. | ||
var reqOpt = { | ||
hostname: parsedHost.host, | ||
port: options.port || parsedHost.port, | ||
headers: reqHeaders(req, options), | ||
method: req.method, | ||
path: path, | ||
bodyContent: bodyContent, | ||
params: req.params, | ||
rejectUnauthorized | ||
}; | ||
if (preserveReqSession) { | ||
reqOpt.session = req.session; | ||
} | ||
if (decorateRequest) { | ||
reqOpt = decorateRequest(reqOpt, req) || reqOpt; | ||
} | ||
bodyContent = reqOpt.bodyContent; | ||
delete reqOpt.bodyContent; | ||
delete reqOpt.params; | ||
bodyContent = options.reqAsBuffer ? | ||
asBuffer(bodyContent, options) : | ||
asBufferOrString(bodyContent); | ||
reqOpt.headers['content-length'] = getContentLength(bodyContent); | ||
if (bodyEncoding(options)) { | ||
reqOpt.headers['Accept-Charset'] = bodyEncoding(options); | ||
} | ||
function postIntercept(res, next, rspData) { | ||
return function(err, rspd, sent) { | ||
if (err) { | ||
return next(err); | ||
} | ||
rspd = asBuffer(rspd, options); | ||
rspd = maybeZipResponse(rspd, res); | ||
if (!Buffer.isBuffer(rspd)) { | ||
next(new Error('intercept should return string or' + | ||
'buffer as data')); | ||
} | ||
if (!res.headersSent) { | ||
res.set('content-length', rspd.length); | ||
} else if (rspd.length !== rspData.length) { | ||
var error = '"Content-Length" is already sent,' + | ||
'the length of response data can not be changed'; | ||
next(new Error(error)); | ||
} | ||
if (!sent) { | ||
res.send(rspd); | ||
} | ||
}; | ||
} | ||
var proxyTargetRequest = parsedHost.module.request(reqOpt, function(rsp) { | ||
var chunks = []; | ||
rsp.on('data', function(chunk) { | ||
chunks.push(chunk); | ||
}); | ||
rsp.on('end', function() { | ||
var rspData = Buffer.concat(chunks, chunkLength(chunks)); | ||
if (intercept) { | ||
rspData = maybeUnzipResponse(rspData, res); | ||
var callback = postIntercept(res, next, rspData); | ||
intercept(rsp, rspData, req, res, callback); | ||
var resolver = (container.options.proxyErrorHandler) ? | ||
container.options.proxyErrorHandler : | ||
handleProxyErrors; | ||
resolver(err, res, next); | ||
} else { | ||
// see issue https://github.com/villadora/express-http-proxy/issues/104 | ||
// Not sure how to automate tests on this line, so be careful when changing. | ||
if (!res.headersSent) { | ||
res.send(rspData); | ||
} | ||
next(); | ||
} | ||
}); | ||
rsp.on('error', function(err) { | ||
next(err); | ||
}); | ||
if (!res.headersSent) { | ||
res.status(rsp.statusCode); | ||
Object.keys(rsp.headers) | ||
.filter(function(item) { return item !== 'transfer-encoding'; }) | ||
.forEach(function(item) { | ||
res.set(item, rsp.headers[item]); | ||
}); | ||
} | ||
}); | ||
proxyTargetRequest.on('socket', function(socket) { | ||
if (options.timeout) { | ||
socket.setTimeout(options.timeout, function() { | ||
proxyTargetRequest.abort(); | ||
}); | ||
} | ||
}); | ||
proxyTargetRequest.on('error', function(err) { | ||
if (err.code === 'ECONNRESET') { | ||
res.setHeader('X-Timout-Reason', | ||
'express-http-proxy timed out your request after ' + | ||
options.timeout + 'ms.'); | ||
res.writeHead(504, {'Content-Type': 'text/plain'}); | ||
res.end(); | ||
} else { | ||
next(err); | ||
} | ||
}); | ||
if (bodyContent.length) { | ||
proxyTargetRequest.write(bodyContent); | ||
} | ||
proxyTargetRequest.end(); | ||
req.on('aborted', function() { | ||
proxyTargetRequest.abort(); | ||
}); | ||
} | ||
}; | ||
}; | ||
function extend(obj, source, skips) { | ||
if (!source) { | ||
return obj; | ||
} | ||
for (var prop in source) { | ||
if (!skips || skips.indexOf(prop) === -1) { | ||
obj[prop] = source[prop]; | ||
} | ||
} | ||
return obj; | ||
} | ||
function parseHost(host, req, options) { | ||
host = (typeof host === 'function') ? host(req) : host.toString(); | ||
if (!host) { | ||
return new Error('Empty host parameter'); | ||
} | ||
if (!/http(s)?:\/\//.test(host)) { | ||
host = 'http://' + host; | ||
} | ||
var parsed = url.parse(host); | ||
if (!parsed.hostname) { | ||
return new Error('Unable to parse hostname, possibly missing protocol://?'); | ||
} | ||
var ishttps = options.https || parsed.protocol === 'https:'; | ||
return { | ||
host: parsed.hostname, | ||
port: parsed.port || (ishttps ? 443 : 80), | ||
module: ishttps ? https : http, | ||
}; | ||
} | ||
function reqHeaders(req, options) { | ||
var headers = options.headers || {}; | ||
var skipHdrs = [ 'connection', 'content-length' ]; | ||
if (!options.preserveHostHdr) { | ||
skipHdrs.push('host'); | ||
} | ||
var hds = extend(headers, req.headers, skipHdrs); | ||
hds.connection = 'close'; | ||
return hds; | ||
} | ||
function defaultFilter() { | ||
// No-op version of filter. Allows everything! | ||
return true; | ||
} | ||
function defaultForwardPath(req) { | ||
return url.parse(req.url).path; | ||
} | ||
function bodyEncoding(options) { | ||
/* For reqBodyEncoding, these is a meaningful difference between null and | ||
* undefined. null should be passed forward as the value of reqBodyEncoding, | ||
* and undefined should result in utf-8. | ||
*/ | ||
return options.reqBodyEncoding !== undefined ? options.reqBodyEncoding: 'utf-8'; | ||
} | ||
function chunkLength(chunks) { | ||
return chunks.reduce(function(len, buf) { | ||
return len + buf.length; | ||
}, 0); | ||
} | ||
function defaultForwardPathAsync(forwardPath) { | ||
return function(req, res) { | ||
return new Promise(function(resolve) { | ||
resolve(forwardPath(req, res)); | ||
}); | ||
}; | ||
} | ||
function asBuffer(body, options) { | ||
var ret; | ||
if (Buffer.isBuffer(body)) { | ||
ret = body; | ||
} else if (typeof body === 'object') { | ||
ret = new Buffer(JSON.stringify(body), bodyEncoding(options)); | ||
} else if (typeof body === 'string') { | ||
ret = new Buffer(body, bodyEncoding(options)); | ||
} | ||
return ret; | ||
} | ||
function asBufferOrString(body) { | ||
var ret; | ||
if (Buffer.isBuffer(body)) { | ||
ret = body; | ||
} else if (typeof body === 'object') { | ||
ret = JSON.stringify(body); | ||
} else if (typeof body === 'string') { | ||
ret = body; | ||
} | ||
return ret; | ||
} | ||
function getContentLength(body) { | ||
var result; | ||
if (Buffer.isBuffer(body)) { // Buffer | ||
result = body.length; | ||
} else if (typeof body === 'string') { | ||
result = Buffer.byteLength(body); | ||
} | ||
return result; | ||
} | ||
function isResGzipped(res) { | ||
return res._headers['content-encoding'] === 'gzip'; | ||
} | ||
function zipOrUnzip(method) { | ||
return function(rspData, res) { | ||
return (isResGzipped(res)) ? zlib[method](rspData) : rspData; | ||
}; | ||
} | ||
function maybeParseBody(req, limit) { | ||
var promise; | ||
if (req._body && req.body) { | ||
promise = new Promise(function(resolve) { | ||
resolve(req.body); | ||
}); | ||
} else { | ||
// Returns a promise if no callback specified and global Promise exists. | ||
promise = getRawBody(req, { | ||
length: req.headers['content-length'], | ||
limit: limit, | ||
}); | ||
} | ||
return promise; | ||
} | ||
var maybeUnzipResponse = zipOrUnzip('gunzipSync'); | ||
var maybeZipResponse = zipOrUnzip('gzipSync'); |
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var app = require('express')(); | ||
@@ -2,0 +4,0 @@ |
{ | ||
"name": "front-express-http-proxy", | ||
"version": "0.11.0-2", | ||
"version": "1.6.2-1", | ||
"description": "http proxy middleware for express", | ||
"engines": { | ||
"node": ">=4.0.0" | ||
"node": ">=6.0.0" | ||
}, | ||
"engineStrict": true, | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "npm -s run mocha && npm run -s lint && npm run -s jscs", | ||
"test:debug": "mocha debug -R spec test/*.js", | ||
"mocha": "mocha -R spec test/*.js", | ||
"lint": "jshint index.js test/*.js", | ||
"jscs": "jscs index.js test/*.js" | ||
"test": "npm -s run mocha && npm run -s lint", | ||
"test:debug": "mocha debug -R spec test --recursive --exit", | ||
"mocha": "mocha -R spec test --recursive --exit", | ||
"lint": "eslint index.js **/*js" | ||
}, | ||
@@ -33,12 +31,15 @@ "repository": { | ||
"devDependencies": { | ||
"body-parser": "^1.15.2", | ||
"express": "^4.3.1", | ||
"jscs": "^3.0.7", | ||
"jshint": "^2.5.5", | ||
"mocha": "^2.1.0", | ||
"supertest": "^1.2.0" | ||
"body-parser": "^1.17.2", | ||
"chai": "^4.1.2", | ||
"cookie-parser": "^1.4.3", | ||
"eslint": "^4.19.1", | ||
"express": "^4.15.4", | ||
"mocha": "^8.0.1", | ||
"nock": "^10.0.6", | ||
"supertest": "^3.4.2" | ||
}, | ||
"dependencies": { | ||
"es6-promise": "^3.2.1", | ||
"raw-body": "^2.1.7" | ||
"debug": "^3.0.1", | ||
"es6-promise": "^4.1.1", | ||
"raw-body": "^2.3.0" | ||
}, | ||
@@ -45,0 +46,0 @@ "contributors": [ |
474
README.md
@@ -1,5 +0,6 @@ | ||
# express-http-proxy [![NPM version](https://badge.fury.io/js/express-http-proxy.svg)](http://badge.fury.io/js/express-http-proxy) [![Build Status](https://travis-ci.org/villadora/express-http-proxy.svg?branch=master)](https://travis-ci.org/villadora/express-http-proxy) [![Dependency Status](https://gemnasium.com/villadora/express-http-proxy.svg)](https://gemnasium.com/villadora/express-http-proxy) | ||
# express-http-proxy [![NPM version](https://badge.fury.io/js/express-http-proxy.svg)](http://badge.fury.io/js/express-http-proxy) [![Build Status](https://travis-ci.org/villadora/express-http-proxy.svg?branch=master)](https://travis-ci.org/villadora/express-http-proxy) | ||
Express proxy middleware to forward request to another host and pass response back | ||
Express middleware to proxy request to another host and pass response back to original caller. | ||
## Install | ||
@@ -16,2 +17,3 @@ | ||
### Example: | ||
To proxy URLS starting with '/proxy' to the host 'www.google.com': | ||
@@ -21,3 +23,2 @@ | ||
var proxy = require('express-http-proxy'); | ||
var app = require('express')(); | ||
@@ -28,32 +29,100 @@ | ||
### Options | ||
### Streaming | ||
Proxy requests and user responses are piped/streamed/chunked by default. | ||
If you define a response modifier (userResDecorator, userResHeaderDecorator), | ||
or need to inspect the response before continuing (maybeSkipToNext), streaming | ||
is disabled, and the request and response are buffered. | ||
This can cause performance issues with large payloads. | ||
#### forwardPath | ||
### Promises | ||
The ```forwardPath``` option allows you to modify the path prior to proxying the request. | ||
Many function hooks support Promises. | ||
If any Promise is rejected, ```next(x)``` is called in the hosting application, where ```x``` is whatever you pass to ```Promise.reject```; | ||
e.g. | ||
```js | ||
var proxy = require('express-http-proxy'); | ||
app.use(proxy('/reject-promise', { | ||
proxyReqOptDecorator: function() { | ||
return Promise.reject('An arbitrary rejection message.'); | ||
} | ||
})); | ||
``` | ||
var app = require('express')(); | ||
eventually calls | ||
app.use('/proxy', proxy('www.google.com', { | ||
forwardPath: function(req, res) { | ||
return require('url').parse(req.url).path; | ||
} | ||
})); | ||
```js | ||
next('An arbitrary rejection messasage'); | ||
``` | ||
#### forwardPathAsync | ||
The ```forwardPathAsync``` options allows you to modify the path asyncronously prior to proxying the request, using Promises. | ||
### Host | ||
The first positional argument is for the proxy host; in many cases you will use a static string here, eg. | ||
```js | ||
app.use(proxy('httpbin.org', { | ||
forwardPathAsync: function() { | ||
return new Promise(function(resolve, reject) { | ||
// ... | ||
// eventually | ||
resolve( /* your resolved forwardPath as string */ ) | ||
app.use('/', proxy('http://google.com')) | ||
``` | ||
However, this argument can also be a function, and that function can be | ||
memoized or computed on each request, based on the setting of | ||
```memoizeHost```. | ||
```js | ||
function selectProxyHost() { | ||
return (new Date() % 2) ? 'http://google.com' : 'http://altavista.com'; | ||
} | ||
app.use('/', proxy(selectProxyHost)); | ||
``` | ||
### Middleware mixing | ||
If you use 'https://www.npmjs.com/package/body-parser' you should declare it AFTER the proxy configuration, otherwise original 'POST' body could be modified and not proxied correctly. | ||
``` | ||
app.use('/proxy', 'http://foo.bar.com') | ||
// Declare use of body-parser AFTER the use of proxy | ||
app.use(bodyParser.foo(bar)) | ||
app.use('/api', ...) | ||
``` | ||
### Options | ||
#### proxyReqPathResolver (supports Promises) | ||
Note: In ```express-http-proxy```, the ```path``` is considered the portion of | ||
the url after the host, and including all query params. E.g. for the URL | ||
```http://smoogle.com/search/path?q=123```; the path is | ||
```/search/path?q=123```. Authors using this resolver must also handle the query parameter portion of the path. | ||
Provide a proxyReqPathResolver function if you'd like to | ||
operate on the path before issuing the proxy request. Use a Promise for async | ||
operations. | ||
```js | ||
app.use(proxy('localhost:12345', { | ||
proxyReqPathResolver: function (req) { | ||
var parts = req.url.split('?'); | ||
var queryString = parts[1]; | ||
var updatedPath = parts[0].replace(/test/, 'tent'); | ||
return updatedPath + (queryString ? '?' + queryString : ''); | ||
} | ||
})); | ||
``` | ||
Promise form | ||
```js | ||
app.use('/proxy', proxy('localhost:12345', { | ||
proxyReqPathResolver: function(req) { | ||
return new Promise(function (resolve, reject) { | ||
setTimeout(function () { // simulate async | ||
var parts = req.url.split('?'); | ||
var queryString = parts[1]; | ||
var updatedPath = parts[0].replace(/test/, 'tent'); | ||
var resolvedPathValue = updatedPath + (queryString ? '?' + queryString : ''); | ||
resolve(resolvedPathValue); | ||
}, 200); | ||
}); | ||
@@ -64,6 +133,18 @@ } | ||
#### filter | ||
#### forwardPath | ||
The ```filter``` option can be used to limit what requests are proxied. For example, if you only want to proxy get request | ||
DEPRECATED. See proxyReqPathResolver | ||
#### forwardPathAsync | ||
DEPRECATED. See proxyReqPathResolver | ||
#### filter (supports Promises) | ||
The ```filter``` option can be used to limit what requests are proxied. Return | ||
```true``` to continue to execute proxy; return false-y to skip proxy for this | ||
request. | ||
For example, if you only want to proxy get request: | ||
```js | ||
@@ -73,5 +154,2 @@ app.use('/proxy', proxy('www.google.com', { | ||
return req.method == 'GET'; | ||
}, | ||
forwardPath: function(req, res) { | ||
return require('url').parse(req.url).path; | ||
} | ||
@@ -81,12 +159,28 @@ })); | ||
#### intercept | ||
Promise form: | ||
You can intercept the response before sending it back to the client. | ||
```js | ||
app.use(proxy('localhost:12346', { | ||
filter: function (req, res) { | ||
return new Promise(function (resolve) { | ||
resolve(req.method === 'GET'); | ||
}); | ||
} | ||
})); | ||
``` | ||
Note that in the previous example, `resolve(false)` will execute the happy path | ||
for filter here (skipping the rest of the proxy, and calling `next()`). | ||
`reject()` will also skip the rest of proxy and call `next()`. | ||
#### userResDecorator (was: intercept) (supports Promise) | ||
You can modify the proxy's response before sending it to the client. | ||
```js | ||
app.use('/proxy', proxy('www.google.com', { | ||
intercept: function(rsp, data, req, res, callback) { | ||
// rsp - original response from the target | ||
data = JSON.parse(data.toString('utf8')); | ||
callback(null, JSON.stringify(data)); | ||
userResDecorator: function(proxyRes, proxyResData, userReq, userRes) { | ||
data = JSON.parse(proxyResData.toString('utf8')); | ||
data.newProperty = 'exciting data'; | ||
return JSON.stringify(data); | ||
} | ||
@@ -96,2 +190,35 @@ })); | ||
```js | ||
app.use(proxy('httpbin.org', { | ||
userResDecorator: function(proxyRes, proxyResData) { | ||
return new Promise(function(resolve) { | ||
proxyResData.funkyMessage = 'oi io oo ii'; | ||
setTimeout(function() { | ||
resolve(proxyResData); | ||
}, 200); | ||
}); | ||
} | ||
})); | ||
``` | ||
##### 304 - Not Modified | ||
When your proxied service returns 304, not modified, this step will be skipped, since there is no body to decorate. | ||
##### exploiting references | ||
The intent is that this be used to modify the proxy response data only. | ||
Note: | ||
The other arguments (proxyRes, userReq, userRes) are passed by reference, so | ||
you *can* currently exploit this to modify either response's headers, for | ||
instance, but this is not a reliable interface. I expect to close this | ||
exploit in a future release, while providing an additional hook for mutating | ||
the userRes before sending. | ||
##### gzip responses | ||
If your proxy response is gzipped, this program will automatically unzip | ||
it before passing to your function, then zip it back up before piping it to the | ||
user response. There is currently no way to short-circuit this behavior. | ||
#### limit | ||
@@ -114,3 +241,3 @@ | ||
When true, the ```host``` argument will be parsed on first request, and | ||
memoized for all subsequent requests. | ||
memoized for subsequent requests. | ||
@@ -139,25 +266,132 @@ When ```false```, ```host``` argument will be parsed on each request. | ||
### userResHeaderDecorator | ||
When a `userResHeaderDecorator` is defined, the return of this method will replace (rather than be merged on to) the headers for `userRes`. | ||
```js | ||
app.use('/proxy', proxy('www.google.com', { | ||
userResHeaderDecorator(headers, userReq, userRes, proxyReq, proxyRes) { | ||
// recieves an Object of headers, returns an Object of headers. | ||
return headers; | ||
} | ||
})); | ||
``` | ||
#### decorateRequest | ||
You can change the request options before it is sent to the target. | ||
REMOVED: See ```proxyReqOptDecorator``` and ```proxyReqBodyDecorator```. | ||
#### skipToNextHandlerFilter(supports Promise form) | ||
(experimental: this interface may change in upcoming versions) | ||
Allows you to inspect the proxy response, and decide if you want to continue processing (via express-http-proxy) or call ```next()``` to return control to express. | ||
```js | ||
app.use('/proxy', proxy('www.google.com', { | ||
decorateRequest: function(proxyReq, originalReq) { | ||
skipToNextHandlerFilter: function(proxyRes) { | ||
return proxyRes.statusCode === 404; | ||
} | ||
})); | ||
``` | ||
### proxyErrorHandler | ||
By default, ```express-http-proxy``` will pass any errors except ECONNRESET to | ||
next, so that your application can handle or react to them, or just drop | ||
through to your default error handling. ECONNRESET errors are immediately | ||
returned to the user for historical reasons. | ||
If you would like to modify this behavior, you can provide your own ```proxyErrorHandler```. | ||
```js | ||
// Example of skipping all error handling. | ||
app.use(proxy('localhost:12346', { | ||
proxyErrorHandler: function(err, res, next) { | ||
next(err); | ||
} | ||
})); | ||
// Example of rolling your own | ||
app.use(proxy('localhost:12346', { | ||
proxyErrorHandler: function(err, res, next) { | ||
switch (err && err.code) { | ||
case 'ECONNRESET': { return res.status(405).send('504 became 405'); } | ||
case 'ECONNREFUSED': { return res.status(200).send('gotcher back'); } | ||
default: { next(err); } | ||
} | ||
}})); | ||
``` | ||
#### proxyReqOptDecorator (supports Promise form) | ||
You can override most request options before issuing the proxyRequest. | ||
proxyReqOpt represents the options argument passed to the (http|https).request | ||
module. | ||
NOTE: req.path cannot be changed via this method; use ```proxyReqPathResolver``` instead. (see https://github.com/villadora/express-http-proxy/issues/243) | ||
```js | ||
app.use('/proxy', proxy('www.google.com', { | ||
proxyReqOptDecorator: function(proxyReqOpts, srcReq) { | ||
// you can update headers | ||
proxyReq.headers['Content-Type'] = 'text/html'; | ||
proxyReqOpts.headers['Content-Type'] = 'text/html'; | ||
// you can change the method | ||
proxyReq.method = 'GET'; | ||
// you can munge the bodyContent. | ||
proxyReq.bodyContent = proxyReq.bodyContent.replace(/losing/, 'winning!'); | ||
return proxyReq; | ||
proxyReqOpts.method = 'GET'; | ||
return proxyReqOpts; | ||
} | ||
})); | ||
``` | ||
You can use a Promise for async style. | ||
```js | ||
app.use('/proxy', proxy('www.google.com', { | ||
proxyReqOptDecorator: function(proxyReqOpts, srcReq) { | ||
return new Promise(function(resolve, reject) { | ||
proxyReqOpts.headers['Content-Type'] = 'text/html'; | ||
resolve(proxyReqOpts); | ||
}) | ||
} | ||
})); | ||
``` | ||
#### proxyReqBodyDecorator (supports Promise form) | ||
You can mutate the body content before sending the proxyRequest. | ||
```js | ||
app.use('/proxy', proxy('www.google.com', { | ||
proxyReqBodyDecorator: function(bodyContent, srcReq) { | ||
return bodyContent.split('').reverse().join(''); | ||
} | ||
})); | ||
``` | ||
You can use a Promise for async style. | ||
```js | ||
app.use('/proxy', proxy('www.google.com', { | ||
proxyReqBodyDecorator: function(proxyReq, srcReq) { | ||
return new Promise(function(resolve, reject) { | ||
http.get('http://dev/null', function (err, res) { | ||
if (err) { reject(err); } | ||
resolve(res); | ||
}); | ||
}) | ||
} | ||
})); | ||
``` | ||
#### https | ||
Normally, your proxy request will be made on the same protocol as the original | ||
request. If you'd like to force the proxy request to be https, use this | ||
Normally, your proxy request will be made on the same protocol as the `host` | ||
parameter. If you'd like to force the proxy request to be https, use this | ||
option. | ||
@@ -181,3 +415,35 @@ | ||
#### parseReqBody | ||
The ```parseReqBody``` option allows you to control parsing the request body. | ||
For example, disabling body parsing is useful for large uploads where it would be inefficient | ||
to hold the data in memory. | ||
##### Note: this setting is required for binary uploads. A future version of this library may handle this for you. | ||
This defaults to true in order to preserve legacy behavior. | ||
When false, no action will be taken on the body and accordingly ```req.body``` will no longer be set. | ||
Note that setting this to false overrides ```reqAsBuffer``` and ```reqBodyEncoding``` below. | ||
```js | ||
app.use('/proxy', proxy('www.google.com', { | ||
parseReqBody: false | ||
})); | ||
``` | ||
You can use function instead of boolean value for dynamic value generation based on request | ||
```js | ||
app.use('/proxy', proxy('www.google.com', { | ||
parseReqBody: function (proxyReq) { | ||
if (proxyReq.headers["content-type"] === "application/json") { | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
} | ||
})); | ||
``` | ||
#### reqAsBuffer | ||
@@ -194,2 +460,4 @@ | ||
Ignored if ```parseReqBody``` is set to false. | ||
```js | ||
@@ -210,2 +478,4 @@ app.use('/proxy', proxy('www.google.com', { | ||
Ignored if ```parseReqBody``` is set to false. | ||
```js | ||
@@ -217,6 +487,7 @@ app.use('/post', proxy('httpbin.org', { | ||
#### timeout | ||
By default, node does not express a timeout on connections. Use timeout option to impose a specific timeout. Timed-out requests will respond with 504 status code and a X-Timeout-Reason header. | ||
By default, node does not express a timeout on connections. | ||
Use timeout option to impose a specific timeout. | ||
Timed-out requests will respond with 504 status code and a X-Timeout-Reason header. | ||
@@ -229,28 +500,102 @@ ```js | ||
## Trace debugging | ||
## Questions | ||
The node-debug module is used to provide a trace debugging capability. | ||
### Q: Can it support https proxy? | ||
``` | ||
DEBUG=express-http-proxy npm run YOUR_PROGRAM | ||
DEBUG=express-http-proxy npm run YOUR_PROGRAM | grep 'express-http-proxy' # to filter down to just these messages | ||
``` | ||
The library will use https if the provided path has 'https://' or ':443'. You can use decorateRequest to ammend any auth or challenge headers required to succeed https. | ||
Will trace the execution of the express-http-proxy module in order to aide debugging. | ||
Here is an older answer about using the https-proxy-agent package. It may be useful if the included functionality in ```http-express-proxy``` does not solve your use case. | ||
A: Yes, you can use the 'https-proxy-agent' package. Something like this: | ||
## Upgrade to 1.0, transition guide and breaking changes | ||
1. | ||
```decorateRequest``` has been REMOVED, and will generate an error when called. See ```proxyReqOptDecorator``` and ```proxyReqBodyDecorator```. | ||
Resolution: Most authors will simply need to change the method name for their | ||
decorateRequest method; if author was decorating reqOpts and reqBody in the | ||
same method, this will need to be split up. | ||
2. | ||
```intercept``` has been REMOVED, and will generate an error when called. See ```userResDecorator```. | ||
Resolution: Most authors will simply need to change the method name from ```intercept``` to ```userResDecorator```, and exit the method by returning the value, rather than passing it to a callback. E.g.: | ||
Before: | ||
```js | ||
var corporateProxyServer = process.env.HTTP_PROXY || process.env.http_proxy || process.env.HTTPS_PROXY || process.env.https_proxy; | ||
app.use('/proxy', proxy('www.google.com', { | ||
intercept: function(proxyRes, proxyResData, userReq, userRes, cb) { | ||
data = JSON.parse(proxyResData.toString('utf8')); | ||
data.newProperty = 'exciting data'; | ||
cb(null, JSON.stringify(data)); | ||
} | ||
})); | ||
``` | ||
if (corporateProxyServer) { | ||
corporateProxyAgent = new HttpsProxyAgent(corporateProxyServer); | ||
} | ||
Now: | ||
```js | ||
app.use('/proxy', proxy('www.google.com', { | ||
userResDecorator: function(proxyRes, proxyResData, userReq, userRes) { | ||
data = JSON.parse(proxyResData.toString('utf8')); | ||
data.newProperty = 'exciting data'; | ||
return JSON.stringify(data); | ||
} | ||
})); | ||
``` | ||
Then inside the decorateRequest method, add the agent to the request: | ||
3. | ||
```forwardPath``` and ```forwardPathAsync``` have been DEPRECATED and will generate a warning when called. See ```proxyReqPathResolver```. | ||
Resolution: Simple update the name of either ```forwardPath``` or ```forwardPathAsync``` to ```proxyReqPathResolver```. | ||
## When errors occur on your proxy server | ||
When your proxy server responds with an error, express-http-proxy returns a response with the same status code. See ```test/catchingErrors``` for syntax details. | ||
When your proxy server times out, express-http-proxy will continue to wait indefinitely for a response, unless you define a ```timeout``` as described above. | ||
## Questions | ||
### Q: Does it support https proxy? | ||
The library will automatically use https if the provided path has 'https://' or ':443'. You may also set option ```https``` to true to always use https. | ||
You can use ```proxyReqOptDecorator``` to ammend any auth or challenge headers required to succeed https. | ||
### Q: How can I support non-standard certificate chains? | ||
You can use the ability to decorate the proxy request prior to sending. See ```proxyReqOptDecorator``` for more details. | ||
```js | ||
req.agent = corporateProxyAgent; | ||
app.use('/', proxy('internalhost.example.com', { | ||
proxyReqOptDecorator: function(proxyReqOpts, originalReq) { | ||
proxyReqOpts.ca = [caCert, intermediaryCert] | ||
return proxyReqOpts; | ||
} | ||
}) | ||
``` | ||
### Q: How to ignore self-signed certificates ? | ||
You can set the `rejectUnauthorized` value in proxy request options prior to sending. See ```proxyReqOptDecorator``` for more details. | ||
```js | ||
app.use('/', proxy('internalhost.example.com', { | ||
proxyReqOptDecorator: function(proxyReqOpts, originalReq) { | ||
proxyReqOpts.rejectUnauthorized = false | ||
return proxyReqOpts; | ||
} | ||
})) | ||
``` | ||
## Release Notes | ||
@@ -260,3 +605,20 @@ | ||
| --- | --- | | ||
| 0.11.1 | Allow author to prevent host from being memoized between requests. General program cleanup. | | ||
| 1.6.2 | Update node.js versions used by ci. | | ||
| 1.6.1 | Minor bug fixes and documentation. | | ||
| 1.6.0 | Do gzip and gunzip aysyncronously. Test and documentation improvements, dependency updates. | | ||
| 1.5.1 | Fixes bug in stringifying debug messages. | | ||
| 1.5.0 | Fixes bug in `filter` signature. Fix bug in skipToNextHandler, add expressHttpProxy value to user res when skipped. Add tests for host as ip address. | | ||
| 1.4.0 | DEPRECATED. Critical bug in the `filter` api.| | ||
| 1.3.0 | DEPRECATED. Critical bug in the `filter` api. `filter` now supports Promises. Update linter to eslint. | | ||
| 1.2.0 | Auto-stream when no decorations are made to req/res. Improved docs, fixes issues in maybeSkipToNexthandler, allow authors to manage error handling. | | ||
| 1.1.0 | Add step to allow response headers to be modified. | ||
| 1.0.7 | Update dependencies. Improve docs on promise rejection. Fix promise rejection on body limit. Improve debug output. | | ||
| 1.0.6 | Fixes preserveHostHdr not working, skip userResDecorator on 304, add maybeSkipToNext, test improvements and cleanup. | | ||
| 1.0.5 | Minor documentation and test patches | | ||
| 1.0.4 | Minor documentation, test, and package fixes | | ||
| 1.0.3 | Fixes 'limit option is not taken into account | | ||
| 1.0.2 | Minor docs corrections. | | ||
| 1.0.1 | Minor docs adjustments. | | ||
| 1.0.0 | Major revision. <br > REMOVE decorateRequest, ADD proxyReqOptDecorator and proxyReqBodyDecorator. <br /> REMOVE intercept, ADD userResDecorator <br /> userResDecorator supports a Promise form for async operations. <br /> General cleanup of structure and application of hooks. Documentation improvements. Update all dependencies. Re-organize code as a series of workflow steps, each (potentially) supporting a promise, and creating a reusable pattern for future development. | | ||
| 0.11.0 | Allow author to prevent host from being memoized between requests. General program cleanup. | | ||
| 0.10.1| Fixed issue where 'body encoding' was being incorrectly set to the character encoding. <br /> Dropped explicit support for node 0.10. <br /> Intercept can now deal with gziped responses. <br /> Author can now 'force https', even if the original request is over http. <br /> Do not call next after ECONNRESET catch. | | ||
@@ -263,0 +625,0 @@ | 0.10.0 | Fix regression in forwardPath implementation. | |
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
var assert = require('assert'); | ||
@@ -7,37 +8,42 @@ var express = require('express'); | ||
var proxy = require('../'); | ||
var startProxyTarget = require('./support/proxyTarget'); | ||
describe('body encoding', function() { | ||
'use strict'; | ||
this.timeout(10000); | ||
var app; | ||
describe('body encoding', function () { | ||
var server; | ||
beforeEach(function() { | ||
app = express(); | ||
app.use(proxy('httpbin.org')); | ||
before(function () { | ||
server = startProxyTarget(8109, 1000); | ||
}); | ||
after(function () { | ||
server.close(); | ||
}); | ||
it('allow raw data', function(done) { | ||
var pngHex = '89504e470d0a1a0a0' + | ||
'000000d4948445200' + | ||
'00000100000001080' + | ||
'60000001f15c48900' + | ||
'00000a49444154789' + | ||
'c6300010000050001' + | ||
'0d0a2db4000000004' + | ||
'9454e44ae426082'; | ||
var pngData = new Buffer(pngHex, 'hex'); | ||
this.timeout(10000); | ||
var pngHex = '89504e470d0a1a0a0' + | ||
'000000d4948445200' + | ||
'00000100000001080' + | ||
'60000001f15c48900' + | ||
'00000a49444154789' + | ||
'c6300010000050001' + | ||
'0d0a2db4000000004' + | ||
'9454e44ae426082'; | ||
var pngData = new Buffer(pngHex, 'hex'); | ||
it('allow raw data', function (done) { | ||
var filename = os.tmpdir() + '/express-http-proxy-test-' + (new Date()).getTime() + '-png-transparent.png'; | ||
var app = express(); | ||
app.use(proxy('httpbin.org', { | ||
app.use(proxy('localhost:8109', { | ||
reqBodyEncoding: null, | ||
decorateRequest: function(reqOpts) { | ||
assert((new Buffer(reqOpts.bodyContent).toString('hex')).indexOf(pngData.toString('hex')) >= 0, | ||
proxyReqBodyDecorator: function (bodyContent) { | ||
assert((new Buffer(bodyContent).toString('hex')).indexOf(pngData.toString('hex')) >= 0, | ||
'body should contain same data'); | ||
return reqOpts; | ||
return bodyContent; | ||
} | ||
})); | ||
fs.writeFile(filename, pngData, function(err) { | ||
fs.writeFile(filename, pngData, function (err) { | ||
if (err) { throw err; } | ||
@@ -47,5 +53,11 @@ request(app) | ||
.attach('image', filename) | ||
.end(function(err, res) { | ||
fs.unlink(filename); | ||
assert.equal(res.body.files.image, 'data:image/png;base64,' + pngData.toString('base64')); | ||
.end(function (err) { | ||
fs.unlinkSync(filename); | ||
// This test is both broken and I think unnecessary. | ||
// Its broken because http.bin no longer supports /post, but this test assertion is based on the old | ||
// httpbin behavior. | ||
// The assertion in the decorateRequest above verifies the test title. | ||
//var response = new Buffer(res.body.attachment.data).toString('base64'); | ||
//assert(response.indexOf(pngData.toString('base64')) >= 0, 'response should include original raw data'); | ||
done(err); | ||
@@ -57,7 +69,126 @@ }); | ||
describe('when user sets parseReqBody as bool', function () { | ||
it('should not parse body', function (done) { | ||
var filename = os.tmpdir() + '/express-http-proxy-test-' + (new Date()).getTime() + '-png-transparent.png'; | ||
var app = express(); | ||
app.use(proxy('localhost:8109', { | ||
parseReqBody: false, | ||
proxyReqBodyDecorator: function (bodyContent) { | ||
assert(!bodyContent, 'body content should not be parsed.'); | ||
return bodyContent; | ||
} | ||
})); | ||
describe('when user sets reqBodyEncoding', function() { | ||
it('should set the accepts-charset header', function(done) { | ||
fs.writeFile(filename, pngData, function (err) { | ||
if (err) { throw err; } | ||
request(app) | ||
.post('/post') | ||
.attach('image', filename) | ||
.end(function (err) { | ||
fs.unlinkSync(filename); | ||
// This test is both broken and I think unnecessary. | ||
// Its broken because http.bin no longer supports /post, but this test assertion is based on the old | ||
// httpbin behavior. | ||
// The assertion in the decorateRequest above verifies the test title. | ||
// var response = new Buffer(res.body.attachment.data).toString('base64'); | ||
// assert(response.indexOf(pngData.toString('base64')) >= 0, 'response should include original raw data'); | ||
done(err); | ||
}); | ||
}); | ||
}); | ||
it('should not fail on large limit', function (done) { | ||
var filename = os.tmpdir() + '/express-http-proxy-test-' + (new Date()).getTime() + '-png-transparent.png'; | ||
var app = express(); | ||
app.use(proxy('httpbin.org', { | ||
app.use(proxy('localhost:8109', { | ||
parseReqBody: false, | ||
limit: '20gb', | ||
})); | ||
fs.writeFile(filename, pngData, function (err) { | ||
if (err) { throw err; } | ||
request(app) | ||
.post('/post') | ||
.attach('image', filename) | ||
.end(function (err) { | ||
fs.unlinkSync(filename); | ||
assert(err === null); | ||
// This test is both broken and I think unnecessary. | ||
// Its broken because http.bin no longer supports /post, but this test assertion is based on the old | ||
// httpbin behavior. | ||
// The assertion in the decorateRequest above verifies the test title. | ||
//var response = new Buffer(res.body.attachment.data).toString('base64'); | ||
//assert(response.indexOf(pngData.toString('base64')) >= 0, 'response should include original raw data'); | ||
done(err); | ||
}); | ||
}); | ||
}); | ||
it('should fail with an error when exceeding limit', function (done) { | ||
var app = express(); | ||
app.use(proxy('localhost:8109', { | ||
limit: 1, | ||
})); | ||
// silence jshint warning about unused vars - express error handler *needs* 4 args | ||
app.use(function (err, req, res, next) { // eslint-disable-line no-unused-vars | ||
res.json(err); | ||
}); | ||
request(app) | ||
.post('/post') | ||
.send({ some: 'json' }) | ||
.end(function (err, response) { | ||
assert(response.body.message === 'request entity too large'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('when user sets parseReqBody as function', function () { | ||
it('should not parse body with form-data content', function (done) { | ||
var filename = os.tmpdir() + '/express-http-proxy-test-' + (new Date()).getTime() + '-png-transparent.png'; | ||
var app = express(); | ||
app.use(proxy('localhost:8109', { | ||
parseReqBody: (proxyReq) => proxyReq.headers['content-type'].includes('application/json'), | ||
proxyReqBodyDecorator: function (bodyContent) { | ||
assert(!bodyContent, 'body content should not be parsed.'); | ||
return bodyContent; | ||
} | ||
})); | ||
fs.writeFile(filename, pngData, function (err) { | ||
if (err) { throw err; } | ||
request(app) | ||
.post('/post') | ||
.attach('image', filename) | ||
.end(function (err) { | ||
fs.unlinkSync(filename); | ||
done(err); | ||
}); | ||
}); | ||
}); | ||
it('should parse body with json content', function (done) { | ||
var app = express(); | ||
app.use(proxy('localhost:8109', { | ||
parseReqBody: (proxyReq) => proxyReq.headers['content-type'].includes('application/json'), | ||
proxyReqBodyDecorator: function (bodyContent) { | ||
assert(bodyContent, 'body content should be parsed.'); | ||
return bodyContent; | ||
} | ||
})); | ||
request(app) | ||
.post('/post') | ||
.send({ some: 'json' }) | ||
.end(function (err) { | ||
done(err); | ||
}); | ||
}); | ||
}); | ||
describe('when user sets reqBodyEncoding', function () { | ||
it('should set the accepts-charset header', function (done) { | ||
var app = express(); | ||
app.use(proxy('localhost:8109', { | ||
reqBodyEncoding: 'utf-16' | ||
@@ -67,5 +198,5 @@ })); | ||
.get('/headers') | ||
.end(function(err, res) { | ||
.end(function (err, res) { | ||
if (err) { throw err; } | ||
assert.equal(res.body.headers['Accept-Charset'], 'utf-16'); | ||
assert.equal(res.body.headers['accept-charset'], 'utf-16'); | ||
done(err); | ||
@@ -75,3 +206,4 @@ }); | ||
}); | ||
}); | ||
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var assert = require('assert'); | ||
@@ -6,19 +8,39 @@ var express = require('express'); | ||
describe('proxies cookie', function() { | ||
'use strict'; | ||
var proxyTarget = require('../test/support/proxyTarget'); | ||
var proxyRouteFn = [{ | ||
method: 'get', | ||
path: '/cookieTest', | ||
fn: function (req, res) { | ||
Object.keys(req.cookies).forEach(function (key) { | ||
res.cookie(key, req.cookies[key]); | ||
}); | ||
res.sendStatus(200); | ||
} | ||
}]; | ||
describe('proxies cookie', function () { | ||
this.timeout(10000); | ||
var app; | ||
var proxyServer; | ||
beforeEach(function() { | ||
beforeEach(function () { | ||
proxyServer = proxyTarget(12346, 100, proxyRouteFn); | ||
app = express(); | ||
app.use(proxy('httpbin.org')); | ||
app.use(proxy('localhost:12346')); | ||
}); | ||
it('set cookie', function(done) { | ||
afterEach(function () { | ||
proxyServer.close(); | ||
}); | ||
it('set cookie', function (done) { | ||
request(app) | ||
.get('/cookies/set?mycookie=value') | ||
.end(function(err, res) { | ||
assert(res.headers['set-cookie']); | ||
.get('/cookieTest') | ||
.set('Cookie', 'myApp-token=12345667') | ||
.end(function (err, res) { | ||
var cookiesMatch = res.headers['set-cookie'].filter(function (item) { | ||
return item.match(/myApp-token=12345667/); | ||
}); | ||
assert(cookiesMatch); | ||
done(err); | ||
@@ -25,0 +47,0 @@ }); |
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var assert = require('assert'); | ||
@@ -6,4 +8,3 @@ var express = require('express'); | ||
describe('proxies headers', function() { | ||
'use strict'; | ||
describe('proxies headers', function () { | ||
this.timeout(10000); | ||
@@ -13,3 +14,3 @@ | ||
beforeEach(function() { | ||
beforeEach(function () { | ||
http = express(); | ||
@@ -23,7 +24,7 @@ http.use(proxy('http://httpbin.org', { | ||
it('passed as options', function(done) { | ||
it('passed as options', function (done) { | ||
request(http) | ||
.get('/headers') | ||
.expect(200) | ||
.end(function(err, res) { | ||
.end(function (err, res) { | ||
if (err) { return done(err); } | ||
@@ -35,3 +36,3 @@ assert(res.body.headers['X-Current-President'] === 'taft'); | ||
it('passed as on request', function(done) { | ||
it('passed as on request', function (done) { | ||
request(http) | ||
@@ -41,3 +42,3 @@ .get('/headers') | ||
.expect(200) | ||
.end(function(err, res) { | ||
.end(function (err, res) { | ||
if (err) { return done(err); } | ||
@@ -44,0 +45,0 @@ assert(res.body.headers['X-Powerererer']); |
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var express = require('express'); | ||
@@ -5,4 +7,3 @@ var request = require('supertest'); | ||
describe('host can be a dynamic function', function() { | ||
'use strict'; | ||
describe('host can be a dynamic function', function () { | ||
@@ -12,36 +13,60 @@ this.timeout(10000); | ||
var app = express(); | ||
var firstProxyApp = express(); | ||
var secondProxyApp = express(); | ||
var firstPort = Math.floor(Math.random() * 10000); | ||
var secondPort = Math.floor(Math.random() * 10000); | ||
describe('and memoization can be disabled', function () { | ||
var firstProxyApp = express(); | ||
var secondProxyApp = express(); | ||
// TODO: This seems like a bug factory. We will have intermittent port conflicts, yeah? | ||
app.use('/proxy/:port', proxy(function(req) { | ||
return 'localhost:' + req.params.port; | ||
}, { | ||
memoizeHost: false | ||
})); | ||
function randomNumberInPortRange() { | ||
return Math.floor(Math.random() * 48000) + 1024; | ||
} | ||
var firstPort = randomNumberInPortRange(); | ||
var secondPort = randomNumberInPortRange(); | ||
firstProxyApp.get('/', function(req, res) { | ||
res.sendStatus(204); | ||
}); | ||
firstProxyApp.listen(firstPort); | ||
var hostFn = function (req) { | ||
return 'localhost:' + req.params.port; | ||
}; | ||
secondProxyApp.get('/', function(req, res) { | ||
res.sendStatus(200); | ||
}); | ||
secondProxyApp.listen(secondPort); | ||
app.use('/proxy/:port', proxy(hostFn, { memoizeHost: false })); | ||
it('can proxy with session value', function(done) { | ||
request(app) | ||
.get('/proxy/' + firstPort) | ||
.expect(204) | ||
.end(function(err) { | ||
if (err) { | ||
return done(err); | ||
} | ||
request(app) | ||
firstProxyApp | ||
.get('/', function (req, res) { res.sendStatus(204); }) | ||
.listen(firstPort); | ||
secondProxyApp | ||
.get('/', function (req, res) { res.sendStatus(200); }) | ||
.listen(secondPort); | ||
it('when not memoized, host resolves to a second value on the seecond call', function (done) { | ||
request(app) | ||
.get('/proxy/' + firstPort) | ||
.expect(204) | ||
.end(function (err) { | ||
if (err) { | ||
return done(err); | ||
} | ||
request(app) | ||
.get('/proxy/' + secondPort) | ||
.expect(200, done); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('host can be an ip address', function () { | ||
it('with a port', function (done) { | ||
var app = express(); | ||
app.use('/proxy/', proxy('127.0.0.1:3020')); | ||
var targetApp = express(); | ||
targetApp | ||
.get('/', function (req, res) { res.sendStatus(211); }) | ||
.listen(3020); | ||
request(app) | ||
.get('/proxy/') | ||
.expect(211) | ||
.end(done); | ||
}); | ||
}); | ||
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var assert = require('assert'); | ||
@@ -6,4 +8,3 @@ var express = require('express'); | ||
describe('proxies https', function() { | ||
'use strict'; | ||
describe('proxies https', function () { | ||
@@ -14,3 +15,3 @@ this.timeout(10000); | ||
beforeEach(function() { | ||
beforeEach(function () { | ||
app = express(); | ||
@@ -22,6 +23,6 @@ }); | ||
.get('/get?show_env=1') | ||
.end(function(err, res) { | ||
.end(function (err, res) { | ||
if (err) { return done(err); } | ||
assert(res.body.headers['X-Forwarded-Ssl'] === 'on'); | ||
assert(res.body.headers['X-Forwarded-Protocol'] === 'https'); | ||
assert(res.body.headers['X-Forwarded-Port'] === '443', 'Expects forwarded 443 Port'); | ||
assert(res.body.headers['X-Forwarded-Proto'] === 'https', 'Expects forwarded protocol to be https'); | ||
done(); | ||
@@ -31,5 +32,5 @@ }); | ||
describe('when host is a String', function() { | ||
describe('and includes "https" as protocol', function() { | ||
it('proxys via https', function(done) { | ||
describe('when host is a String', function () { | ||
describe('and includes "https" as protocol', function () { | ||
it('proxys via https', function (done) { | ||
app.use(proxy('https://httpbin.org')); | ||
@@ -39,5 +40,5 @@ assertSecureRequest(app, done); | ||
}); | ||
describe('option https is set to "true"', function() { | ||
it('proxys via https', function(done) { | ||
app.use(proxy('http://httpbin.org', {https: true})); | ||
describe('option https is set to "true"', function () { | ||
it('proxys via https', function (done) { | ||
app.use(proxy('http://httpbin.org', { https: true })); | ||
assertSecureRequest(app, done); | ||
@@ -48,12 +49,12 @@ }); | ||
describe('when host is a Function', function() { | ||
describe('returned value includes "https" as protocol', function() { | ||
it('proxys via https', function(done) { | ||
app.use(proxy(function() { return 'https://httpbin.org'; })); | ||
describe('when host is a Function', function () { | ||
describe('returned value includes "https" as protocol', function () { | ||
it('proxys via https', function (done) { | ||
app.use(proxy(function () { return 'https://httpbin.org'; })); | ||
assertSecureRequest(app, done); | ||
}); | ||
}); | ||
describe('option https is set to "true"', function() { | ||
it('proxys via https', function(done) { | ||
app.use(proxy(function() { return 'http://httpbin.org'; }, {https: true})); | ||
describe('option https is set to "true"', function () { | ||
it('proxys via https', function (done) { | ||
app.use(proxy(function () { return 'http://httpbin.org'; }, { https: true })); | ||
assertSecureRequest(app, done); | ||
@@ -60,0 +61,0 @@ }); |
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var assert = require('assert'); | ||
@@ -6,10 +8,30 @@ var express = require('express'); | ||
var proxy = require('../'); | ||
var proxyTarget = require('../test/support/proxyTarget'); | ||
describe('middleware compatibility', function() { | ||
'use strict'; | ||
it('should use req.body if defined', function(done) { | ||
var proxyRouteFn = [{ | ||
method: 'post', | ||
path: '/poster', | ||
fn: function (req, res) { | ||
res.send(req.body); | ||
} | ||
}]; | ||
describe('middleware compatibility', function () { | ||
var proxyServer; | ||
beforeEach(function () { | ||
proxyServer = proxyTarget(12346, 100, proxyRouteFn); | ||
}); | ||
afterEach(function () { | ||
proxyServer.close(); | ||
}); | ||
it('should use req.body if defined', function (done) { | ||
var app = express(); | ||
// Simulate another middleware that puts req stream into the body | ||
app.use(function(req, res, next) { | ||
app.use(function (req, res, next) { | ||
var received = []; | ||
@@ -28,8 +50,8 @@ req.on('data', function onData(chunk) { | ||
app.use(proxy('httpbin.org', { | ||
intercept: function(rsp, data, req, res, cb) { | ||
app.use(proxy('localhost:12346', { | ||
userResDecorator: function (rsp, data, req) { | ||
assert(req.body); | ||
assert.equal(req.body.foo, 1); | ||
assert.equal(req.body.mypost, 'hello'); | ||
cb(null, data); | ||
return data; | ||
} | ||
@@ -39,14 +61,12 @@ })); | ||
request(app) | ||
.post('/post') | ||
.send({ | ||
mypost: 'hello' | ||
.post('/poster') | ||
.send({ mypost: 'hello' }) | ||
.expect(function (res) { | ||
assert.equal(res.body.foo, 1); | ||
assert.equal(res.body.mypost, 'hello'); | ||
}) | ||
.expect(function(res) { | ||
assert.equal(res.body.json.foo, 1); | ||
assert.equal(res.body.json.mypost, 'hello'); | ||
}) | ||
.end(done); | ||
}); | ||
it('should stringify req.body when it is a json body so it is written to proxy request', function(done) { | ||
it('should stringify req.body when it is a json body so it is written to proxy request', function (done) { | ||
var app = express(); | ||
@@ -57,5 +77,5 @@ app.use(bodyParser.json()); | ||
})); | ||
app.use(proxy('httpbin.org')); | ||
app.use(proxy('localhost:12346')); | ||
request(app) | ||
.post('/post') | ||
.post('/poster') | ||
.send({ | ||
@@ -65,5 +85,5 @@ mypost: 'hello', | ||
}) | ||
.expect(function(res) { | ||
assert.equal(res.body.json.doorknob, 'wrect'); | ||
assert.equal(res.body.json.mypost, 'hello'); | ||
.expect(function (res) { | ||
assert.equal(res.body.doorknob, 'wrect'); | ||
assert.equal(res.body.mypost, 'hello'); | ||
}) | ||
@@ -73,3 +93,3 @@ .end(done); | ||
it('should convert req.body to a Buffer when reqAsBuffer is set', function(done) { | ||
it('should convert req.body to a Buffer when reqAsBuffer is set', function (done) { | ||
var app = express(); | ||
@@ -80,7 +100,7 @@ app.use(bodyParser.json()); | ||
})); | ||
app.use(proxy('httpbin.org', { | ||
app.use(proxy('localhost:12346', { | ||
reqAsBuffer: true | ||
})); | ||
request(app) | ||
.post('/post') | ||
.post('/poster') | ||
.send({ | ||
@@ -90,5 +110,5 @@ mypost: 'hello', | ||
}) | ||
.expect(function(res) { | ||
assert.equal(res.body.json.doorknob, 'wrect'); | ||
assert.equal(res.body.json.mypost, 'hello'); | ||
.expect(function (res) { | ||
assert.equal(res.body.doorknob, 'wrect'); | ||
assert.equal(res.body.mypost, 'hello'); | ||
}) | ||
@@ -95,0 +115,0 @@ .end(done); |
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var assert = require('assert'); | ||
@@ -7,6 +9,5 @@ var express = require('express'); | ||
function proxyTarget(port) { | ||
'use strict'; | ||
var other = express(); | ||
other.get('/', function(req, res) { | ||
other.get('/', function (req, res) { | ||
res.send('Success'); | ||
@@ -17,12 +18,12 @@ }); | ||
describe('proxies to requested port', function() { | ||
'use strict'; | ||
describe('proxies to requested port', function () { | ||
var other; | ||
var http; | ||
var other, http; | ||
beforeEach(function() { | ||
beforeEach(function () { | ||
http = express(); | ||
other = proxyTarget(8080); | ||
other = proxyTarget(56001); | ||
}); | ||
afterEach(function() { | ||
afterEach(function () { | ||
other.close(); | ||
@@ -36,3 +37,3 @@ }); | ||
.expect(200) | ||
.end(function(err, res) { | ||
.end(function (err, res) { | ||
if (err) { return done(err); } | ||
@@ -44,7 +45,7 @@ assert(res.text === 'Success'); | ||
describe('when host is a String', function() { | ||
it('when passed as an option', function(done) { | ||
describe('when host is a String', function () { | ||
it('when passed as an option', function (done) { | ||
http.use(proxy('http://localhost', { | ||
port: 8080 | ||
port: 56001 | ||
})); | ||
@@ -55,5 +56,5 @@ | ||
it('when passed on the host string', function(done) { | ||
it('when passed on the host string', function (done) { | ||
http.use(proxy('http://localhost:8080')); | ||
http.use(proxy('http://localhost:56001')); | ||
@@ -65,9 +66,9 @@ assertSuccess(http, done); | ||
describe('when host is a function', function() { | ||
describe('when host is a function', function () { | ||
it('and port is on the String returned', function(done) { | ||
it('and port is on the String returned', function (done) { | ||
http.use(proxy( | ||
function() { return 'http://localhost:8080'; } | ||
function () { return 'http://localhost:56001'; } | ||
)); | ||
@@ -78,7 +79,7 @@ | ||
it('and port passed as an option', function(done) { | ||
it('and port passed as an option', function (done) { | ||
http.use(proxy( | ||
function() { return 'http://localhost'; }, | ||
{ port: 8080 } | ||
function () { return 'http://localhost'; }, | ||
{ port: 56001 } | ||
)); | ||
@@ -85,0 +86,0 @@ |
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var assert = require('assert'); | ||
@@ -6,4 +8,3 @@ var express = require('express'); | ||
describe('preserveReqSession', function() { | ||
'use strict'; | ||
describe('preserveReqSession', function () { | ||
@@ -14,3 +15,3 @@ this.timeout(10000); | ||
beforeEach(function() { | ||
beforeEach(function () { | ||
app = express(); | ||
@@ -20,5 +21,5 @@ app.use(proxy('httpbin.org')); | ||
it('preserveReqSession', function(done) { | ||
it('preserveReqSession', function (done) { | ||
var app = express(); | ||
app.use(function(req, res, next) { | ||
app.use(function (req, res, next) { | ||
req.session = 'hola'; | ||
@@ -29,4 +30,5 @@ next(); | ||
preserveReqSession: true, | ||
decorateRequest: function(req) { | ||
assert(req.session, 'hola'); | ||
proxyReqOptDecorator: function (reqOpts) { | ||
assert(reqOpts.session, 'hola'); | ||
return reqOpts; | ||
} | ||
@@ -37,3 +39,3 @@ })); | ||
.get('/user-agent') | ||
.end(function(err) { | ||
.end(function (err) { | ||
if (err) { return done(err); } | ||
@@ -40,0 +42,0 @@ done(); |
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var express = require('express'); | ||
@@ -6,7 +8,5 @@ var request = require('supertest'); | ||
describe('proxies status code', function() { | ||
'use strict'; | ||
describe('proxies status code', function () { | ||
var proxyServer = express(); | ||
var port = 21231; | ||
var port = 21239; | ||
var proxiedEndpoint = 'http://localhost:' + port; | ||
@@ -17,12 +17,12 @@ var server; | ||
beforeEach(function() { | ||
server = mockEndpoint.listen(21231); | ||
beforeEach(function () { | ||
server = mockEndpoint.listen(21239); | ||
}); | ||
afterEach(function() { | ||
afterEach(function () { | ||
server.close(); | ||
}); | ||
[304, 404, 200, 401, 500].forEach(function(status) { | ||
it('on ' + status, function(done) { | ||
[304, 404, 200, 401, 500].forEach(function (status) { | ||
it('on ' + status, function (done) { | ||
request(proxyServer) | ||
@@ -29,0 +29,0 @@ .get('/status/' + status) |
@@ -1,27 +0,23 @@ | ||
var assert = require('assert'); | ||
'use strict'; | ||
var express = require('express'); | ||
var request = require('supertest'); | ||
var proxy = require('../'); | ||
var proxyTarget = require('./support/proxyTarget'); | ||
function proxyTarget(port, timeout) { | ||
'use strict'; | ||
var other = express(); | ||
other.get('/', function(req, res) { | ||
setTimeout(function() { | ||
res.send('Success'); | ||
},timeout); | ||
}); | ||
return other.listen(port); | ||
} | ||
describe('honors timeout option', function () { | ||
describe('honors timeout option', function() { | ||
'use strict'; | ||
var other; | ||
var http; | ||
var other, http; | ||
beforeEach(function() { | ||
beforeEach(function () { | ||
http = express(); | ||
other = proxyTarget(8080, 1000); | ||
other = proxyTarget(56001, 1000, [{ | ||
method: 'get', | ||
path: '/', | ||
fn: function (req, res) { res.sendStatus(200); } | ||
}]); | ||
}); | ||
afterEach(function() { | ||
afterEach(function () { | ||
other.close(); | ||
@@ -34,7 +30,3 @@ }); | ||
.expect(200) | ||
.end(function(err, res) { | ||
if (err) { return done(err); } | ||
assert(res.text === 'Success'); | ||
done(); | ||
}); | ||
.end(done); | ||
} | ||
@@ -45,13 +37,11 @@ | ||
.get('/') | ||
.expect(408) | ||
.expect('X-Timout-Reason', 'express-http-proxy timed out your request after 100 ms.') | ||
.end(function() { | ||
done(); | ||
}); | ||
.expect(504) | ||
.expect('X-Timeout-Reason', 'express-http-proxy reset the request.') | ||
.end(done); | ||
} | ||
describe('when timeout option is set lower than server response time', function() { | ||
it('should fail with CONNECTION TIMEOUT', function(done) { | ||
describe('when timeout option is set lower than server response time', function () { | ||
it('should fail with CONNECTION TIMEOUT', function (done) { | ||
http.use(proxy('http://localhost:8080', { | ||
http.use(proxy('http://localhost:56001', { | ||
timeout: 100, | ||
@@ -64,6 +54,6 @@ })); | ||
describe('when timeout option is set higher than server response time', function() { | ||
it('should succeed', function(done) { | ||
describe('when timeout option is set higher than server response time', function () { | ||
it('should succeed', function (done) { | ||
http.use(proxy('http://localhost:8080', { | ||
http.use(proxy('http://localhost:56001', { | ||
timeout: 1200, | ||
@@ -70,0 +60,0 @@ })); |
@@ -0,1 +1,3 @@ | ||
'use strict'; | ||
var assert = require('assert'); | ||
@@ -6,8 +8,7 @@ var express = require('express'); | ||
describe('url parsing', function() { | ||
'use strict'; | ||
describe('url parsing', function () { | ||
this.timeout(10000); | ||
it('can parse a url with a port', function(done) { | ||
it('can parse a url with a port', function (done) { | ||
var app = express(); | ||
@@ -17,3 +18,3 @@ app.use(proxy('http://httpbin.org:80')); | ||
.get('/') | ||
.end(function(err) { | ||
.end(function (err) { | ||
if (err) { return done(err); } | ||
@@ -25,3 +26,3 @@ assert(true); | ||
it('does not throw `Uncaught RangeError` if you have both a port and a trailing slash', function(done) { | ||
it('does not throw `Uncaught RangeError` if you have both a port and a trailing slash', function (done) { | ||
var app = express(); | ||
@@ -31,3 +32,3 @@ app.use(proxy('http://httpbin.org:80/')); | ||
.get('/') | ||
.end(function(err) { | ||
.end(function (err) { | ||
if (err) { return done(err); } | ||
@@ -41,2 +42,1 @@ assert(true); | ||
@@ -0,8 +1,10 @@ | ||
'use strict'; | ||
var assert = require('assert'); | ||
var express = require('express'); | ||
var bodyParser = require('body-parser'); | ||
var request = require('supertest'); | ||
var proxy = require('../'); | ||
describe('http verbs', function() { | ||
'use strict'; | ||
describe('http verbs', function () { | ||
this.timeout(10000); | ||
@@ -12,11 +14,13 @@ | ||
beforeEach(function() { | ||
beforeEach(function () { | ||
app = express(); | ||
app.use(bodyParser.json()); | ||
app.use(bodyParser.urlencoded({ extended: false })); | ||
app.use(proxy('httpbin.org')); | ||
}); | ||
it('test proxy get', function(done) { | ||
it('test proxy get', function (done) { | ||
request(app) | ||
.get('/get') | ||
.end(function(err, res) { | ||
.end(function (err, res) { | ||
if (err) { return done(err); } | ||
@@ -29,3 +33,3 @@ assert(/node-superagent/.test(res.body.headers['User-Agent'])); | ||
it('test proxy post', function(done) { | ||
it('test proxy post', function (done) { | ||
request(app) | ||
@@ -36,3 +40,3 @@ .post('/post') | ||
}) | ||
.end(function(err, res) { | ||
.end(function (err, res) { | ||
assert.equal(res.body.data, '{"mypost":"hello"}'); | ||
@@ -43,4 +47,15 @@ done(err); | ||
it('test proxy put', function(done) { | ||
it('test proxy post by x-www-form-urlencoded', function (done) { | ||
request(app) | ||
.post('/post') | ||
.set('Content-Type', 'application/x-www-form-urlencoded') | ||
.send('mypost=hello') | ||
.end(function (err, res) { | ||
assert.equal(JSON.stringify(res.body.form), '{"mypost":"hello"}'); | ||
done(err); | ||
}); | ||
}); | ||
it('test proxy put', function (done) { | ||
request(app) | ||
.put('/put') | ||
@@ -50,3 +65,3 @@ .send({ | ||
}) | ||
.end(function(err, res) { | ||
.end(function (err, res) { | ||
assert.equal(res.body.data, '{"mypost":"hello"}'); | ||
@@ -57,3 +72,3 @@ done(err); | ||
it('test proxy patch', function(done) { | ||
it('test proxy patch', function (done) { | ||
request(app) | ||
@@ -64,3 +79,3 @@ .patch('/patch') | ||
}) | ||
.end(function(err, res) { | ||
.end(function (err, res) { | ||
assert.equal(res.body.data, '{"mypost":"hello"}'); | ||
@@ -71,3 +86,3 @@ done(err); | ||
it('test proxy delete', function(done) { | ||
it('test proxy delete', function (done) { | ||
request(app) | ||
@@ -78,3 +93,3 @@ .del('/delete') | ||
}) | ||
.end(function(err, res) { | ||
.end(function (err, res) { | ||
assert.equal(res.body.data, '{"mypost":"hello"}'); | ||
@@ -81,0 +96,0 @@ done(err); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
117665
59
2796
623
3
8
8
+ Addeddebug@^3.0.1
+ Addeddebug@3.2.7(transitive)
+ Addedes6-promise@4.2.8(transitive)
+ Addedms@2.1.3(transitive)
- Removedes6-promise@3.3.1(transitive)
Updatedes6-promise@^4.1.1
Updatedraw-body@^2.3.0