Created
May 29, 2012 13:49
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Since node-http-proxy changed its API this is now obsolete.