mirror of
https://github.com/http-party/node-http-proxy.git
synced 2025-12-08 20:59:18 +00:00
[api] Completely refactored node-http-proxy with help from Mikeal
This commit is contained in:
parent
c887a75762
commit
1221939acc
33
README.md
33
README.md
@ -52,9 +52,9 @@ see the [demo](http://github.com/nodejitsu/node-http-proxy/blob/master/demo.js)
|
||||
httpProxy = require('http-proxy');
|
||||
|
||||
// create a proxy server with custom application logic
|
||||
httpProxy.createServer(function (req, res, proxy) {
|
||||
httpProxy.createServer(function (req, res, proxyRequest) {
|
||||
// Put your custom server logic here
|
||||
proxy.proxyRequest(9000, 'localhost', req, res);
|
||||
proxyRequest(9000, 'localhost');
|
||||
}).listen(8000);
|
||||
|
||||
http.createServer(function (req, res){
|
||||
@ -65,25 +65,16 @@ see the [demo](http://github.com/nodejitsu/node-http-proxy/blob/master/demo.js)
|
||||
|
||||
</pre>
|
||||
|
||||
### How to proxy requests with a regular http server
|
||||
### How to proxy requests with latent operations (IO, etc.)
|
||||
|
||||
node-http-proxy supports event buffering, that means if an event (like 'data', or 'end') is raised by the incoming request before you have a chance to perform your custom server logic, those events will be captured and re-raised when you later proxy the request. Here's a simple example:
|
||||
|
||||
<pre>
|
||||
var http = require('http'),
|
||||
httpProxy = require('http-proxy');
|
||||
|
||||
// create a regular http server and proxy its handler
|
||||
http.createServer(function (req, res){
|
||||
var proxy = new httpProxy.HttpProxy;
|
||||
proxy.watch(req, res);
|
||||
// Put your custom server logic here
|
||||
proxy.proxyRequest(9000, 'localhost', req, res);
|
||||
}).listen(8001);
|
||||
|
||||
http.createServer(function (req, res){
|
||||
res.writeHead(200, {'Content-Type': 'text/plain'});
|
||||
res.write('request successfully proxied: ' + req.url +'\n' + JSON.stringify(req.headers, true, 2));
|
||||
res.end();
|
||||
}).listen(9000);
|
||||
|
||||
httpProxy.createServer(function (req, res, proxyRequest) {
|
||||
setTimeout(function () {
|
||||
proxyRequest(port, server);
|
||||
}, latency);
|
||||
}).listen(8081);
|
||||
</pre>
|
||||
|
||||
### Why doesn't node-http-proxy have more advanced features like x, y, or z?
|
||||
@ -96,7 +87,7 @@ If you have a suggestion for a feature currently not supported, feel free to ope
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) 2010 Charlie Robbins & Marak Squires http://github.com/nodejitsu/
|
||||
Copyright (c) 2010 Mikeal Rogers, Charlie Robbins & Marak Squires
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
19
demo.js
19
demo.js
@ -27,7 +27,7 @@
|
||||
var sys = require('sys'),
|
||||
colors = require('colors')
|
||||
http = require('http'),
|
||||
httpProxy = require('http-proxy');
|
||||
httpProxy = require('./lib/node-http-proxy');
|
||||
|
||||
// ascii art from http://github.com/marak/asciimo
|
||||
var welcome = '\
|
||||
@ -45,24 +45,13 @@ httpProxy.createServer(9000, 'localhost').listen(8000);
|
||||
sys.puts('http proxy server'.blue + ' started '.green.bold + 'on port '.blue + '8000'.yellow);
|
||||
|
||||
/****** http proxy server with latency******/
|
||||
httpProxy.createServer(function (req, res, proxy){
|
||||
httpProxy.createServer(function (req, res, proxyRequest){
|
||||
setTimeout(function(){
|
||||
proxy.proxyRequest(9000, 'localhost', req, res);
|
||||
}, 200)
|
||||
proxyRequest(9000, 'localhost', req, res);
|
||||
}, 2000)
|
||||
}).listen(8001);
|
||||
sys.puts('http proxy server '.blue + 'started '.green.bold + 'on port '.blue + '8001 '.yellow + 'with latency'.magenta.underline );
|
||||
|
||||
/****** http server with proxyRequest handler and latency******/
|
||||
http.createServer(function (req, res){
|
||||
var proxy = new httpProxy.HttpProxy;
|
||||
proxy.watch(req, res);
|
||||
|
||||
setTimeout(function(){
|
||||
proxy.proxyRequest(9000, 'localhost', req, res);
|
||||
}, 200);
|
||||
}).listen(8002);
|
||||
sys.puts('http server '.blue + 'started '.green.bold + 'on port '.blue + '8002 '.yellow + 'with proxyRequest handler'.cyan.underline + ' and latency'.magenta);
|
||||
|
||||
/****** regular http server ******/
|
||||
http.createServer(function (req, res){
|
||||
res.writeHead(200, {'Content-Type': 'text/plain'});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*
|
||||
node-http-proxy.js: http proxy for node.js
|
||||
node-http-proxy.js: http proxy for node.js with pooling and event buffering
|
||||
|
||||
Copyright (c) 2010 Charlie Robbins & Marak Squires http://github.com/nodejitsu/node-http-proxy
|
||||
Copyright (c) 2010 Mikeal Rogers, Charlie Robbins
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
@ -24,172 +24,110 @@
|
||||
|
||||
*/
|
||||
|
||||
var sys = require('sys'),
|
||||
http = require('http'),
|
||||
events = require('events');
|
||||
var sys = require('sys'),
|
||||
http = require('http'),
|
||||
pool = require('pool'),
|
||||
url = require('url'),
|
||||
events = require('events'),
|
||||
min = 0,
|
||||
max = 100;
|
||||
|
||||
exports.HttpProxy = function () {
|
||||
this.emitter = new(events.EventEmitter);
|
||||
this.events = {};
|
||||
this.listeners = {};
|
||||
this.collisions = {};
|
||||
};
|
||||
// Setup the PoolManager
|
||||
var manager = pool.createPoolManager();
|
||||
manager.setMinClients(min);
|
||||
manager.setMaxClients(max);
|
||||
|
||||
exports.createServer = function () {
|
||||
// Initialize the nodeProxy to start proxying requests
|
||||
var proxy = new (exports.HttpProxy);
|
||||
return proxy.createServer.apply(proxy, arguments);
|
||||
var args, action, port, host;
|
||||
args = Array.prototype.slice.call(arguments);
|
||||
action = typeof args[args.length - 1] === 'function' && args.pop();
|
||||
if (args[0]) port = args[0];
|
||||
if (args[1]) host = args[1];
|
||||
|
||||
var proxy = createProxy();
|
||||
proxy.on('route', function (req, res, callback) {
|
||||
var uri = url.parse(req.url);
|
||||
if (action) {
|
||||
action(req, res, callback);
|
||||
}
|
||||
else {
|
||||
port = port ? port : uri.port ? uri.port : 80;
|
||||
host = host ? host : uri.hostname;
|
||||
callback(port, host);
|
||||
}
|
||||
});
|
||||
return proxy;
|
||||
};
|
||||
|
||||
exports.HttpProxy.prototype = {
|
||||
toArray: function (obj){
|
||||
var len = obj.length,
|
||||
arr = new Array(len);
|
||||
for (var i = 0; i < len; ++i) {
|
||||
arr[i] = obj[i];
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
|
||||
createServer: function () {
|
||||
var self = this,
|
||||
server,
|
||||
port,
|
||||
callback;
|
||||
exports.setMin = function (value) {
|
||||
min = value;
|
||||
manager.setMinClients(min);
|
||||
};
|
||||
|
||||
exports.setMax = function (value) {
|
||||
max = value;
|
||||
manager.setMaxClients(max);
|
||||
}
|
||||
|
||||
var createProxy = function () {
|
||||
var server = http.createServer(function (req, res) {
|
||||
var buffers = [],
|
||||
b = function (chunk) { buffers.push(chunk) },
|
||||
e = function () { e = false };
|
||||
|
||||
if (typeof(arguments[0]) === "function") {
|
||||
callback = arguments[0];
|
||||
}
|
||||
else {
|
||||
port = arguments[0];
|
||||
server = arguments[1];
|
||||
}
|
||||
req.on('data', b);
|
||||
req.on('end', e);
|
||||
|
||||
var proxyServer = http.createServer(function (req, res){
|
||||
self.watch(req, res);
|
||||
server.emit('route', req, res, function (port, hostname) {
|
||||
var p = manager.getPool(port, hostname);
|
||||
|
||||
// If we were passed a callback to process the request
|
||||
// or response in some way, then call it.
|
||||
if(callback) {
|
||||
callback(req, res, self);
|
||||
}
|
||||
else {
|
||||
self.proxyRequest(port, server, req, res);
|
||||
}
|
||||
});
|
||||
|
||||
return proxyServer;
|
||||
},
|
||||
|
||||
watch: function (req, res) {
|
||||
var self = this;
|
||||
|
||||
// Create a unique id for this request so
|
||||
// we can reference it later.
|
||||
var id = new Date().getTime().toString();
|
||||
|
||||
// If we get a request in the same tick, we need to
|
||||
// append to the id so it stays unique.
|
||||
if(typeof this.collisions[id] === 'undefined') {
|
||||
this.collisions[id] = 0;
|
||||
}
|
||||
else {
|
||||
this.collisions[id]++;
|
||||
id += this.collisions[id];
|
||||
}
|
||||
|
||||
req.id = id;
|
||||
this.events[req.id] = [];
|
||||
|
||||
this.listeners[req.id] = {
|
||||
onData: function () {
|
||||
self.events[req.id].push(['data'].concat(self.toArray(arguments)));
|
||||
},
|
||||
onEnd: function () {
|
||||
self.events[req.id].push(['end'].concat(self.toArray(arguments)));
|
||||
}
|
||||
};
|
||||
p.request(req.method, req.url, req.headers, function (reverse_proxy) {
|
||||
var data = '';
|
||||
reverse_proxy.on('error', function (err) {
|
||||
res.writeHead(500, {'Content-Type': 'text/plain'});
|
||||
|
||||
req.addListener('data', this.listeners[req.id].onData);
|
||||
req.addListener('end', this.listeners[req.id].onEnd);
|
||||
|
||||
},
|
||||
|
||||
unwatch: function (req, res) {
|
||||
req.removeListener('data', this.listeners[req.id].onData);
|
||||
req.removeListener('end', this.listeners[req.id].onEnd);
|
||||
|
||||
// Rebroadcast any events that have been buffered
|
||||
while(this.events[req.id].length > 0) {
|
||||
var args = this.events[req.id].shift();
|
||||
req.emit.apply(req, args);
|
||||
}
|
||||
|
||||
// Remove the data from the event and listeners hashes
|
||||
delete this.listeners[req.id];
|
||||
delete this.events[req.id];
|
||||
|
||||
// If this request id is a base time, delete it
|
||||
if (typeof this.collisions[req.id] !== 'undefined') {
|
||||
delete this.collisions[req.id];
|
||||
}
|
||||
},
|
||||
if(req.method !== 'HEAD') {
|
||||
res.write('An error has occurred: ' + sys.puts(JSON.stringify(err)));
|
||||
}
|
||||
|
||||
proxyRequest: function (port, server, req, res) {
|
||||
// Remark: nodeProxy.body exists solely for testability
|
||||
this.body = '';
|
||||
var self = this;
|
||||
|
||||
// Open new HTTP request to internal resource with will act as a reverse proxy pass
|
||||
var c = http.createClient(port, server);
|
||||
res.end();
|
||||
});
|
||||
|
||||
// Make request to internal server, passing along the method and headers
|
||||
var reverse_proxy = c.request(req.method, req.url, req.headers);
|
||||
|
||||
// Add a listener for the connection timeout event
|
||||
reverse_proxy.connection.addListener('error', function (err) {
|
||||
res.writeHead(200, {'Content-Type': 'text/plain'});
|
||||
|
||||
if(req.method !== 'HEAD') {
|
||||
res.write('An error has occurred: ' + sys.puts(JSON.stringify(err)));
|
||||
}
|
||||
|
||||
res.end();
|
||||
});
|
||||
|
||||
|
||||
// Add a listener for the reverse_proxy response event
|
||||
reverse_proxy.addListener('response', function (response) {
|
||||
// Set the response headers of the client response
|
||||
res.writeHead(response.statusCode, response.headers);
|
||||
|
||||
// Add event handler for the proxied response in chunks
|
||||
response.addListener('data', function (chunk) {
|
||||
if(req.method !== 'HEAD') {
|
||||
res.write(chunk, 'binary');
|
||||
self.body += chunk;
|
||||
}
|
||||
});
|
||||
|
||||
// Add event listener for end of proxied response
|
||||
response.addListener('end', function () {
|
||||
// Remark: Emit the end event for testability
|
||||
self.emitter.emit('end', null, self.body);
|
||||
buffers.forEach(function (c) {
|
||||
data += c;
|
||||
reverse_proxy.write(c);
|
||||
});
|
||||
|
||||
res.end();
|
||||
buffers = null;
|
||||
req.removeListener('data', b);
|
||||
sys.pump(req, reverse_proxy);
|
||||
|
||||
if (e) {
|
||||
req.removeListener('end', e);
|
||||
req.addListener('end', function () { reverse_proxy.end() });
|
||||
}
|
||||
else {
|
||||
reverse_proxy.end();
|
||||
}
|
||||
|
||||
// Add a listener for the reverse_proxy response event
|
||||
reverse_proxy.addListener('response', function (response) {
|
||||
// These two listeners are for testability and observation
|
||||
// of what's passed back from the target server
|
||||
response.addListener('data', function (chunk) {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
response.addListener('end', function() {
|
||||
server.emit('proxy', null, data);
|
||||
});
|
||||
|
||||
// Set the response headers of the client response
|
||||
res.writeHead(response.statusCode, response.headers);
|
||||
sys.pump(response, res);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Chunk the client request body as chunks from the proxied request come in
|
||||
req.addListener('data', function (chunk) {
|
||||
reverse_proxy.write(chunk, 'binary');
|
||||
})
|
||||
|
||||
// At the end of the client request, we are going to stop the proxied request
|
||||
req.addListener('end', function () {
|
||||
reverse_proxy.end();
|
||||
});
|
||||
|
||||
this.unwatch(req, res);
|
||||
}
|
||||
};
|
||||
})
|
||||
return server;
|
||||
};
|
||||
@ -29,14 +29,14 @@ var vows = require('vows'),
|
||||
assert = require('assert'),
|
||||
http = require('http');
|
||||
|
||||
var httpProxy = require('http-proxy');
|
||||
var httpProxy = require('./../lib/node-http-proxy');
|
||||
var testServers = {};
|
||||
|
||||
//
|
||||
// Creates the reverse proxy server
|
||||
//
|
||||
var startProxyServer = function (port, server, proxy) {
|
||||
var proxyServer = proxy.createServer(port, server);
|
||||
var startProxyServer = function (port, server) {
|
||||
var proxyServer = httpProxy.createServer(port, server);
|
||||
proxyServer.listen(8080);
|
||||
return proxyServer;
|
||||
};
|
||||
@ -44,11 +44,11 @@ var startProxyServer = function (port, server, proxy) {
|
||||
//
|
||||
// Creates the reverse proxy server with a specified latency
|
||||
//
|
||||
var startLatentProxyServer = function (port, server, proxy, latency) {
|
||||
var startLatentProxyServer = function (port, server, latency) {
|
||||
// Initialize the nodeProxy and start proxying the request
|
||||
var proxyServer = proxy.createServer(function (req, res, proxy) {
|
||||
var proxyServer = httpProxy.createServer(function (req, res, proxy) {
|
||||
setTimeout(function () {
|
||||
proxy.proxyRequest(port, server, req, res);
|
||||
proxy(port, server);
|
||||
}, latency);
|
||||
});
|
||||
|
||||
@ -73,57 +73,72 @@ var startTargetServer = function (port) {
|
||||
//
|
||||
// The default test bootstrapper with no latency
|
||||
//
|
||||
var startTest = function (proxy, port) {
|
||||
var startTest = function (port) {
|
||||
var proxyServer = startProxyServer(port, 'localhost'),
|
||||
targetServer = startTargetServer(port);
|
||||
|
||||
testServers.noLatency = [];
|
||||
testServers.noLatency.push(startProxyServer(port, 'localhost', proxy));
|
||||
testServers.noLatency.push(startTargetServer(port));
|
||||
testServers.noLatency.push(proxyServer);
|
||||
testServers.noLatency.push(targetServer);
|
||||
return proxyServer;
|
||||
};
|
||||
|
||||
//
|
||||
// The test bootstrapper with some latency
|
||||
//
|
||||
var startTestWithLatency = function (proxy, port) {
|
||||
var startTestWithLatency = function (port) {
|
||||
var proxyServer = startLatentProxyServer(port, 'localhost', 2000),
|
||||
targetServer = startTargetServer(port);
|
||||
|
||||
testServers.latency = [];
|
||||
testServers.latency.push(startLatentProxyServer(port, 'localhost', proxy, 2000));
|
||||
testServers.latency.push(startTargetServer(port));
|
||||
testServers.latency.push(proxyServer);
|
||||
testServers.latency.push(targetServer);
|
||||
return proxyServer;
|
||||
};
|
||||
|
||||
//var proxy = startTest(8082);
|
||||
//var latent = startTestWithLatency(8083);
|
||||
|
||||
vows.describe('node-http-proxy').addBatch({
|
||||
"A node-http-proxy": {
|
||||
"when instantiated directly": {
|
||||
"and an incoming request is proxied to the helloNode server" : {
|
||||
"with no latency" : {
|
||||
topic: function () {
|
||||
var proxy = new (httpProxy.HttpProxy);
|
||||
startTest(proxy, 8082);
|
||||
proxy.emitter.addListener('end', this.callback);
|
||||
var proxyServer = startTest(8082);
|
||||
proxyServer.on('proxy', this.callback);
|
||||
|
||||
var client = http.createClient(8080, 'localhost');
|
||||
var request = client.request('GET', '/');
|
||||
request.end();
|
||||
},
|
||||
teardown: function () {
|
||||
|
||||
},
|
||||
"it should received 'hello world'": function (err, body) {
|
||||
assert.equal(body, 'hello world');
|
||||
testServers.noLatency.forEach(function (server) {
|
||||
server.close();
|
||||
})
|
||||
});
|
||||
}
|
||||
},
|
||||
"with latency": {
|
||||
topic: function () {
|
||||
var proxy = new (httpProxy.HttpProxy);
|
||||
startTestWithLatency(proxy, 8083);
|
||||
proxy.emitter.addListener('end', this.callback);
|
||||
var proxyServer = startTestWithLatency(8083);
|
||||
proxyServer.on('proxy', this.callback);
|
||||
|
||||
var client = http.createClient(8081, 'localhost');
|
||||
var request = client.request('GET', '/');
|
||||
request.end();
|
||||
},
|
||||
teardown: function () {
|
||||
|
||||
},
|
||||
"it should receive 'hello world'": function (err, body) {
|
||||
assert.equal(body, 'hello world');
|
||||
testServers.latency.forEach(function (server) {
|
||||
server.close();
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user