mirror of
https://github.com/http-party/node-http-proxy.git
synced 2025-12-08 20:59:18 +00:00
226 lines
6.8 KiB
JavaScript
226 lines
6.8 KiB
JavaScript
/*
|
|
node-http-proxy.js: Lookup table for proxy targets in node.js
|
|
|
|
Copyright (c) 2010 Charlie Robbins
|
|
|
|
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 util = require('util'),
|
|
events = require('events'),
|
|
fs = require('fs'),
|
|
url = require('url');
|
|
|
|
//
|
|
// ### function ProxyTable (router, silent)
|
|
// #### @router {Object} Object containing the host based routes
|
|
// #### @silent {Boolean} Value indicating whether we should suppress logs
|
|
// #### @hostnameOnly {Boolean} Value indicating if we should route based on __hostname string only__
|
|
// Constructor function for the ProxyTable responsible for getting
|
|
// locations of proxy targets based on ServerRequest headers; specifically
|
|
// the HTTP host header.
|
|
//
|
|
var ProxyTable = exports.ProxyTable = function (options) {
|
|
events.EventEmitter.call(this);
|
|
|
|
this.silent = options.silent || options.silent !== true;
|
|
this.target = options.target || {};
|
|
this.hostnameOnly = options.hostnameOnly === true;
|
|
|
|
if (typeof options.router === 'object') {
|
|
//
|
|
// If we are passed an object literal setup
|
|
// the routes with RegExps from the router
|
|
//
|
|
this.setRoutes(options.router);
|
|
}
|
|
else if (typeof options.router === 'string') {
|
|
//
|
|
// If we are passed a string then assume it is a
|
|
// file path, parse that file and watch it for changes
|
|
//
|
|
var self = this;
|
|
this.routeFile = options.router;
|
|
this.setRoutes(JSON.parse(fs.readFileSync(options.router)).router);
|
|
|
|
fs.watchFile(this.routeFile, function () {
|
|
fs.readFile(self.routeFile, function (err, data) {
|
|
if (err) {
|
|
self.emit('error', err);
|
|
}
|
|
|
|
self.setRoutes(JSON.parse(data).router);
|
|
self.emit('routes', self.hostnameOnly === false ? self.routes : self.router);
|
|
});
|
|
});
|
|
}
|
|
else {
|
|
throw new Error('Cannot parse router with unknown type: ' + typeof router);
|
|
}
|
|
};
|
|
|
|
//
|
|
// Inherit from `events.EventEmitter`
|
|
//
|
|
util.inherits(ProxyTable, events.EventEmitter);
|
|
|
|
//
|
|
// ### function setRoutes (router)
|
|
// #### @router {Object} Object containing the host based routes
|
|
// 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.');
|
|
}
|
|
|
|
var self = this;
|
|
this.router = router;
|
|
|
|
if (this.hostnameOnly === false) {
|
|
this.routes = [];
|
|
|
|
Object.keys(router).forEach(function (path) {
|
|
if (!/http[s]?/.test(router[path])) {
|
|
router[path] = (self.target.https ? 'https://' : 'http://')
|
|
+ router[path];
|
|
}
|
|
|
|
var target = url.parse(router[path]),
|
|
defaultPort = self.target.https ? 443 : 80;
|
|
|
|
//
|
|
// Setup a robust lookup table for the route:
|
|
//
|
|
// {
|
|
// source: {
|
|
// regexp: /^foo.com/i,
|
|
// sref: 'foo.com',
|
|
// url: {
|
|
// protocol: 'http:',
|
|
// slashes: true,
|
|
// host: 'foo.com',
|
|
// hostname: 'foo.com',
|
|
// href: 'http://foo.com/',
|
|
// pathname: '/',
|
|
// path: '/'
|
|
// }
|
|
// },
|
|
// {
|
|
// target: {
|
|
// sref: '127.0.0.1:8000/',
|
|
// url: {
|
|
// protocol: 'http:',
|
|
// slashes: true,
|
|
// host: '127.0.0.1:8000',
|
|
// hostname: '127.0.0.1',
|
|
// href: 'http://127.0.0.1:8000/',
|
|
// pathname: '/',
|
|
// path: '/'
|
|
// }
|
|
// },
|
|
//
|
|
self.routes.push({
|
|
source: {
|
|
regexp: new RegExp('^' + path, 'i'),
|
|
sref: path,
|
|
url: url.parse('http://' + path)
|
|
},
|
|
target: {
|
|
sref: target.hostname + ':' + (target.port || defaultPort) + target.path,
|
|
url: target
|
|
}
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
//
|
|
// ### function getProxyLocation (req)
|
|
// #### @req {ServerRequest} The incoming server request to get proxy information about.
|
|
// Returns the proxy location based on the HTTP Headers in the ServerRequest `req`
|
|
// available to this instance.
|
|
//
|
|
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)) {
|
|
var location = this.router[target].split(':'),
|
|
host = location[0],
|
|
port = location.length === 1 ? 80 : location[1];
|
|
|
|
return {
|
|
port: port,
|
|
host: host
|
|
};
|
|
}
|
|
}
|
|
else {
|
|
target += req.url;
|
|
for (var i in this.routes) {
|
|
var route = this.routes[i];
|
|
if (target.match(route.source.regexp)) {
|
|
//
|
|
// Attempt to perform any path replacement for differences
|
|
// between the source path and the target path. This replaces the
|
|
// path's part of the URL to the target's part of the URL.
|
|
//
|
|
// 1. Parse the request URL
|
|
// 2. Replace any portions of the source path with the target path
|
|
// 3. Set the request URL to the formatted URL with replacements.
|
|
//
|
|
var parsed = url.parse(req.url);
|
|
|
|
parsed.pathname = parsed.pathname.replace(
|
|
route.source.url.pathname,
|
|
route.target.url.pathname
|
|
);
|
|
|
|
req.url = url.format(parsed);
|
|
|
|
return {
|
|
protocol: route.target.url.protocol.replace(':', ''),
|
|
host: route.target.url.hostname,
|
|
port: route.target.url.port
|
|
|| (this.target.https ? 443 : 80)
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
//
|
|
// ### close function ()
|
|
// Cleans up the event listeneners maintained
|
|
// by this instance.
|
|
//
|
|
ProxyTable.prototype.close = function () {
|
|
if (typeof this.routeFile === 'string') {
|
|
fs.unwatchFile(this.routeFile);
|
|
}
|
|
};
|