Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

front-express-http-proxy

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

front-express-http-proxy - npm Package Compare versions

Comparing version 0.7.2-2 to 0.11.0-1

.jscsrc

379

index.js

@@ -0,3 +1,4 @@

'use strict';
var assert = require('assert');
var util = require('util');
var url = require('url');

@@ -7,8 +8,10 @@ var http = require('http');

var getRawBody = require('raw-body');
var isError = require('lodash.iserror');
var zlib = require('zlib');
require('buffer');
function unset(val) {
return (typeof val === 'undefined' || val === '' || val === null);
}
module.exports = function proxy(host, options) {
assert(host, 'Host should not be empty');

@@ -18,13 +21,6 @@

var port = 80;
var parsedHost;
var parsedHost = null;
if (typeof host != 'function') {
parsedHost = parseHost(host.toString());
if (isError(parsedHost))
throw parsedHost;
}
/**
* intercept(targetResponse, data, res, req, function(err, json));
* Function :: intercept(targetResponse, data, res, req, function(err, json, sent));
*/

@@ -34,155 +30,165 @@ var intercept = options.intercept;

var forwardPath = options.forwardPath || defaultForwardPath;
var resolveProxyPathAsync = options.forwardPathAsync || defaultForwardPathAsync(forwardPath);
var filter = options.filter || defaultFilter;
var limit = options.limit || '1mb';
var preserveHostHdr = options.preserveHostHdr || false;
var preserveReqSession = options.preserveReqSession;
var memoizeHost = unset(options.memoizeHost) ? true : options.memoizeHost;
/* 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.
*/
var reqBodyEncoding = options.reqBodyEncoding !== undefined ? options.reqBodyEncoding: 'utf-8';
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);
});
};
var path;
function sendProxyRequest(req, res, next, path, bodyContent) {
parsedHost = (memoizeHost && parsedHost) ? parsedHost : parseHost(host, req, options);
path = forwardPath(req, res);
var reqOpt = {
hostname: parsedHost.host,
port: options.port || parsedHost.port,
headers: reqHeaders(req, options),
method: req.method,
path: path,
bodyContent: bodyContent,
params: req.params,
};
if (!parsedHost) {
parsedHost = parseHost((typeof host == 'function') ? host(req) : host.toString());
if (isError(parsedHost))
throw parsedHost;
if (preserveReqSession) {
reqOpt.session = req.session;
}
getRawBody(req, {
length: req.headers['content-length'],
limit: limit,
encoding: reqBodyEncoding
}, runProxy);
function runProxy(err, bodyContent) {
var reqOpt = {
hostname: parsedHost.host,
port: options.port || parsedHost.port,
headers: reqHeaders(req, options),
method: req.method,
path: path,
bodyContent: bodyContent,
params: req.params
};
if (decorateRequest) {
reqOpt = decorateRequest(reqOpt, req) || reqOpt;
}
if (preserveReqSession) {
reqOpt.session = req.session;
}
bodyContent = reqOpt.bodyContent;
delete reqOpt.bodyContent;
delete reqOpt.params;
if (decorateRequest)
reqOpt = decorateRequest(reqOpt, req) || reqOpt;
bodyContent = options.reqAsBuffer ?
asBuffer(bodyContent, options) :
asBufferOrString(bodyContent);
bodyContent = reqOpt.bodyContent;
delete reqOpt.bodyContent;
delete reqOpt.params;
reqOpt.headers['content-length'] = getContentLength(bodyContent);
if (err && !bodyContent) return next(err);
if (bodyEncoding(options)) {
reqOpt.headers['Accept-Charset'] = bodyEncoding(options);
}
if (typeof bodyContent == 'string')
reqOpt.headers['content-length'] = Buffer.byteLength(bodyContent);
else if (Buffer.isBuffer(bodyContent)) // Buffer
reqOpt.headers['content-length'] = bodyContent.length;
var chunks = [];
var realRequest = parsedHost.module.request(reqOpt, function(rsp) {
var rspData = null;
rsp.on('data', function(chunk) {
chunks.push(chunk);
});
function postIntercept(res, next, rspData) {
return function(err, rspd, sent) {
if (err) {
return next(err);
}
rspd = asBuffer(rspd, options);
rspd = maybeZipResponse(rspd, res);
rsp.on('end', function() {
var totalLength = chunks.reduce(function(len, buf) {
return len + buf.length;
}, 0);
if (!Buffer.isBuffer(rspd)) {
next(new Error('intercept should return string or' +
'buffer as data'));
}
var rspData = Buffer.concat(chunks, totalLength);
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 (intercept) {
intercept(rsp, rspData, req, res, function(err, rspd, sent) {
if (err) {
return next(err);
}
if (!sent) {
res.send(rspd);
}
};
}
var encode = 'utf8';
if (rsp.headers && rsp.headers['content-type']) {
var contentType = rsp.headers['content-type'];
if (/charset=/.test(contentType)) {
var attrs = contentType.split(';').map(function(str) { return str.trim(); });
for(var i = 0, len = attrs.length; i < len; i++) {
var attr = attrs[i];
if (/charset=/.test(attr)) {
// encode = attr.split('=')[1];
break;
}
}
}
}
var proxyTargetRequest = parsedHost.module.request(reqOpt, function(rsp) {
var chunks = [];
if (typeof rspd == 'string')
rspd = new Buffer(rspd, encode);
rsp.on('data', function(chunk) {
chunks.push(chunk);
});
if (!Buffer.isBuffer(rspd)) {
next(new Error("intercept should return string or buffer as data"));
}
rsp.on('end', function() {
if (!res.headersSent)
res.set('content-length', rspd.length);
else if (rspd.length != rspData.length) {
next(new Error("'Content-Length' is already sent, the length of response data can not be changed"));
}
var rspData = Buffer.concat(chunks, chunkLength(chunks));
if (!sent) {
res.send(rspd);
}
});
} else {
if (intercept) {
rspData = maybeUnzipResponse(rspData, res);
var callback = postIntercept(res, next, rspData);
intercept(rsp, rspData, req, res, callback);
} 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);
}
});
rsp.on('error', function(e) {
next(e);
});
if (!res.headersSent) { // if header is not set yet
res.status(rsp.statusCode);
for (var p in rsp.headers) {
if (p == 'transfer-encoding')
continue;
res.set(p, rsp.headers[p]);
}
}
});
realRequest.on('error', function(e) {
next(e);
rsp.on('error', function(err) {
next(err);
});
if (bodyContent.length) {
realRequest.write(bodyContent);
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]);
});
}
});
realRequest.end();
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;
if (!source) {
return obj;
}
for (var prop in source) {
if (!skips || skips.indexOf(prop) == -1)
if (!skips || skips.indexOf(prop) === -1) {
obj[prop] = source[prop];
}
}

@@ -193,17 +199,21 @@

function parseHost(host) {
function parseHost(host, req, options) {
host = (typeof host === 'function') ? host(req) : host.toString();
if (!host) {
return new Error("Empty host parameter");
return new Error('Empty host parameter');
}
if (!/http(s)?:\/\//.test(host)) {
host = "http://" + host;
host = 'http://' + host;
}
var parsed = url.parse(host);
if (! parsed.hostname) {
return new Error("Unable to parse hostname, possibly missing protocol://?");
if (!parsed.hostname) {
return new Error('Unable to parse hostname, possibly missing protocol://?');
}
var ishttps = parsed.protocol === 'https:';
var ishttps = options.https || parsed.protocol === 'https:';

@@ -213,3 +223,3 @@ return {

port: parsed.port || (ishttps ? 443 : 80),
module: ishttps ? https : http
module: ishttps ? https : http,
};

@@ -219,2 +229,4 @@ }

function reqHeaders(req, options) {
var headers = options.headers || {};

@@ -232,9 +244,106 @@

function defaultFilter(req, res) {
// Allow everything!
function defaultFilter() {
// No-op version of filter. Allows everything!
return true;
}
function defaultForwardPath(req, res) {
return url.parse(req.url).path;
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');
{
"name": "front-express-http-proxy",
"version": "0.7.2-2",
"version": "0.11.0-1",
"description": "http proxy middleware for express",
"engines": {
"node": ">=4.0.0"
},
"engineStrict": true,
"main": "index.js",
"scripts": {
"test": "npm run lint && npm run mocha",
"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"
"lint": "jshint index.js test/*.js",
"jscs": "jscs index.js test/*.js"
},

@@ -18,5 +24,2 @@ "repository": {

],
"engines": {
"node": ">=0.10.0"
},
"author": {

@@ -31,11 +34,12 @@ "name": "villadora",

"devDependencies": {
"body-parser": "^1.15.2",
"express": "^4.3.1",
"iconv": "^2.1.4",
"jscs": "^3.0.7",
"jshint": "^2.5.5",
"mocha": "^2.1.0",
"supertest": "^0.13.0"
"supertest": "^1.2.0"
},
"dependencies": {
"lodash.iserror": "^3.1.0",
"raw-body": "^1.1.6"
"es6-promise": "^3.2.1",
"raw-body": "^2.1.7"
},

@@ -42,0 +46,0 @@ "contributors": [

@@ -29,2 +29,3 @@ # 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)

#### forwardPath

@@ -45,4 +46,20 @@

```
#### forwardPathAsync
The ```forwardPathAsync``` options allows you to modify the path asyncronously prior to proxying the request, using Promises.
```js
app.use(proxy('httpbin.org', {
forwardPathAsync: function() {
return new Promise(function(resolve, reject) {
// ...
// eventually
resolve( /* your resolved forwardPath as string */ )
});
}
}));
```
#### filter
The ```filter``` option can be used to limit what requests are proxied. For example, if you only want to proxy get request

@@ -62,2 +79,3 @@

#### intercept
You can intercept the response before sending it back to the client.

@@ -75,2 +93,43 @@

#### limit
This sets the body size limit (default: `1mb`). If the body size is larger than the specified (or default) limit,
a `413 Request Entity Too Large` error will be returned. See [bytes.js](https://www.npmjs.com/package/bytes) for
a list of supported formats.
```js
app.use('/proxy', proxy('www.google.com', {
limit: '5mb'
}));
```
#### memoizeHost
Defaults to ```true```.
When true, the ```host``` argument will be parsed on first request, and
memoized for all subsequent requests.
When ```false```, ```host``` argument will be parsed on each request.
E.g.,
```js
function coinToss() { return Math.random() > .5 }
function getHost() { return coinToss() ? 'http://yahoo.com' : 'http://google.com' }
app.use(proxy(getHost, {
memoizeHost: false
}))
```
In this example, when ```memoizeHost:false```, the coinToss occurs on each
request, and each request could get either value.
Conversely, When ```memoizeHost:true```, the coinToss would occur on the first
request, and all additional requests would return the value resolved on the
first request.
#### decorateRequest

@@ -82,7 +141,10 @@

app.use('/proxy', proxy('www.google.com', {
decorateRequest: function(reqOpt, req) {
reqOpt.headers['Content-Type'] = '';
reqOpt.method = 'GET';
reqOpt.bodyContent = wrap(req.bodyContent);
return reqOpt;
decorateRequest: function(proxyReq, originalReq) {
// you can update headers
proxyReq.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;
}

@@ -93,2 +155,14 @@ }));

#### 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
option.
```js
app.use('/proxy', proxy('www.google.com', {
https: true
}));
```
#### preserveHostHdr

@@ -98,3 +172,3 @@

```
```js
app.use('/proxy', proxy('www.google.com', {

@@ -105,10 +179,30 @@ preserveHostHdr: true

#### reqAsBuffer
Note: this is an experimental feature. ymmv
The ```reqAsBuffer``` option allows you to ensure the req body is encoded as a Node
```Buffer``` when sending a proxied request. Any value for this is truthy.
This defaults to to false in order to preserve legacy behavior. Note that
the value of ```reqBodyEnconding``` is used as the encoding when coercing strings
(and stringified JSON) to Buffer.
```js
app.use('/proxy', proxy('www.google.com', {
reqAsBuffer: true
}));
```
#### reqBodyEncoding
Encoding used to decode request body. Default to ```utf-8```.
Encoding used to decode request body. Defaults to ```utf-8```.
Use ```null``` to avoid decoding and pass the body as is.
Use ```null``` to preserve as Buffer when proxied request body is a Buffer. (e.g image upload)
Accept any values supported by [raw-body](https://www.npmjs.com/package/raw-body#readme).
```
The same encoding is used in the intercept method.
```js
app.use('/post', proxy('httpbin.org', {

@@ -119,2 +213,14 @@ reqBodyEncoding: null

#### 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.
```js
app.use('/', proxy('httpbin.org', {
timeout: 2000 // in milliseconds, two seconds
}));
```
## Questions

@@ -124,2 +230,7 @@

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.
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:

@@ -145,2 +256,10 @@

| --- | --- |
| 0.11.1 | 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. |
| 0.10.0 | Fix regression in forwardPath implementation. |
| 0.9.1 | Documentation updates. Set 'Accept-Encoding' header to match bodyEncoding. |
| 0.9.0 | Better handling for request body when body is JSON. |
| 0.8.0 | Features: add forwardPathAsync option <br />Updates: modernize dependencies <br />Fixes: Exceptions parsing proxied response causes error: Can't set headers after they are sent. (#111) <br />If client request aborts, proxied request is aborted too (#107) |
| 0.7.4 | Move jscs to devDependencies to avoid conflict with nsp. |
| 0.7.3 | Adds a timeout option. Code organization and small bug fixes. |
| 0.7.2 | Collecting many minor documentation and test improvements. |

@@ -147,0 +266,0 @@ | 0.4.0 | Signature of `intercept` callback changed from `function(data, req, res, callback)` to `function(rsp, data, req, res, callback)` where `rsp` is the original response from the target |

@@ -7,2 +7,4 @@ var assert = require('assert');

function proxyTarget(port) {
'use strict';
var other = express();

@@ -16,4 +18,6 @@ other.get('/', function(req, res) {

describe('proxies to requested port', function() {
'use strict';
var other, http;
beforeEach(function () {
beforeEach(function() {
http = express();

@@ -23,3 +27,3 @@ other = proxyTarget(8080);

afterEach(function () {
afterEach(function() {
other.close();

@@ -34,4 +38,4 @@ });

.end(function(err, res) {
if (err) return done(err);
assert(res.text === "Success");
if (err) { return done(err); }
assert(res.text === 'Success');
done();

@@ -41,3 +45,3 @@ });

describe('when host is a String', function () {
describe('when host is a String', function() {
it('when passed as an option', function(done) {

@@ -61,3 +65,3 @@

describe('when host is a function', function () {
describe('when host is a function', function() {

@@ -68,3 +72,3 @@

http.use(proxy(
function () { return 'http://localhost:8080'; }
function() { return 'http://localhost:8080'; }
));

@@ -78,3 +82,3 @@

http.use(proxy(
function () { return 'http://localhost'; },
function() { return 'http://localhost'; },
{ port: 8080 }

@@ -81,0 +85,0 @@ ));

@@ -1,2 +0,1 @@

var assert = require('assert');
var express = require('express');

@@ -8,14 +7,16 @@ var request = require('supertest');

describe('proxies status code', function() {
var proxyServer = express(),
port = 21231,
proxiedEndpoint = 'http://localhost:' + port,
server;
'use strict';
var proxyServer = express();
var port = 21231;
var proxiedEndpoint = 'http://localhost:' + port;
var server;
proxyServer.use(proxy(proxiedEndpoint));
beforeEach(function () {
beforeEach(function() {
server = mockEndpoint.listen(21231);
});
afterEach(function () {
afterEach(function() {
server.close();

@@ -22,0 +23,0 @@ });

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc