mirror of
https://github.com/http-party/node-http-proxy.git
synced 2025-12-08 20:59:18 +00:00
[api breaking] Begin refactor to optimize node-http-proxy by managing one instance of HttpProxy per host:port location
This commit is contained in:
parent
db10c4af91
commit
d2b0e4399e
@ -28,7 +28,6 @@ var util = require('util'),
|
||||
http = require('http'),
|
||||
https = require('https'),
|
||||
events = require('events'),
|
||||
ProxyTable = require('./proxy-table').ProxyTable,
|
||||
maxSockets = 100;
|
||||
|
||||
//
|
||||
@ -37,110 +36,11 @@ var util = require('util'),
|
||||
require('pkginfo')(module, 'version');
|
||||
|
||||
//
|
||||
// Track our own list of agents internal to `node-http-proxy`
|
||||
// ### Export the relevant objects exposed by `node-http-proxy`
|
||||
//
|
||||
var _agents = {};
|
||||
|
||||
//
|
||||
// ### function _getAgent (host, port, secure)
|
||||
// #### @host {string} Host of the agent to get
|
||||
// #### @port {number} Port of the agent to get
|
||||
// #### @secure {boolean} Value indicating whether or not to use HTTPS
|
||||
// Retreives an agent from the `http` or `https` module
|
||||
// and sets the `maxSockets` property appropriately.
|
||||
//
|
||||
function _getAgent (host, port, secure) {
|
||||
var Agent, id = [host, port].join(':');
|
||||
|
||||
if (!port) {
|
||||
port = secure ? 443 : 80;
|
||||
}
|
||||
|
||||
if (!_agents[id]) {
|
||||
Agent = secure ? https.Agent : http.Agent;
|
||||
|
||||
_agents[id] = new Agent({
|
||||
host: host,
|
||||
port: port
|
||||
});
|
||||
|
||||
_agents[id].maxSockets = maxSockets;
|
||||
}
|
||||
|
||||
return _agents[id];
|
||||
}
|
||||
|
||||
//
|
||||
// ### function _getProtocol (secure, outgoing)
|
||||
// #### @secure {Object|boolean} Settings for `https`
|
||||
// #### @outgoing {Object} Outgoing request options
|
||||
// Returns the appropriate protocol based on the settings in
|
||||
// `secure`. If the protocol is `https` this function will update
|
||||
// the options in `outgoing` as appropriate by adding `ca`, `key`,
|
||||
// and `cert` if they exist in `secure`.
|
||||
//
|
||||
function _getProtocol (secure, outgoing) {
|
||||
var protocol = secure ? https : http;
|
||||
|
||||
if (typeof secure === 'object') {
|
||||
outgoing = outgoing || {};
|
||||
['ca', 'cert', 'key'].forEach(function (prop) {
|
||||
if (secure[prop]) {
|
||||
outgoing[prop] = secure[prop];
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return protocol;
|
||||
}
|
||||
|
||||
//
|
||||
// ### function getMaxSockets ()
|
||||
// Returns the maximum number of sockets
|
||||
// allowed on __every__ outgoing request
|
||||
// made by __all__ instances of `HttpProxy`
|
||||
//
|
||||
exports.getMaxSockets = function () {
|
||||
return maxSockets;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function setMaxSockets ()
|
||||
// Sets the maximum number of sockets
|
||||
// allowed on __every__ outgoing request
|
||||
// made by __all__ instances of `HttpProxy`
|
||||
//
|
||||
exports.setMaxSockets = function (value) {
|
||||
maxSockets = value;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function stack (middlewares, proxy)
|
||||
// adapted from https://github.com/creationix/stack
|
||||
//
|
||||
exports.stack = function stack (middlewares, proxy) {
|
||||
var handle;
|
||||
middlewares.reverse().forEach(function (layer) {
|
||||
var child = handle;
|
||||
handle = function (req, res) {
|
||||
var next = function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
//
|
||||
// TODO: figure out where to send errors.
|
||||
// return error(req, res, err);
|
||||
//
|
||||
}
|
||||
child(req, res);
|
||||
}
|
||||
|
||||
next.__proto__ = proxy;
|
||||
layer(req, res, next);
|
||||
};
|
||||
});
|
||||
|
||||
return handle;
|
||||
}
|
||||
var HttpProxy = exports.HttpProxy = require('./node-http-proxy/http-proxy').HttpProxy,
|
||||
ProxyTable = exports.ProxyTable = require('./node-http-proxy/proxy-table').ProxyTable,
|
||||
RoutingProxy = exports.RoutingProxy = require('./node-http-proxy/routing-proxy').RoutingProxy;
|
||||
|
||||
//
|
||||
// ### function createServer ([port, host, options, handler])
|
||||
@ -255,59 +155,6 @@ exports.createServer = function () {
|
||||
return server;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function HttpProxy (options)
|
||||
// #### @options {Object} Options for this instance.
|
||||
// Constructor function for new instances of HttpProxy responsible
|
||||
// for managing the life-cycle of streaming reverse proxyied HTTP requests.
|
||||
//
|
||||
// Example options:
|
||||
//
|
||||
// {
|
||||
// router: {
|
||||
// 'foo.com': 'localhost:8080',
|
||||
// 'bar.com': 'localhost:8081'
|
||||
// },
|
||||
// forward: {
|
||||
// host: 'localhost',
|
||||
// port: 9001
|
||||
// }
|
||||
// }
|
||||
//
|
||||
var HttpProxy = exports.HttpProxy = function (options) {
|
||||
events.EventEmitter.call(this);
|
||||
|
||||
var self = this;
|
||||
options = options || {};
|
||||
|
||||
//
|
||||
// Setup basic proxying options
|
||||
//
|
||||
this.https = options.https;
|
||||
this.forward = options.forward;
|
||||
this.target = options.target || {};
|
||||
|
||||
//
|
||||
// Setup additional options for WebSocket proxying. When forcing
|
||||
// the WebSocket handshake to change the `sec-websocket-location`
|
||||
// and `sec-websocket-origin` headers `options.source` **MUST**
|
||||
// be provided or the operation will fail with an `origin mismatch`
|
||||
// by definition.
|
||||
//
|
||||
this.source = options.source || { host: 'localhost', port: 8000 };
|
||||
this.changeOrigin = options.changeOrigin || false;
|
||||
|
||||
if (options.router) {
|
||||
this.proxyTable = new ProxyTable(options.router, options.silent, options.hostnameOnly);
|
||||
this.proxyTable.on('routes', function (routes) {
|
||||
self.emit('routes', routes);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Inherit from events.EventEmitter
|
||||
util.inherits(HttpProxy, events.EventEmitter);
|
||||
|
||||
//
|
||||
// ### function buffer (obj)
|
||||
// #### @obj {Object} Object to pause events from
|
||||
@ -328,8 +175,10 @@ util.inherits(HttpProxy, events.EventEmitter);
|
||||
// This simply chooses to manage the scope of the events on a new Object literal as opposed to
|
||||
// [on the HttpProxy instance](https://github.com/nodejitsu/node-http-proxy/blob/v0.3.1/lib/node-http-proxy.js#L154).
|
||||
//
|
||||
HttpProxy.prototype.buffer = function (obj) {
|
||||
var onData, onEnd, events = [];
|
||||
exports.buffer = function (obj) {
|
||||
var events = [],
|
||||
onData,
|
||||
onEnd;
|
||||
|
||||
obj.on('data', onData = function (data, encoding) {
|
||||
events.push(['data', data, encoding]);
|
||||
@ -354,567 +203,116 @@ HttpProxy.prototype.buffer = function (obj) {
|
||||
};
|
||||
|
||||
//
|
||||
// ### function close ()
|
||||
// Frees the resources associated with this instance,
|
||||
// if they exist.
|
||||
// ### function getMaxSockets ()
|
||||
// Returns the maximum number of sockets
|
||||
// allowed on __every__ outgoing request
|
||||
// made by __all__ instances of `HttpProxy`
|
||||
//
|
||||
HttpProxy.prototype.close = function () {
|
||||
if (this.proxyTable) {
|
||||
this.proxyTable.close();
|
||||
}
|
||||
exports.getMaxSockets = function () {
|
||||
return maxSockets;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function proxyRequest (req, res, [port, host, paused])
|
||||
// #### @req {ServerRequest} Incoming HTTP Request to proxy.
|
||||
// #### @res {ServerResponse} Outgoing HTTP Request to write proxied data to.
|
||||
// #### @options {Object} Options for the outgoing proxy request.
|
||||
// ### function setMaxSockets ()
|
||||
// Sets the maximum number of sockets
|
||||
// allowed on __every__ outgoing request
|
||||
// made by __all__ instances of `HttpProxy`
|
||||
//
|
||||
// options.port {number} Port to use on the proxy target host.
|
||||
// options.host {string} Host of the proxy target.
|
||||
// options.buffer {Object} Result from `httpProxy.buffer(req)`
|
||||
// options.https {Object|boolean} Settings for https.
|
||||
// options.enableXForwarded {boolean} Don't clobber x-forwarded headers to allow layered proxies.
|
||||
//
|
||||
HttpProxy.prototype.proxyRequest = function (req, res, options) {
|
||||
var self = this, errState = false, location, outgoing, protocol, reverseProxy;
|
||||
|
||||
//
|
||||
// Create an empty options hash if none is passed.
|
||||
// If default options have been passed to the constructor
|
||||
// of this instance, use them by default.
|
||||
//
|
||||
options = options || {};
|
||||
options.host = options.host || this.target.host;
|
||||
options.port = options.port || this.target.port;
|
||||
options.enableXForwarded =
|
||||
(undefined === options.enableXForwarded ? true : options.enableXForwarded);
|
||||
|
||||
//
|
||||
// Check the proxy table for this instance to see if we need
|
||||
// to get the proxy location for the request supplied. We will
|
||||
// always ignore the proxyTable if an explicit `port` and `host`
|
||||
// arguments are supplied to `proxyRequest`.
|
||||
//
|
||||
if (this.proxyTable && !options.host) {
|
||||
location = this.proxyTable.getProxyLocation(req);
|
||||
|
||||
//
|
||||
// If no location is returned from the ProxyTable instance
|
||||
// then respond with `404` since we do not have a valid proxy target.
|
||||
//
|
||||
if (!location) {
|
||||
res.writeHead(404);
|
||||
return res.end();
|
||||
}
|
||||
|
||||
//
|
||||
// When using the ProxyTable in conjunction with an HttpProxy instance
|
||||
// only the following arguments are valid:
|
||||
//
|
||||
// * `proxy.proxyRequest(req, res, { host: 'localhost' })`: This will be skipped
|
||||
// * `proxy.proxyRequest(req, res, { buffer: buffer })`: Buffer will get updated appropriately
|
||||
// * `proxy.proxyRequest(req, res)`: Options will be assigned appropriately.
|
||||
//
|
||||
options.port = location.port;
|
||||
options.host = location.host;
|
||||
}
|
||||
|
||||
//
|
||||
// Add common proxy headers to the request so that they can
|
||||
// be availible to the proxy target server:
|
||||
//
|
||||
// * `x-forwarded-for`: IP Address of the original request
|
||||
// * `x-forwarded-proto`: Protocol of the original request
|
||||
// * `x-forwarded-port`: Port of the original request.
|
||||
//
|
||||
if (options.enableXForwarded === true) {
|
||||
req.headers['x-forwarded-for'] = req.connection.remoteAddress || req.connection.socket.remoteAddress;
|
||||
req.headers['x-forwarded-port'] = req.connection.remotePort || req.connection.socket.remotePort;
|
||||
req.headers['x-forwarded-proto'] = res.connection.pair ? 'https' : 'http';
|
||||
}
|
||||
|
||||
//
|
||||
// Emit the `start` event indicating that we have begun the proxy operation.
|
||||
//
|
||||
this.emit('start', req, res, options);
|
||||
|
||||
//
|
||||
// If forwarding is enabled for this instance, foward proxy the
|
||||
// specified request to the address provided in `this.forward`
|
||||
//
|
||||
if (this.forward) {
|
||||
this.emit('forward', req, res, this.forward);
|
||||
this._forwardRequest(req);
|
||||
}
|
||||
|
||||
//
|
||||
// #### function proxyError (err)
|
||||
// #### @err {Error} Error contacting the proxy target
|
||||
// Short-circuits `res` in the event of any error when
|
||||
// contacting the proxy target at `host` / `port`.
|
||||
//
|
||||
function proxyError(err) {
|
||||
errState = true;
|
||||
|
||||
//
|
||||
// Emit an `error` event, allowing the application to use custom
|
||||
// error handling. The error handler should end the response.
|
||||
//
|
||||
if (self.emit('proxyError', err, req, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
||||
|
||||
if (req.method !== 'HEAD') {
|
||||
//
|
||||
// This NODE_ENV=production behavior is mimics Express and
|
||||
// Connect.
|
||||
//
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
res.write('Internal Server Error');
|
||||
}
|
||||
else {
|
||||
res.write('An error has occurred: ' + JSON.stringify(err));
|
||||
}
|
||||
}
|
||||
|
||||
res.end();
|
||||
}
|
||||
|
||||
outgoing = {
|
||||
host: options.host,
|
||||
port: options.port,
|
||||
agent: _getAgent(options.host, options.port, options.https || this.target.https),
|
||||
method: req.method,
|
||||
path: req.url,
|
||||
headers: req.headers
|
||||
};
|
||||
|
||||
protocol = _getProtocol(options.https || this.target.https, outgoing);
|
||||
|
||||
// Open new HTTP request to internal resource with will act as a reverse proxy pass
|
||||
reverseProxy = protocol.request(outgoing, function (response) {
|
||||
|
||||
// Process the `reverseProxy` `response` when it's received.
|
||||
if (response.headers.connection) {
|
||||
if (req.headers.connection) response.headers.connection = req.headers.connection;
|
||||
else response.headers.connection = 'close';
|
||||
}
|
||||
|
||||
// Set the headers of the client response
|
||||
res.writeHead(response.statusCode, response.headers);
|
||||
|
||||
// `response.statusCode === 304`: No 'data' event and no 'end'
|
||||
if (response.statusCode === 304) {
|
||||
return res.end();
|
||||
}
|
||||
|
||||
// For each data `chunk` received from the `reverseProxy`
|
||||
// `response` write it to the outgoing `res`.
|
||||
// If the res socket has been killed already, then write()
|
||||
// will throw. Nevertheless, try our best to end it nicely.
|
||||
response.on('data', function (chunk) {
|
||||
if (req.method !== 'HEAD' && res.writable) {
|
||||
try {
|
||||
res.write(chunk);
|
||||
} catch (er) {
|
||||
try {
|
||||
res.end();
|
||||
} catch (er) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// When the `reverseProxy` `response` ends, end the
|
||||
// corresponding outgoing `res` unless we have entered
|
||||
// an error state. In which case, assume `res.end()` has
|
||||
// already been called and the 'error' event listener
|
||||
// removed.
|
||||
response.on('end', function () {
|
||||
if (!errState) {
|
||||
reverseProxy.removeListener('error', proxyError);
|
||||
res.end();
|
||||
|
||||
// Emit the `end` event now that we have completed proxying
|
||||
self.emit('end', req, res);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Handle 'error' events from the `reverseProxy`.
|
||||
reverseProxy.once('error', proxyError);
|
||||
|
||||
// For each data `chunk` received from the incoming
|
||||
// `req` write it to the `reverseProxy` request.
|
||||
req.on('data', function (chunk) {
|
||||
if (!errState) {
|
||||
reverseProxy.write(chunk);
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// When the incoming `req` ends, end the corresponding `reverseProxy`
|
||||
// request unless we have entered an error state.
|
||||
//
|
||||
req.on('end', function () {
|
||||
if (!errState) {
|
||||
reverseProxy.end();
|
||||
}
|
||||
});
|
||||
|
||||
// If we have been passed buffered data, resume it.
|
||||
if (options.buffer && !errState) {
|
||||
options.buffer.resume();
|
||||
}
|
||||
exports.setMaxSockets = function (value) {
|
||||
maxSockets = value;
|
||||
};
|
||||
|
||||
//
|
||||
// ### @private function _forwardRequest (req)
|
||||
// #### @req {ServerRequest} Incoming HTTP Request to proxy.
|
||||
// Forwards the specified `req` to the location specified
|
||||
// by `this.forward` ignoring errors and the subsequent response.
|
||||
// ### function stack (middlewares, proxy)
|
||||
// adapted from https://github.com/creationix/stack
|
||||
//
|
||||
HttpProxy.prototype._forwardRequest = function (req) {
|
||||
var self = this, port, host, outgoing, protocol, forwardProxy;
|
||||
exports.stack = function stack (middlewares, proxy) {
|
||||
var handle;
|
||||
middlewares.reverse().forEach(function (layer) {
|
||||
var child = handle;
|
||||
handle = function (req, res) {
|
||||
var next = function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
//
|
||||
// TODO: figure out where to send errors.
|
||||
// return error(req, res, err);
|
||||
//
|
||||
}
|
||||
child(req, res);
|
||||
}
|
||||
|
||||
port = this.forward.port;
|
||||
host = this.forward.host;
|
||||
|
||||
outgoing = {
|
||||
host: host,
|
||||
port: port,
|
||||
agent: _getAgent(host, port, this.forward.https),
|
||||
method: req.method,
|
||||
path: req.url,
|
||||
headers: req.headers
|
||||
};
|
||||
|
||||
// Force the `connection` header to be 'close' until
|
||||
// node.js core re-implements 'keep-alive'.
|
||||
outgoing.headers['connection'] = 'close';
|
||||
|
||||
protocol = _getProtocol(this.forward.https, outgoing);
|
||||
|
||||
// Open new HTTP request to internal resource with will act as a reverse proxy pass
|
||||
forwardProxy = protocol.request(outgoing, function (response) {
|
||||
//
|
||||
// Ignore the response from the forward proxy since this is a 'fire-and-forget' proxy.
|
||||
// Remark (indexzero): We will eventually emit a 'forward' event here for performance tuning.
|
||||
//
|
||||
next.__proto__ = proxy;
|
||||
layer(req, res, next);
|
||||
};
|
||||
});
|
||||
|
||||
// Add a listener for the connection timeout event.
|
||||
//
|
||||
// Remark: Ignoring this error in the event
|
||||
// forward target doesn't exist.
|
||||
//
|
||||
forwardProxy.once('error', function (err) { });
|
||||
|
||||
// Chunk the client request body as chunks from the proxied request come in
|
||||
req.on('data', function (chunk) {
|
||||
forwardProxy.write(chunk);
|
||||
})
|
||||
|
||||
// At the end of the client request, we are going to stop the proxied request
|
||||
req.on('end', function () {
|
||||
forwardProxy.end();
|
||||
});
|
||||
return handle;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function proxyWebSocketRequest (req, socket, head, options)
|
||||
// #### @req {ServerRequest} Websocket request to proxy.
|
||||
// #### @socket {net.Socket} Socket for the underlying HTTP request
|
||||
// #### @head {string} Headers for the Websocket request.
|
||||
// #### @options {Object} Options to use when proxying this request.
|
||||
// ### function _getAgent (host, port, secure)
|
||||
// #### @options {Object} Options to use when creating the agent.
|
||||
//
|
||||
// options.port {number} Port to use on the proxy target host.
|
||||
// options.host {string} Host of the proxy target.
|
||||
// options.buffer {Object} Result from `httpProxy.buffer(req)`
|
||||
// options.https {Object|boolean} Settings for https.
|
||||
// {
|
||||
// host: 'localhost',
|
||||
// port: 9000,
|
||||
// https: true,
|
||||
// maxSockets: 100
|
||||
// }
|
||||
//
|
||||
HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, options) {
|
||||
var self = this,
|
||||
listeners = {},
|
||||
errState = false,
|
||||
CRLF = '\r\n',
|
||||
outgoing;
|
||||
|
||||
options = options || {};
|
||||
options.host = options.host || this.target.host;
|
||||
options.port = options.port || this.target.port;
|
||||
|
||||
if (this.proxyTable && !options.host) {
|
||||
location = this.proxyTable.getProxyLocation(req);
|
||||
|
||||
if (!location) {
|
||||
return socket.destroy();
|
||||
}
|
||||
|
||||
options.port = location.port;
|
||||
options.host = location.host;
|
||||
// Createsan agent from the `http` or `https` module
|
||||
// and sets the `maxSockets` property appropriately.
|
||||
//
|
||||
exports._getAgent = function _getAgent (options) {
|
||||
if (!options || !options.host) {
|
||||
throw new Error('`options.host` is required to create an Agent.');
|
||||
}
|
||||
|
||||
//
|
||||
// WebSocket requests must have the `GET` method and
|
||||
// the `upgrade:websocket` header
|
||||
//
|
||||
if (req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket') {
|
||||
//
|
||||
// This request is not WebSocket request
|
||||
//
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Helper function for setting appropriate socket values:
|
||||
// 1. Turn of all bufferings
|
||||
// 2. For server set KeepAlive
|
||||
// 3. For client set encoding
|
||||
//
|
||||
function _socket(socket, keepAlive) {
|
||||
socket.setTimeout(0);
|
||||
socket.setNoDelay(true);
|
||||
if (keepAlive) {
|
||||
if (socket.setKeepAlive) {
|
||||
socket.setKeepAlive(true, 0);
|
||||
}
|
||||
else if (socket.pair.cleartext.socket.setKeepAlive) {
|
||||
socket.pair.cleartext.socket.setKeepAlive(true, 0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
socket.setEncoding('utf8');
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// On `upgrade` from the Agent socket, listen to
|
||||
// the appropriate events.
|
||||
//
|
||||
function onUpgrade (reverseProxy, proxySocket) {
|
||||
if (!reverseProxy) {
|
||||
proxySocket.end();
|
||||
socket.end();
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Any incoming data on this WebSocket to the proxy target
|
||||
// will be written to the `reverseProxy` socket.
|
||||
//
|
||||
proxySocket.on('data', listeners.onIncoming = function (data) {
|
||||
if (reverseProxy.incoming.socket.writable) {
|
||||
try {
|
||||
self.emit('websocket:outgoing', req, socket, head, data);
|
||||
reverseProxy.incoming.socket.write(data);
|
||||
}
|
||||
catch (e) {
|
||||
reverseProxy.incoming.socket.end();
|
||||
proxySocket.end();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Any outgoing data on this Websocket from the proxy target
|
||||
// will be written to the `proxySocket` socket.
|
||||
//
|
||||
reverseProxy.incoming.socket.on('data', listeners.onOutgoing = function(data) {
|
||||
try {
|
||||
self.emit('websocket:incoming', reverseProxy, reverseProxy.incoming, head, data);
|
||||
proxySocket.write(data);
|
||||
}
|
||||
catch (e) {
|
||||
proxySocket.end();
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Helper function to detach all event listeners
|
||||
// from `reverseProxy` and `proxySocket`.
|
||||
//
|
||||
function detach() {
|
||||
proxySocket.removeListener('end', listeners.onIncomingClose);
|
||||
proxySocket.removeListener('data', listeners.onIncoming);
|
||||
reverseProxy.incoming.socket.removeListener('end', listeners.onOutgoingClose);
|
||||
reverseProxy.incoming.socket.removeListener('data', listeners.onOutgoing);
|
||||
}
|
||||
|
||||
//
|
||||
// If the incoming `proxySocket` socket closes, then
|
||||
// detach all event listeners.
|
||||
//
|
||||
proxySocket.on('end', listeners.onIncomingClose = function() {
|
||||
reverseProxy.incoming.socket.end();
|
||||
detach();
|
||||
|
||||
// Emit the `end` event now that we have completed proxying
|
||||
self.emit('websocket:end', req, socket, head);
|
||||
});
|
||||
|
||||
//
|
||||
// If the `reverseProxy` socket closes, then detach all
|
||||
// event listeners.
|
||||
//
|
||||
reverseProxy.incoming.socket.on('end', listeners.onOutgoingClose = function() {
|
||||
proxySocket.end();
|
||||
detach();
|
||||
});
|
||||
};
|
||||
|
||||
// Setup the incoming client socket.
|
||||
_socket(socket);
|
||||
|
||||
function getPort (port) {
|
||||
port = port || 80;
|
||||
return port - 80 === 0 ? '' : ':' + port
|
||||
}
|
||||
|
||||
//
|
||||
// Get the protocol, and host for this request and create an instance
|
||||
// of `http.Agent` or `https.Agent` from the pool managed by `node-http-proxy`.
|
||||
//
|
||||
var protocolName = options.https || this.target.https ? 'https' : 'http',
|
||||
portUri = getPort(this.source.port),
|
||||
remoteHost = options.host + portUri,
|
||||
agent = _getAgent(options.host, options.port, options.https || this.target.https);
|
||||
|
||||
// Change headers (if requested).
|
||||
if (this.changeOrigin) {
|
||||
req.headers.host = remoteHost;
|
||||
req.headers.origin = protocolName + '://' + remoteHost;
|
||||
}
|
||||
|
||||
//
|
||||
// Make the outgoing WebSocket request
|
||||
//
|
||||
outgoing = {
|
||||
host: options.host,
|
||||
port: options.port,
|
||||
method: 'GET',
|
||||
path: req.url,
|
||||
headers: req.headers,
|
||||
};
|
||||
|
||||
var reverseProxy = agent.appendMessage(outgoing);
|
||||
|
||||
//
|
||||
// On any errors from the `reverseProxy` emit the
|
||||
// `webSocketProxyError` and close the appropriate
|
||||
// connections.
|
||||
//
|
||||
function proxyError (err) {
|
||||
reverseProxy.end();
|
||||
if (self.emit('webSocketProxyError', req, socket, head)) {
|
||||
return;
|
||||
}
|
||||
|
||||
socket.end();
|
||||
if (!options.port) {
|
||||
options.port = options.https ? 443 : 80;
|
||||
}
|
||||
|
||||
//
|
||||
// Here we set the incoming `req`, `socket` and `head` data to the outgoing
|
||||
// request so that we can reuse this data later on in the closure scope
|
||||
// available to the `upgrade` event. This bookkeeping is not tracked anywhere
|
||||
// in nodejs core and is **very** specific to proxying WebSockets.
|
||||
//
|
||||
reverseProxy.agent = agent;
|
||||
reverseProxy.incoming = {
|
||||
request: req,
|
||||
socket: socket,
|
||||
head: head
|
||||
};
|
||||
var Agent = options.https ? https.Agent : http.Agent,
|
||||
agent;
|
||||
|
||||
//
|
||||
// If the agent for this particular `host` and `port` combination
|
||||
// is not already listening for the `upgrade` event, then do so once.
|
||||
// This will force us not to disconnect.
|
||||
//
|
||||
// In addition, it's important to note the closure scope here. Since
|
||||
// there is no mapping of the
|
||||
//
|
||||
if (!agent._events || agent._events['upgrade'].length === 0) {
|
||||
agent.on('upgrade', function (_, remoteSocket, head) {
|
||||
//
|
||||
// Prepare the socket for the reverseProxy request and begin to
|
||||
// stream data between the two sockets. Here it is important to
|
||||
// note that `remoteSocket._httpMessage === reverseProxy`.
|
||||
//
|
||||
_socket(remoteSocket, true);
|
||||
onUpgrade(remoteSocket._httpMessage, remoteSocket);
|
||||
});
|
||||
}
|
||||
agent = new Agent({
|
||||
host: options.host,
|
||||
port: options.port
|
||||
});
|
||||
|
||||
//
|
||||
// If the reverseProxy connection has an underlying socket,
|
||||
// then execute the WebSocket handshake.
|
||||
//
|
||||
if (typeof reverseProxy.socket !== 'undefined') {
|
||||
reverseProxy.socket.on('data', function handshake (data) {
|
||||
//
|
||||
// Ok, kind of harmfull part of code. Socket.IO sends a hash
|
||||
// at the end of handshake if protocol === 76, but we need
|
||||
// to replace 'host' and 'origin' in response so we split
|
||||
// data to printable data and to non-printable. (Non-printable
|
||||
// will come after double-CRLF).
|
||||
//
|
||||
var sdata = data.toString();
|
||||
agent.maxSockets = options.maxSockets || maxSockets;
|
||||
|
||||
// Get the Printable data
|
||||
sdata = sdata.substr(0, sdata.search(CRLF + CRLF));
|
||||
return agent;
|
||||
}
|
||||
|
||||
// Get the Non-Printable data
|
||||
data = data.slice(Buffer.byteLength(sdata), data.length);
|
||||
|
||||
if (self.https && !self.target.https) {
|
||||
//
|
||||
// If the proxy server is running HTTPS but the client is running
|
||||
// HTTP then replace `ws` with `wss` in the data sent back to the client.
|
||||
//
|
||||
sdata = sdata.replace('ws:', 'wss:');
|
||||
}
|
||||
|
||||
try {
|
||||
//
|
||||
// Write the printable and non-printable data to the socket
|
||||
// from the original incoming request.
|
||||
//
|
||||
self.emit('websocket:handshake', req, socket, head, sdata, data);
|
||||
socket.write(sdata);
|
||||
socket.write(data);
|
||||
}
|
||||
catch (ex) {
|
||||
proxyError(ex)
|
||||
}
|
||||
|
||||
// Catch socket errors
|
||||
socket.on('error', proxyError);
|
||||
|
||||
// Remove data listener now that the 'handshake' is complete
|
||||
reverseProxy.socket.removeListener('data', handshake);
|
||||
});
|
||||
}
|
||||
|
||||
reverseProxy.on('error', proxyError);
|
||||
|
||||
try {
|
||||
//
|
||||
// Attempt to write the upgrade-head to the reverseProxy request.
|
||||
//
|
||||
reverseProxy.write(head);
|
||||
}
|
||||
catch (ex) {
|
||||
proxyError(ex);
|
||||
}
|
||||
|
||||
//
|
||||
// If we have been passed buffered data, resume it.
|
||||
//
|
||||
if (options.buffer && !errState) {
|
||||
options.buffer.resume();
|
||||
}
|
||||
//
|
||||
// ### function _getProtocol (options)
|
||||
// #### @options {Object} Options for the proxy target.
|
||||
// Returns the appropriate node.js core protocol module (i.e. `http` or `https`)
|
||||
// based on the `options` supplied.
|
||||
//
|
||||
exports._getProtocol = function _getProtocol (options) {
|
||||
return options.https ? https : http;
|
||||
};
|
||||
|
||||
//
|
||||
// ### function _getBase (options)
|
||||
// #### @options {Object} Options for the proxy target.
|
||||
// Returns the relevate base object to create on outgoing proxy request.
|
||||
// If `options.https` are supplied, this function respond with an object
|
||||
// containing the relevant `ca`, `key`, and `cert` properties.
|
||||
//
|
||||
exports._getBase = function _getBase (options) {
|
||||
var result = {};
|
||||
|
||||
if (typeof options.https === 'object') {
|
||||
['ca', 'cert', 'key'].forEach(function (key) {
|
||||
if (options.https[key]) {
|
||||
result[key] = options.https[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
624
lib/node-http-proxy/http-proxy.js
Normal file
624
lib/node-http-proxy/http-proxy.js
Normal file
@ -0,0 +1,624 @@
|
||||
/*
|
||||
node-http-proxy.js: http proxy for node.js
|
||||
|
||||
Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Marak Squires, Fedor Indutny
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
var events = require('events'),
|
||||
util = require('util'),
|
||||
httpProxy = require('../node-http-proxy');
|
||||
|
||||
//
|
||||
// ### function HttpProxy (options)
|
||||
// #### @options {Object} Options for this instance.
|
||||
// Constructor function for new instances of HttpProxy responsible
|
||||
// for managing the life-cycle of streaming reverse proxyied HTTP requests.
|
||||
//
|
||||
// Example options:
|
||||
//
|
||||
// {
|
||||
// target: {
|
||||
// host: 'localhost',
|
||||
// port: 9000
|
||||
// },
|
||||
// forward: {
|
||||
// host: 'localhost',
|
||||
// port: 9001
|
||||
// }
|
||||
// }
|
||||
//
|
||||
var HttpProxy = exports.HttpProxy = function (options) {
|
||||
if (!options || !options.target) {
|
||||
throw new Error('Both `options` and `options.target` are required.');
|
||||
}
|
||||
|
||||
events.EventEmitter.call(this);
|
||||
|
||||
var self = this;
|
||||
|
||||
//
|
||||
// Setup basic proxying options:
|
||||
//
|
||||
// * forward {Object} Options for a forward-proxy (if-any)
|
||||
// * target {Object} Options for the **sole** proxy target of this instance
|
||||
//
|
||||
this.forward = options.forward;
|
||||
this.target = options.target;
|
||||
|
||||
//
|
||||
// Setup the necessary instances instance variables for
|
||||
// the `target` and `forward` `host:port` combinations
|
||||
// used by this instance.
|
||||
//
|
||||
// * agent {http[s].Agent} Agent to be used by this instance.
|
||||
// * protocol {http|https} Core node.js module to make requests with.
|
||||
// * base {Object} Base object to create when proxying containing any https settings.
|
||||
//
|
||||
function setupProxy (key) {
|
||||
self[key].agent = httpProxy._getAgent(self[key]);
|
||||
self[key].protocol = httpProxy._getProtocol(self[key]);
|
||||
self[key].base = httpProxy._getBase(self[key]);
|
||||
}
|
||||
|
||||
setupProxy('target')
|
||||
if (this.forward) { setupProxy('forward') }
|
||||
|
||||
//
|
||||
// Setup opt-in features
|
||||
//
|
||||
this.enable = options.enable || {};
|
||||
this.enable.xforward = typeof this.enable.xforward === 'boolean'
|
||||
? this.enable.xforward
|
||||
: true;
|
||||
|
||||
//
|
||||
// Setup additional options for WebSocket proxying. When forcing
|
||||
// the WebSocket handshake to change the `sec-websocket-location`
|
||||
// and `sec-websocket-origin` headers `options.source` **MUST**
|
||||
// be provided or the operation will fail with an `origin mismatch`
|
||||
// by definition.
|
||||
//
|
||||
this.source = options.source || { host: 'localhost', port: 8000 };
|
||||
this.changeOrigin = options.changeOrigin || false;
|
||||
};
|
||||
|
||||
// Inherit from events.EventEmitter
|
||||
util.inherits(HttpProxy, events.EventEmitter);
|
||||
|
||||
//
|
||||
// ### function proxyRequest (req, res, [port, host, paused])
|
||||
// #### @req {ServerRequest} Incoming HTTP Request to proxy.
|
||||
// #### @res {ServerResponse} Outgoing HTTP Request to write proxied data to.
|
||||
// #### @buffer {Object} Result from `httpProxy.buffer(req)`
|
||||
//
|
||||
HttpProxy.prototype.proxyRequest = function (req, res, buffer) {
|
||||
var self = this,
|
||||
errState = false,
|
||||
outgoing = new Object(this.target.base),
|
||||
reverseProxy;
|
||||
|
||||
//
|
||||
// Add common proxy headers to the request so that they can
|
||||
// be availible to the proxy target server:
|
||||
//
|
||||
// * `x-forwarded-for`: IP Address of the original request
|
||||
// * `x-forwarded-proto`: Protocol of the original request
|
||||
// * `x-forwarded-port`: Port of the original request.
|
||||
//
|
||||
if (this.enable.xforward) {
|
||||
req.headers['x-forwarded-for'] = req.connection.remoteAddress || req.connection.socket.remoteAddress;
|
||||
req.headers['x-forwarded-port'] = req.connection.remotePort || req.connection.socket.remotePort;
|
||||
req.headers['x-forwarded-proto'] = res.connection.pair ? 'https' : 'http';
|
||||
}
|
||||
|
||||
//
|
||||
// Emit the `start` event indicating that we have begun the proxy operation.
|
||||
//
|
||||
this.emit('start', req, res, this.target);
|
||||
|
||||
//
|
||||
// If forwarding is enabled for this instance, foward proxy the
|
||||
// specified request to the address provided in `this.forward`
|
||||
//
|
||||
if (this.forward) {
|
||||
this.emit('forward', req, res, this.forward);
|
||||
this._forwardRequest(req);
|
||||
}
|
||||
|
||||
//
|
||||
// #### function proxyError (err)
|
||||
// #### @err {Error} Error contacting the proxy target
|
||||
// Short-circuits `res` in the event of any error when
|
||||
// contacting the proxy target at `host` / `port`.
|
||||
//
|
||||
function proxyError(err) {
|
||||
errState = true;
|
||||
|
||||
//
|
||||
// Emit an `error` event, allowing the application to use custom
|
||||
// error handling. The error handler should end the response.
|
||||
//
|
||||
if (self.emit('proxyError', err, req, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
||||
|
||||
if (req.method !== 'HEAD') {
|
||||
//
|
||||
// This NODE_ENV=production behavior is mimics Express and
|
||||
// Connect.
|
||||
//
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
res.write('Internal Server Error');
|
||||
}
|
||||
else {
|
||||
res.write('An error has occurred: ' + JSON.stringify(err));
|
||||
}
|
||||
}
|
||||
|
||||
res.end();
|
||||
}
|
||||
|
||||
//
|
||||
// Setup outgoing proxy with relevant properties.
|
||||
//
|
||||
outgoing.host = this.target.host;
|
||||
outgoing.port = this.target.port;
|
||||
outgoing.agent = this.target.agent;
|
||||
outgoing.method = req.method;
|
||||
outgoing.path = req.url;
|
||||
outgoing.headers = req.headers;
|
||||
|
||||
//
|
||||
// Open new HTTP request to internal resource with will act
|
||||
// as a reverse proxy pass
|
||||
//
|
||||
reverseProxy = this.target.protocol.request(outgoing, function (response) {
|
||||
//
|
||||
// Process the `reverseProxy` `response` when it's received.
|
||||
//
|
||||
if (response.headers.connection) {
|
||||
if (req.headers.connection) { response.headers.connection = req.headers.connection }
|
||||
else { response.headers.connection = 'close' }
|
||||
}
|
||||
|
||||
// Set the headers of the client response
|
||||
res.writeHead(response.statusCode, response.headers);
|
||||
|
||||
// If `response.statusCode === 304`: No 'data' event and no 'end'
|
||||
if (response.statusCode === 304) {
|
||||
return res.end();
|
||||
}
|
||||
|
||||
//
|
||||
// For each data `chunk` received from the `reverseProxy`
|
||||
// `response` write it to the outgoing `res`.
|
||||
// If the res socket has been killed already, then write()
|
||||
// will throw. Nevertheless, try our best to end it nicely.
|
||||
//
|
||||
response.on('data', function (chunk) {
|
||||
if (req.method !== 'HEAD' && res.writable) {
|
||||
try {
|
||||
res.write(chunk);
|
||||
}
|
||||
catch (er) {
|
||||
try { res.end() }
|
||||
catch (er) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// When the `reverseProxy` `response` ends, end the
|
||||
// corresponding outgoing `res` unless we have entered
|
||||
// an error state. In which case, assume `res.end()` has
|
||||
// already been called and the 'error' event listener
|
||||
// removed.
|
||||
//
|
||||
response.on('end', function () {
|
||||
if (!errState) {
|
||||
reverseProxy.removeListener('error', proxyError);
|
||||
res.end();
|
||||
|
||||
// Emit the `end` event now that we have completed proxying
|
||||
self.emit('end', req, res);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//
|
||||
// Handle 'error' events from the `reverseProxy`.
|
||||
//
|
||||
reverseProxy.once('error', proxyError);
|
||||
|
||||
//
|
||||
// For each data `chunk` received from the incoming
|
||||
// `req` write it to the `reverseProxy` request.
|
||||
//
|
||||
req.on('data', function (chunk) {
|
||||
if (!errState) {
|
||||
reverseProxy.write(chunk);
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// When the incoming `req` ends, end the corresponding `reverseProxy`
|
||||
// request unless we have entered an error state.
|
||||
//
|
||||
req.on('end', function () {
|
||||
if (!errState) {
|
||||
reverseProxy.end();
|
||||
}
|
||||
});
|
||||
|
||||
// If we have been passed buffered data, resume it.
|
||||
if (buffer && !errState) {
|
||||
buffer.resume();
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// ### @private function _forwardRequest (req)
|
||||
// #### @req {ServerRequest} Incoming HTTP Request to proxy.
|
||||
// Forwards the specified `req` to the location specified
|
||||
// by `this.forward` ignoring errors and the subsequent response.
|
||||
//
|
||||
HttpProxy.prototype._forwardRequest = function (req) {
|
||||
var self = this,
|
||||
outgoing = new Object(this.forward.base),
|
||||
forwardProxy;
|
||||
|
||||
//
|
||||
// Setup outgoing proxy with relevant properties.
|
||||
//
|
||||
outgoing.host = this.forward.host;
|
||||
outgoing.port = this.forward.port,
|
||||
outgoing.agent = this.forward.agent;
|
||||
outgoing.method = req.method;
|
||||
outgoing.path = req.url;
|
||||
outgoing.headers = req.headers;
|
||||
|
||||
//
|
||||
// Open new HTTP request to internal resource with will
|
||||
// act as a reverse proxy pass.
|
||||
//
|
||||
forwardProxy = this.forward.protocol.request(outgoing, function (response) {
|
||||
//
|
||||
// Ignore the response from the forward proxy since this is a 'fire-and-forget' proxy.
|
||||
// Remark (indexzero): We will eventually emit a 'forward' event here for performance tuning.
|
||||
//
|
||||
});
|
||||
|
||||
//
|
||||
// Add a listener for the connection timeout event.
|
||||
//
|
||||
// Remark: Ignoring this error in the event
|
||||
// forward target doesn't exist.
|
||||
//
|
||||
forwardProxy.once('error', function (err) { });
|
||||
|
||||
//
|
||||
// Chunk the client request body as chunks from
|
||||
// the proxied request come in
|
||||
//
|
||||
req.on('data', function (chunk) {
|
||||
forwardProxy.write(chunk);
|
||||
})
|
||||
|
||||
//
|
||||
// At the end of the client request, we are going to
|
||||
// stop the proxied request
|
||||
//
|
||||
req.on('end', function () {
|
||||
forwardProxy.end();
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// ### function proxyWebSocketRequest (req, socket, head, options)
|
||||
// #### @req {ServerRequest} Websocket request to proxy.
|
||||
// #### @socket {net.Socket} Socket for the underlying HTTP request
|
||||
// #### @head {string} Headers for the Websocket request.
|
||||
// #### @buffer {Object} Result from `httpProxy.buffer(req)`
|
||||
// Performs a WebSocket proxy operation to the location specified by
|
||||
// `this.target`.
|
||||
//
|
||||
HttpProxy.prototype.proxyWebSocketRequest = function (req, socket, head, buffer) {
|
||||
var self = this,
|
||||
outgoing = new Object(this.target.base);
|
||||
listeners = {},
|
||||
errState = false,
|
||||
CRLF = '\r\n';
|
||||
|
||||
//
|
||||
// WebSocket requests must have the `GET` method and
|
||||
// the `upgrade:websocket` header
|
||||
//
|
||||
if (req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket') {
|
||||
//
|
||||
// This request is not WebSocket request
|
||||
//
|
||||
return socket.destroy();
|
||||
}
|
||||
|
||||
//
|
||||
// Helper function for setting appropriate socket values:
|
||||
// 1. Turn of all bufferings
|
||||
// 2. For server set KeepAlive
|
||||
// 3. For client set encoding
|
||||
//
|
||||
function _socket(socket, keepAlive) {
|
||||
socket.setTimeout(0);
|
||||
socket.setNoDelay(true);
|
||||
|
||||
if (keepAlive) {
|
||||
if (socket.setKeepAlive) {
|
||||
socket.setKeepAlive(true, 0);
|
||||
}
|
||||
else if (socket.pair.cleartext.socket.setKeepAlive) {
|
||||
socket.pair.cleartext.socket.setKeepAlive(true, 0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
socket.setEncoding('utf8');
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Setup the incoming client socket.
|
||||
//
|
||||
_socket(socket);
|
||||
|
||||
//
|
||||
// On `upgrade` from the Agent socket, listen to
|
||||
// the appropriate events.
|
||||
//
|
||||
function onUpgrade (reverseProxy, proxySocket) {
|
||||
if (!reverseProxy) {
|
||||
proxySocket.end();
|
||||
socket.end();
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Any incoming data on this WebSocket to the proxy target
|
||||
// will be written to the `reverseProxy` socket.
|
||||
//
|
||||
proxySocket.on('data', listeners.onIncoming = function (data) {
|
||||
if (reverseProxy.incoming.socket.writable) {
|
||||
try {
|
||||
self.emit('websocket:outgoing', req, socket, head, data);
|
||||
reverseProxy.incoming.socket.write(data);
|
||||
}
|
||||
catch (ex) {
|
||||
reverseProxy.incoming.socket.end();
|
||||
proxySocket.end();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Any outgoing data on this Websocket from the proxy target
|
||||
// will be written to the `proxySocket` socket.
|
||||
//
|
||||
reverseProxy.incoming.socket.on('data', listeners.onOutgoing = function(data) {
|
||||
try {
|
||||
self.emit('websocket:incoming', reverseProxy, reverseProxy.incoming, head, data);
|
||||
proxySocket.write(data);
|
||||
}
|
||||
catch (ex) {
|
||||
proxySocket.end();
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Helper function to detach all event listeners
|
||||
// from `reverseProxy` and `proxySocket`.
|
||||
//
|
||||
function detach() {
|
||||
proxySocket.removeListener('end', listeners.onIncomingClose);
|
||||
proxySocket.removeListener('data', listeners.onIncoming);
|
||||
reverseProxy.incoming.socket.removeListener('end', listeners.onOutgoingClose);
|
||||
reverseProxy.incoming.socket.removeListener('data', listeners.onOutgoing);
|
||||
}
|
||||
|
||||
//
|
||||
// If the incoming `proxySocket` socket closes, then
|
||||
// detach all event listeners.
|
||||
//
|
||||
proxySocket.on('end', listeners.onIncomingClose = function() {
|
||||
reverseProxy.incoming.socket.end();
|
||||
detach();
|
||||
|
||||
// Emit the `end` event now that we have completed proxying
|
||||
self.emit('websocket:end', req, socket, head);
|
||||
});
|
||||
|
||||
//
|
||||
// If the `reverseProxy` socket closes, then detach all
|
||||
// event listeners.
|
||||
//
|
||||
reverseProxy.incoming.socket.on('end', listeners.onOutgoingClose = function() {
|
||||
proxySocket.end();
|
||||
detach();
|
||||
});
|
||||
};
|
||||
|
||||
function getPort (port) {
|
||||
port = port || 80;
|
||||
return port - 80 === 0 ? '' : ':' + port
|
||||
}
|
||||
|
||||
//
|
||||
// Get the protocol, and host for this request and create an instance
|
||||
// of `http.Agent` or `https.Agent` from the pool managed by `node-http-proxy`.
|
||||
//
|
||||
var agent = this.target.agent,
|
||||
protocolName = this.target.https ? 'https' : 'http',
|
||||
portUri = getPort(this.source.port),
|
||||
remoteHost = options.host + portUri;
|
||||
|
||||
//
|
||||
// Change headers (if requested).
|
||||
//
|
||||
if (this.changeOrigin) {
|
||||
req.headers.host = remoteHost;
|
||||
req.headers.origin = protocolName + '://' + remoteHost;
|
||||
}
|
||||
|
||||
//
|
||||
// Make the outgoing WebSocket request
|
||||
//
|
||||
outgoing.host = this.target.host;
|
||||
outgoing.port = this.target.port;
|
||||
outgoing.method = 'GET';
|
||||
outgoing.path = req.url;
|
||||
outgoing.headers = req.headers;
|
||||
|
||||
var reverseProxy = agent.appendMessage(outgoing);
|
||||
|
||||
//
|
||||
// On any errors from the `reverseProxy` emit the
|
||||
// `webSocketProxyError` and close the appropriate
|
||||
// connections.
|
||||
//
|
||||
function proxyError (err) {
|
||||
reverseProxy.end();
|
||||
if (self.emit('webSocketProxyError', req, socket, head)) {
|
||||
return;
|
||||
}
|
||||
|
||||
socket.end();
|
||||
}
|
||||
|
||||
//
|
||||
// Here we set the incoming `req`, `socket` and `head` data to the outgoing
|
||||
// request so that we can reuse this data later on in the closure scope
|
||||
// available to the `upgrade` event. This bookkeeping is not tracked anywhere
|
||||
// in nodejs core and is **very** specific to proxying WebSockets.
|
||||
//
|
||||
reverseProxy.agent = agent;
|
||||
reverseProxy.incoming = {
|
||||
request: req,
|
||||
socket: socket,
|
||||
head: head
|
||||
};
|
||||
|
||||
//
|
||||
// If the agent for this particular `host` and `port` combination
|
||||
// is not already listening for the `upgrade` event, then do so once.
|
||||
// This will force us not to disconnect.
|
||||
//
|
||||
// In addition, it's important to note the closure scope here. Since
|
||||
// there is no mapping of the
|
||||
//
|
||||
if (!agent._events || agent._events['upgrade'].length === 0) {
|
||||
agent.on('upgrade', function (_, remoteSocket, head) {
|
||||
//
|
||||
// Prepare the socket for the reverseProxy request and begin to
|
||||
// stream data between the two sockets. Here it is important to
|
||||
// note that `remoteSocket._httpMessage === reverseProxy`.
|
||||
//
|
||||
_socket(remoteSocket, true);
|
||||
onUpgrade(remoteSocket._httpMessage, remoteSocket);
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// If the reverseProxy connection has an underlying socket,
|
||||
// then execute the WebSocket handshake.
|
||||
//
|
||||
if (typeof reverseProxy.socket !== 'undefined') {
|
||||
reverseProxy.socket.on('data', function handshake (data) {
|
||||
//
|
||||
// Ok, kind of harmfull part of code. Socket.IO sends a hash
|
||||
// at the end of handshake if protocol === 76, but we need
|
||||
// to replace 'host' and 'origin' in response so we split
|
||||
// data to printable data and to non-printable. (Non-printable
|
||||
// will come after double-CRLF).
|
||||
//
|
||||
var sdata = data.toString();
|
||||
|
||||
// Get the Printable data
|
||||
sdata = sdata.substr(0, sdata.search(CRLF + CRLF));
|
||||
|
||||
// Get the Non-Printable data
|
||||
data = data.slice(Buffer.byteLength(sdata), data.length);
|
||||
|
||||
if (self.https && !self.target.https) {
|
||||
//
|
||||
// If the proxy server is running HTTPS but the client is running
|
||||
// HTTP then replace `ws` with `wss` in the data sent back to the client.
|
||||
//
|
||||
sdata = sdata.replace('ws:', 'wss:');
|
||||
}
|
||||
|
||||
try {
|
||||
//
|
||||
// Write the printable and non-printable data to the socket
|
||||
// from the original incoming request.
|
||||
//
|
||||
self.emit('websocket:handshake', req, socket, head, sdata, data);
|
||||
socket.write(sdata);
|
||||
socket.write(data);
|
||||
}
|
||||
catch (ex) {
|
||||
//
|
||||
// Remove data listener on socket error because the
|
||||
// 'handshake' has failed.
|
||||
//
|
||||
reverseProxy.socket.removeListener('data', handshake);
|
||||
return proxyError(ex);
|
||||
}
|
||||
|
||||
// Catch socket errors
|
||||
socket.on('error', proxyError);
|
||||
|
||||
//
|
||||
// Remove data listener now that the 'handshake' is complete
|
||||
//
|
||||
reverseProxy.socket.removeListener('data', handshake);
|
||||
});
|
||||
}
|
||||
|
||||
reverseProxy.on('error', proxyError);
|
||||
|
||||
try {
|
||||
//
|
||||
// Attempt to write the upgrade-head to the reverseProxy request.
|
||||
//
|
||||
reverseProxy.write(head);
|
||||
}
|
||||
catch (ex) {
|
||||
return proxyError(ex);
|
||||
}
|
||||
|
||||
//
|
||||
// If we have been passed buffered data, resume it.
|
||||
//
|
||||
if (buffer && !errState) {
|
||||
buffer.resume();
|
||||
}
|
||||
};
|
||||
@ -81,7 +81,9 @@ util.inherits(ProxyTable, events.EventEmitter);
|
||||
// Sets the host-based routes to be used by this instance.
|
||||
//
|
||||
ProxyTable.prototype.setRoutes = function (router) {
|
||||
if (!router) throw new Error('Cannot update ProxyTable routes without router.');
|
||||
if (!router) {
|
||||
throw new Error('Cannot update ProxyTable routes without router.');
|
||||
}
|
||||
|
||||
this.router = router;
|
||||
|
||||
4
lib/node-http-proxy/routing-proxy.js
Normal file
4
lib/node-http-proxy/routing-proxy.js
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
var RoutingProxy = exports.RoutingProxy = function () {
|
||||
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user