proxy-chain
Advanced tools
Comparing version 0.1.31 to 0.1.32
@@ -21,2 +21,4 @@ 'use strict'; | ||
// TODO: please rename this class to something else than "Handler", it makes it look like the class inherits from HandlerBase, which it doesn't | ||
/** | ||
@@ -23,0 +25,0 @@ * Represents a connection from source client to an external proxy using HTTP CONNECT tunnel, allows TCP connection. |
@@ -40,2 +40,6 @@ 'use strict'; | ||
var _handler_custom_response = require('./handler_custom_response'); | ||
var _handler_custom_response2 = _interopRequireDefault(_handler_custom_response); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -113,10 +117,25 @@ | ||
* @param [options.port] Port where the server the server will listen. By default 8000. | ||
* @param [options.prepareRequestFunction] Custom function to authenticate proxy requests | ||
* and provide URL to chained upstream proxy. It accepts a single parameter which is an object: | ||
* `{ connectionId: Number, request: Object, username: String, password: String, hostname: String, port: Number, isHttp: Boolean }` | ||
* @param [options.prepareRequestFunction] Custom function to authenticate proxy requests, | ||
* provide URL to chained upstream proxy or potentially provide function that generates a custom response to HTTP requests. | ||
* It accepts a single parameter which is an object: | ||
* ```{ | ||
* connectionId: Number, | ||
* request: Object, | ||
* username: String, | ||
* password: String, | ||
* hostname: String, | ||
* port: Number, | ||
* isHttp: Boolean | ||
* }``` | ||
* and returns an object (or promise resolving to the object) with following form: | ||
* `{ requestAuthentication: Boolean, upstreamProxyUrl: String }` | ||
* ```{ | ||
* requestAuthentication: Boolean, | ||
* upstreamProxyUrl: String, | ||
* customResponseFunc: Function | ||
* }``` | ||
* If `upstreamProxyUrl` is false-ish value, no upstream proxy is used. | ||
* If `prepareRequestFunction` is not set, the proxy server will not require any authentication | ||
* and will not use any upstream proxy. | ||
* If `customResponseFunc` is set, it will be called to generate a custom response to the HTTP request. | ||
* It should not be used together with `upstreamProxyUrl`. | ||
* @param [options.authRealm] Realm used in the Proxy-Authenticate header and also in the 'Server' HTTP header. By default it's `ProxyChain`. | ||
@@ -177,11 +196,19 @@ * @param [options.verbose] If true, the server logs | ||
var handlerOptions = void 0; | ||
this.prepareRequestHandling(request).then(function (handlerOpts) { | ||
var handlerOpts = void 0; | ||
this.prepareRequestHandling(request).then(function (result) { | ||
handlerOpts = result; | ||
handlerOpts.srcResponse = response; | ||
handlerOptions = handlerOpts; | ||
_this3.log(handlerOpts.id, 'Using HandlerForward'); | ||
var handler = new _handler_forward2.default(handlerOpts); | ||
var handler = void 0; | ||
if (handlerOpts.customResponseFunc) { | ||
_this3.log(handlerOpts.id, 'Using HandlerCustomResponse'); | ||
handler = new _handler_custom_response2.default(handlerOpts); | ||
} else { | ||
_this3.log(handlerOpts.id, 'Using HandlerForward'); | ||
handler = new _handler_forward2.default(handlerOpts); | ||
} | ||
_this3.handlerRun(handler); | ||
}).catch(function (err) { | ||
_this3.failRequest(request, err, handlerOptions); | ||
_this3.failRequest(request, err, handlerOpts); | ||
}); | ||
@@ -202,6 +229,6 @@ } | ||
var handlerOptions = void 0; | ||
this.prepareRequestHandling(request).then(function (handlerOpts) { | ||
handlerOptions = handlerOpts; | ||
handlerOptions.srcHead = head; | ||
var handlerOpts = void 0; | ||
this.prepareRequestHandling(request).then(function (result) { | ||
handlerOpts = result; | ||
handlerOpts.srcHead = head; | ||
@@ -219,3 +246,3 @@ var handler = void 0; | ||
}).catch(function (err) { | ||
_this4.failRequest(request, err, handlerOptions); | ||
_this4.failRequest(request, err, handlerOpts); | ||
}); | ||
@@ -239,3 +266,3 @@ } | ||
var result = { | ||
var handlerOpts = { | ||
server: this, | ||
@@ -249,3 +276,3 @@ id: ++this.lastHandlerId, | ||
this.log(result.id, '!!! Handling ' + request.method + ' ' + request.url + ' HTTP/' + request.httpVersion); | ||
this.log(handlerOpts.id, '!!! Handling ' + request.method + ' ' + request.url + ' HTTP/' + request.httpVersion); | ||
@@ -262,3 +289,3 @@ var socket = request.socket; | ||
// Note that request.url contains the "server.example.com:80" part | ||
result.trgParsed = (0, _tools.parseHostHeader)(request.url); | ||
handlerOpts.trgParsed = (0, _tools.parseHostHeader)(request.url); | ||
_this5.stats.connectRequestCount++; | ||
@@ -283,3 +310,3 @@ } else { | ||
result.trgParsed = parsed; | ||
handlerOpts.trgParsed = parsed; | ||
isHttp = true; | ||
@@ -289,3 +316,3 @@ | ||
} | ||
result.trgParsed.port = result.trgParsed.port || DEFAULT_TARGET_PORT; | ||
handlerOpts.trgParsed.port = handlerOpts.trgParsed.port || DEFAULT_TARGET_PORT; | ||
@@ -299,8 +326,8 @@ // Authenticate the request using a user function (if provided) | ||
var funcOpts = { | ||
connectionId: result.id, | ||
connectionId: handlerOpts.id, | ||
request: request, | ||
username: null, | ||
password: null, | ||
hostname: result.trgParsed.hostname, | ||
port: result.trgParsed.port, | ||
hostname: handlerOpts.trgParsed.hostname, | ||
port: handlerOpts.trgParsed.port, | ||
isHttp: isHttp | ||
@@ -321,2 +348,3 @@ }; | ||
} | ||
// User function returns a result directly or a promise | ||
@@ -331,9 +359,9 @@ return _this5.prepareRequestFunction(funcOpts); | ||
if (funcResult && funcResult.upstreamProxyUrl) { | ||
result.upstreamProxyUrlParsed = (0, _tools.parseUrl)(funcResult.upstreamProxyUrl); | ||
handlerOpts.upstreamProxyUrlParsed = (0, _tools.parseUrl)(funcResult.upstreamProxyUrl); | ||
if (result.upstreamProxyUrlParsed) { | ||
if (!result.upstreamProxyUrlParsed.hostname || !result.upstreamProxyUrlParsed.port) { | ||
if (handlerOpts.upstreamProxyUrlParsed) { | ||
if (!handlerOpts.upstreamProxyUrlParsed.hostname || !handlerOpts.upstreamProxyUrlParsed.port) { | ||
throw new Error('Invalid "upstreamProxyUrl" provided: URL must have hostname and port'); | ||
} | ||
if (result.upstreamProxyUrlParsed.scheme !== 'http') { | ||
if (handlerOpts.upstreamProxyUrlParsed.scheme !== 'http') { | ||
throw new Error('Invalid "upstreamProxyUrl" provided: URL must have the "http" scheme'); | ||
@@ -344,7 +372,18 @@ } | ||
if (result.upstreamProxyUrlParsed) { | ||
_this5.log(result.id, 'Using upstream proxy ' + (0, _tools.redactParsedUrl)(result.upstreamProxyUrlParsed)); | ||
if (funcResult && funcResult.customResponseFunc) { | ||
_this5.log(handlerOpts.id, 'Using custom response function'); | ||
handlerOpts.customResponseFunc = funcResult.customResponseFunc; | ||
if (!isHttp) { | ||
throw new Error('The "customResponseFunc" option can only be used for HTTP requests.'); | ||
} | ||
if (typeof handlerOpts.customResponseFunc !== 'function') { | ||
throw new Error('The "customResponseFunc" option must be a function.'); | ||
} | ||
} | ||
return result; | ||
if (handlerOpts.upstreamProxyUrlParsed) { | ||
_this5.log(handlerOpts.id, 'Using upstream proxy ' + (0, _tools.redactParsedUrl)(handlerOpts.upstreamProxyUrlParsed)); | ||
} | ||
return handlerOpts; | ||
}).finally(function () { | ||
@@ -383,4 +422,5 @@ if (_this5.prepareRequestFunction) socket.resume(); | ||
key: 'failRequest', | ||
value: function failRequest(request, err, handlerOptions) { | ||
var handlerId = handlerOptions ? handlerOptions.id : null; | ||
value: function failRequest(request, err, handlerOpts) { | ||
var handlerId = handlerOpts ? handlerOpts.id : null; | ||
if (err.name === REQUEST_ERROR_NAME) { | ||
@@ -394,7 +434,8 @@ this.log(handlerId, 'Request failed (status ' + err.statusCode + '): ' + err.message); | ||
} | ||
// emit connection closed if request fails and connection was already reported | ||
if (handlerOptions) { | ||
if (handlerOpts) { | ||
this.log(handlerId, 'Closed because request failed with error'); | ||
this.emit('connectionClosed', { | ||
connectionId: handlerOptions.id, | ||
connectionId: handlerOpts.id, | ||
stats: { srcTxBytes: 0, srcRxBytes: 0 } | ||
@@ -401,0 +442,0 @@ }); |
@@ -32,13 +32,13 @@ 'use strict'; | ||
function createTunnel(proxyUrl, target) { | ||
function createTunnel(proxyUrl, targetHost) { | ||
var providedOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; | ||
var callback = arguments[3]; | ||
// TODO: More and better validations | ||
var _target$split = target.split(':'), | ||
_target$split2 = _slicedToArray(_target$split, 2), | ||
trgHost = _target$split2[0], | ||
trgPort = _target$split2[1]; | ||
// TODO: More and better validations - yeah, make sure targetHost is really a hostname | ||
var _targetHost$split = targetHost.split(':'), | ||
_targetHost$split2 = _slicedToArray(_targetHost$split, 2), | ||
trgHostname = _targetHost$split2[0], | ||
trgPort = _targetHost$split2[1]; | ||
if (!trgHost || !trgPort) throw new Error('target needs to include both hostname and port.'); | ||
if (!trgHostname || !trgPort) throw new Error('target needs to include both hostname and port.'); | ||
@@ -78,3 +78,3 @@ var parsedProxyUrl = (0, _tools.parseUrl)(proxyUrl); | ||
trgParsed: { | ||
hostname: trgHost, | ||
hostname: trgHostname, | ||
port: trgPort | ||
@@ -112,4 +112,2 @@ }, | ||
}); | ||
}).catch(function (err) { | ||
throw err; | ||
}).nodeify(callback); | ||
@@ -126,3 +124,3 @@ } | ||
if (!port) throw new Error('serverPath must contain port'); | ||
if (!runningServers[port]) resolve(false); | ||
return new _bluebird2.default(function (resolve) { | ||
@@ -129,0 +127,0 @@ if (!runningServers[port]) return resolve(false); |
@@ -5,2 +5,4 @@ 'use strict'; | ||
// TODO: please move this into ./test dir | ||
var server = net.createServer(); | ||
@@ -7,0 +9,0 @@ |
@@ -0,1 +1,5 @@ | ||
0.1.32 / 2018-06-08 | ||
=================== | ||
- Added `customResponseFunc` option to `prepareRequestFunction` to support custom response to HTTP requests | ||
0.1.31 / 2018-05-21 | ||
@@ -9,5 +13,6 @@ =================== | ||
0.1.27 / 2018-03-27 | ||
0.1.28 / 2018-03-27 | ||
=================== | ||
- Added option to create tunnels through http proxies for tcp network connections (eq. connection to mongodb/sql database through http proxy) | ||
- Added `createTunnel()` function to create tunnels through HTTP proxies for arbitrary TCP network connections | ||
(eq. connection to mongodb/sql database through HTTP proxy) | ||
@@ -17,3 +22,3 @@ 0.1.27 / 2018-03-05 | ||
- Better error messages for common network errors | ||
- Pass headers from target socket in https tunnel chains | ||
- Pass headers from target socket in HTTPS tunnel chains | ||
@@ -20,0 +25,0 @@ 0.1.26 / 2018-02-14 |
{ | ||
"name": "proxy-chain", | ||
"version": "0.1.31", | ||
"version": "0.1.32", | ||
"description": "Node.js implementation of a proxy server (think Squid) with support for SSL, authentication, upstream proxy chaining, and protocol tunneling.", | ||
@@ -44,3 +44,3 @@ "main": "build/index.js", | ||
"portastic": "^1.0.1", | ||
"underscore": "^1.8.3" | ||
"underscore": "^1.9.1" | ||
}, | ||
@@ -70,8 +70,8 @@ "devDependencies": { | ||
"request": "^2.83.0", | ||
"sinon": "^2.2.0", | ||
"sinon": "^5.1.0", | ||
"sinon-stub-promise": "^4.0.0", | ||
"through": "^2.3.8", | ||
"ws": "^3.3.1" | ||
"ws": "^5.2.0" | ||
}, | ||
"optionalDependencies": {} | ||
} |
@@ -6,3 +6,4 @@ # Programmable HTTP proxy server for Node.js | ||
Node.js implementation of a proxy server (think Squid) with support for SSL, authentication and upstream proxy chaining. | ||
Node.js implementation of a proxy server (think Squid) with support for SSL, authentication, upstream proxy chaining | ||
and custom HTTP responses. | ||
The authentication and proxy chaining configuration is defined in code and can be dynamic. | ||
@@ -72,6 +73,60 @@ Note that the proxy server only supports Basic authentication | ||
server.listen(() => { | ||
console.log(`Proxy server is listening on port ${8000}`); | ||
console.log(`Proxy server is listening on port ${server.port}`); | ||
}); | ||
``` | ||
## Run a HTTP proxy server with custom responses | ||
Custom responses allow you to override the response to a HTTP requests to the proxy, without contacting any target hoste. | ||
For example, this is useful if you want to provide a HTTP proxy-style interface | ||
to an external API or respond with some custom page to certain requests. | ||
Note that this feature is only available for HTTP connections. That's because HTTPS | ||
connections cannot be intercepted without access to target host's private key. | ||
To provide a custom response, the result of the `prepareRequestFunction` function must | ||
define the `prepareRequestFunction` property, which contains a function that generates the custom response. | ||
The function must return an object (or a promise resolving to an object) with the following properties: | ||
```javascript | ||
{ | ||
// Optional HTTP status code of the response. By default it is 200. | ||
statusCode: 200, | ||
// Optional HTTP headers of the response | ||
headers: {} | ||
'X-My-Header': 'bla bla', | ||
} | ||
// Optional string with the body of the HTTP response | ||
body: 'My custom response', | ||
// Optional encoding of the body. If not provided, defaults to 'UTF-8' | ||
encoding: 'UTF-8', | ||
} | ||
``` | ||
Here is a simple example: | ||
```javascript | ||
const ProxyChain = require('proxy-chain'); | ||
const server = new ProxyChain.Server({ | ||
port: 8000, | ||
prepareRequestFunction: ({ request, username, password, hostname, port, isHttp }) => { | ||
return { | ||
customResponseFunc: () => { | ||
return { | ||
statusCode: 200, | ||
body: `My custom response to ${request.url}', | ||
} | ||
}, | ||
}; | ||
}, | ||
}); | ||
server.listen(() => { | ||
console.log(`Proxy server is listening on port ${server.port}`); | ||
}); | ||
``` | ||
## Closing the server | ||
@@ -118,17 +173,23 @@ | ||
### `createTunnel(proxyUrl, target, options, callback)` | ||
### `createTunnel(proxyUrl, targetHost, options, callback)` | ||
Attempts to create a network tunnel through proxy server specified in param "proxyUrl" to a network service | ||
specified in param "target". | ||
Creates a TCP tunnel to `targetHost` that goes through a HTTP proxy server | ||
specified by the `proxyUrl` parameter. | ||
The function takes optional callback that receives the path to local service. | ||
The result of the function is local endpoint in a form of `hostname:port`. | ||
All TCP connections made to the local endpoint will be tunneled through the proxy to the target host and port. | ||
For example, this is useful if you want to access a certain service from a specific IP address. | ||
The tunnel should be eventually closed by calling the `closeTunnel()` function. | ||
The `createTunnel()` function accepts an optional Node.js-style callback that receives the path to the local endpoint. | ||
If no callback is supplied, the function returns a promise that resolves to a String with | ||
the path to local service. | ||
the path to the local endpoint. | ||
Example usage: | ||
Example: | ||
```javascript | ||
const tunnel = await createTunnel('http://<username>:<password>@<proxy-server>:<port>', '<service-host>:<service-port>'); | ||
// tunnel will be in format "localhost:<randomly-assigned-port>" and while it's running | ||
// it can be used instead of '<service-host>:<service-port>' to proxy requests. | ||
const host = await createTunnel('http://bob:pass123@proxy.example.com:8000', 'service.example.com:356'); | ||
// Prints something like "localhost:56836" | ||
console.log(host); | ||
``` | ||
@@ -139,8 +200,8 @@ | ||
Closes tunnel previously started by `createTunnel()`. | ||
Returns false if tunnel is not found or running. Otherwise the result is `true`. | ||
The result value is `false` if the tunnel was not found or was already closed, otherwise it is `true`. | ||
The `closeConnections` parameter indicates whether pending connections are forcibly closed. | ||
The function takes optional callback that receives the result Boolean from the function. | ||
If callback is not provided, the function returns a promise instead. | ||
The function takes an optional callback that receives the result of the function. | ||
If the callback is not provided, the function returns a promise instead. | ||
@@ -147,0 +208,0 @@ ### `parseUrl(url)` |
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
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
114559
17
1785
220
Updatedunderscore@^1.9.1