Skip to content

Instantly share code, notes, and snippets.

@buschtoens
Created May 29, 2012 13:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save buschtoens/2828473 to your computer and use it in GitHub Desktop.
Save buschtoens/2828473 to your computer and use it in GitHub Desktop.
This is a fix for the unix socket problem of node-http-proxy
// https://github.com/nodejitsu/node-http-proxy/issues/104
var http = require("http"),
https = require("https");
module.exports = exports = require("http-proxy");
var maxSockets = exports.getMaxSockets();
exports.HttpProxy.prototype.proxyRequest = function (req, res, buffer) {
var self = this,
errState = false,
outgoing = new(this.target.base),
reverseProxy;
//
// Add common proxy headers to the request so that they can
// be availible to the proxy target server. If the proxy is
// part of proxy chain it will append the address:
//
// * `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.connection && req.socket) {
if (req.headers['x-forwarded-for']){
var addressToAppend = "," + req.connection.remoteAddress || req.socket.remoteAddress;
req.headers['x-forwarded-for'] += addressToAppend;
}
else {
req.headers['x-forwarded-for'] = req.connection.remoteAddress || req.socket.remoteAddress;
}
if (req.headers['x-forwarded-port']){
var portToAppend = "," + req.connection.remotePort || req.socket.remotePort;
req.headers['x-forwarded-port'] += portToAppend;
}
else {
req.headers['x-forwarded-port'] = req.connection.remotePort || req.socket.remotePort;
}
if (req.headers['x-forwarded-proto']){
var protoToAppend = "," + (req.connection.pair) ? 'https' : 'http';
req.headers['x-forwarded-proto'] += protoToAppend;
}
else {
req.headers['x-forwarded-proto'] = req.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));
}
}
try { res.end() }
catch (ex) { console.error("res.end error: %s", ex.message) }
}
//
// Setup outgoing proxy with relevant properties.
//
outgoing.host = this.target.host;
outgoing.port = this.target.port;
outgoing.socketPath = this.target.socketPath;
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' }
}
// Remove `Transfer-Encoding` header if client's protocol is HTTP/1.0
if (req.httpVersion === '1.0') {
delete response.headers['transfer-encoding'];
}
if ((response.statusCode === 301) || (response.statusCode === 302)) {
if (self.source.https && !self.target.https) {
response.headers.location = response.headers.location.replace(/^http\:/, 'https:');
}
if (self.target.https && !self.source.https) {
response.headers.location = response.headers.location.replace(/^https\:/, 'http:');
}
}
// 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) {
try { res.end() }
catch (ex) { console.error("res.end error: %s", ex.message) }
return;
}
function ondata(chunk) {
if (res.writable) {
if (false === res.write(chunk) && response.pause) {
response.pause();
}
}
}
response.on('data', ondata);
function ondrain() {
if (response.readable && response.resume) {
response.resume();
}
}
res.on('drain', ondrain);
//
// 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.
//
var ended = false
response.on('close', function () {
if(!ended) response.emit('end')
})
response.on('end', function () {
ended = true
if (!errState) {
reverseProxy.removeListener('error', proxyError);
try { res.end() }
catch (ex) { console.error("res.end error: %s", ex.message) }
// 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);
//
// If `req` is aborted, we abort our `reverseProxy` request as well.
//
req.on('aborted', function () {
reverseProxy.abort();
});
//
// For each data `chunk` received from the incoming
// `req` write it to the `reverseProxy` request.
//
req.on('data', function (chunk) {
if (!errState) {
var flushed = reverseProxy.write(chunk);
if (!flushed) {
req.pause();
reverseProxy.once('drain', function () {
try { req.resume() }
catch (er) { console.error("req.resume error: %s", er.message) }
});
//
// Force the `drain` event in 100ms if it hasn't
// happened on its own.
//
setTimeout(function () {
reverseProxy.emit('drain');
}, 100);
}
}
});
//
// 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();
}
});
//Aborts reverseProxy if client aborts the connection.
req.on('close', function () {
if (!errState) {
reverseProxy.abort();
}
});
//
// If we have been passed buffered data, resume it.
//
if (buffer) {
return !errState
? buffer.resume()
: buffer.destroy();
}
};
exports.ProxyTable.prototype.getProxyLocation = function (req) {
if (!req || !req.headers || !req.headers.host) {
return null;
}
var target = req.headers.host.split(':')[0];
if (this.hostnameOnly == true) {
if (this.router.hasOwnProperty(target)) {
if (this.router[target][0] == "/") {
var socketPath = this.router[target];
} else {
var location = this.router[target].split(':'),
host = location[0],
port = location.length === 1 ? 80 : location[1];
}
return {
port: port,
host: host,
socketPath: socketPath
};
}
} else {
target += req.url;
for (var i in this.routes) {
var route = this.routes[i];
if (target.match(route.route)) {
var pathSegments = route.path.split('/');
if (pathSegments.length > 1) {
// don't include the proxytable path segments in the proxied request url
pathSegments = new RegExp("/" + pathSegments.slice(1).join('/'));
req.url = req.url.replace(pathSegments, '');
}
if (route.target[0] == "/") {
var socketPath = route.target;
} else {
var location = route.target.split(':'),
host = location[0],
port = location.length === 1 ? 80 : location[1];
}
return {
port: port,
host: host,
socketPath: socketPath
};
}
}
}
return null;
};
exports.RoutingProxy.prototype.proxyRequest = function (req, res, options) {
options = options || {};
//
// 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) {
try {
res.writeHead(404);
res.end();
}
catch (er) {
console.error("res.writeHead/res.end error: %s", er.message);
}
return;
}
//
// 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;
options.socketPath = location.socketPath;
}
var key = this._getKey(options),
proxy;
if (!this.proxies[key]) {
this.add(options);
}
proxy = this.proxies[key];
proxy.proxyRequest(req, res, options.buffer);
};
exports.RoutingProxy.prototype._getKey = function (options) {
if (!options || ((!options.host || !options.port)
&& (!options.target || !options.target.host || !options.target.port)
&& !options.socketPath)) {
throw new Error('options.host and options.port or options.target or options.socketPath are required.');
return;
}
return options.socketPath ? options.socketPath : [
options.host || options.target.host,
options.port || options.target.port
].join(':');
};
exports.RoutingProxy.prototype.add = function (options) {
var self = this,
key = this._getKey(options);
//
// TODO: Consume properties in `options` related to the `ProxyTable`.
//
options.target = options.target || {};
options.target.host = options.target.host || options.host;
options.target.port = options.target.port || options.port;
options.target.https = this.target && this.target.https ||
options.target && options.target.https ||
options.https;
options.target.socketPath = options.socketPath;
//
// Setup options to pass-thru to the new `HttpProxy` instance
// for the specified `options.host` and `options.port` pair.
//
['https', 'enable', 'forward'].forEach(function (key) {
if (options[key] !== false && self[key]) {
options[key] = self[key];
}
});
this.proxies[key] = new exports.HttpProxy(options);
if (this.listeners('proxyError').length > 0) {
this.proxies[key].on('proxyError', this.emit.bind(this, 'proxyError'));
}
if (this.listeners('webSocketProxyError').length > 0) {
this.proxies[key].on('webSocketProxyError', this.emit.bind(this, 'webSocketProxyError'));
}
this.proxies[key].on('start', this.emit.bind(this, 'start'));
this.proxies[key].on('forward', this.emit.bind(this, 'forward'));
this.proxies[key].on('end', this.emit.bind(this, 'end'));
};
exports._getAgent = function _getAgent (options) {
if (!options || !(options.host || options.socketPath)) {
throw new Error('`options.host` or `options.socketPath` is required to create an Agent.');
}
if (!options.port) {
options.port = options.https ? 443 : 80;
}
var Agent = options.https ? https.Agent : http.Agent,
agent;
agent = new Agent({
host: options.host,
port: options.port,
socketPath: options.socketPath
});
agent.maxSockets = options.maxSockets || maxSockets;
return agent;
}
@buschtoens
Copy link
Author

Since node-http-proxy changed its API this is now obsolete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment