Skip to content

Commit 3bb458e

Browse files
committed
[api] Integrated commits from donnerjack and worked on pool changes
2 parents 5d54ea5 + 7e61f0c commit 3bb458e

File tree

2 files changed

+300
-24
lines changed

2 files changed

+300
-24
lines changed

lib/node-http-proxy.js

+246-24
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2424
2525
*/
26-
26+
2727
var sys = require('sys'),
2828
http = require('http'),
2929
events = require('events'),
@@ -43,24 +43,36 @@ exports.createServer = function () {
4343
callback = typeof args[args.length - 1] === 'function' && args.pop();
4444
if (args[0]) port = args[0];
4545
if (args[1]) host = args[1];
46-
46+
4747
var server = http.createServer(function (req, res){
4848
var proxy = new HttpProxy(req, res);
49-
49+
5050
proxy.emitter.on('proxy', function (err, body) {
5151
server.emit('proxy', err, body);
5252
});
53-
53+
5454
// If we were passed a callback to process the request
5555
// or response in some way, then call it.
5656
if(callback) {
5757
callback(req, res, proxy);
5858
}
59-
else {
59+
else {
6060
proxy.proxyRequest(port, server);
6161
}
6262
});
63-
63+
64+
// If callback is empty - tunnel websocket request automatically
65+
if (!callback) {
66+
// WebSocket support
67+
server.on('upgrade', function(req, socket, head) {
68+
var proxy = new HttpProxy(req, socket, head);
69+
70+
// Tunnel websocket requests too
71+
proxy.proxyWebSocketRequest(port, host);
72+
73+
});
74+
}
75+
6476
return server;
6577
};
6678

@@ -74,12 +86,21 @@ exports.setMax = function (value) {
7486
manager.setMaxClients(max);
7587
};
7688

77-
var HttpProxy = function (req, res) {
89+
var HttpProxy = function (req, res, head) {
7890
this.emitter = new(events.EventEmitter);
7991
this.events = {};
8092
this.req = req;
81-
this.res = res;
82-
this.watch(req);
93+
// If this request is upgrade request
94+
// No response will be passed
95+
if (!req.headers.upgrade) {
96+
this.res = res;
97+
this.watch(req);
98+
} else {
99+
// Second argument will be socket
100+
this.sock = res;
101+
this.head = head;
102+
this.watch(res);
103+
}
83104
};
84105

85106
HttpProxy.prototype = {
@@ -91,7 +112,7 @@ HttpProxy.prototype = {
91112
}
92113
return arr;
93114
},
94-
115+
95116
watch: function (req) {
96117
this.events = [];
97118
var self = this;
@@ -106,11 +127,11 @@ HttpProxy.prototype = {
106127
req.addListener('data', this.onData);
107128
req.addListener('end', this.onEnd);
108129
},
109-
130+
110131
unwatch: function (req) {
111132
req.removeListener('data', this.onData);
112133
req.removeListener('end', this.onEnd);
113-
134+
114135
// Rebroadcast any events that have been buffered
115136
for (var i = 0, len = this.events.length; i < len; ++i) {
116137
req.emit.apply(req, this.events[i]);
@@ -121,33 +142,31 @@ HttpProxy.prototype = {
121142
// Remark: nodeProxy.body exists solely for testability
122143
var self = this, req = this.req, res = this.res;
123144
self.body = '';
124-
145+
125146
// Open new HTTP request to internal resource with will act as a reverse proxy pass
126147
var p = manager.getPool(port, server);
127-
128148
sys.puts('current pool count for ' + req.headers.host + ":" + port + ' ' +p.clients.length);
129-
149+
130150
p.on('error', function (err) {
131151
// Remark: We should probably do something here
132-
// but this is a hot-fix because I don't think 'pool'
152+
// but this is a hot-fix because I don't think 'pool'
133153
// should be emitting this event.
134-
sys.puts('p.on, error fired'.red);
135-
eyes.inspect(err);
136-
this.res.end();
137154
});
138155

139156
p.request(req.method, req.url, req.headers, function (reverse_proxy) {
140157
// Create an error handler so we can use it temporarily
141158
var error = function (err) {
142-
res.writeHead(200, {'Content-Type': 'text/plain'});
159+
res.writeHead(500, {'Content-Type': 'text/plain'});
143160

144161
if(req.method !== 'HEAD') {
145-
res.write('An error has occurred: ' + sys.puts(JSON.stringify(err)));
162+
res.write('An error has occurred: ' + JSON.stringify(err));
146163
}
147-
164+
165+
// Response end may never come so removeListener here
166+
reverse_proxy.removeListener('error', error);
148167
res.end();
149168
};
150-
169+
151170
// Add a listener for the connection timeout event
152171
reverse_proxy.addListener('error', error);
153172

@@ -161,6 +180,13 @@ HttpProxy.prototype = {
161180
// Set the response headers of the client response
162181
res.writeHead(response.statusCode, response.headers);
163182

183+
// Status code = 304
184+
// No 'data' event and no 'end'
185+
if (response.statusCode === 304) {
186+
res.end();
187+
return;
188+
}
189+
164190
// Add event handler for the proxied response in chunks
165191
response.addListener('data', function (chunk) {
166192
if(req.method !== 'HEAD') {
@@ -173,6 +199,7 @@ HttpProxy.prototype = {
173199
response.addListener('end', function () {
174200
// Remark: Emit the end event for testability
175201
self.emitter.emit('proxy', null, self.body);
202+
reverse_proxy.removeListener('error', error);
176203
res.end();
177204
});
178205
});
@@ -185,11 +212,206 @@ HttpProxy.prototype = {
185212
// At the end of the client request, we are going to stop the proxied request
186213
req.addListener('end', function () {
187214
reverse_proxy.end();
188-
reverse_proxy.removeListener('error', error);
189215
});
190216

191217
self.unwatch(req);
192218
});
219+
},
220+
221+
/**
222+
* WebSocket Tunnel realization
223+
* Copyright (c) 2010 Fedor Indutny : http://github.com/donnerjack13589
224+
*/
225+
proxyWebSocketRequest: function (port, server, host) {
226+
var self = this, req = self.req, socket = self.sock, head = self.head,
227+
headers = new _headers(req.headers), CRLF = '\r\n';
228+
229+
// Will generate clone of headers
230+
// To not change original
231+
function _headers(headers) {
232+
var h = {};
233+
for (var i in headers) {
234+
h[i] = headers[i];
235+
}
236+
return h;
237+
}
238+
239+
// WebSocket requests has
240+
// method = GET
241+
if (req.method !== 'GET' || headers.upgrade.toLowerCase() !== 'websocket') {
242+
// This request is not WebSocket request
243+
return;
244+
}
245+
246+
// Turn of all bufferings
247+
// For server set KeepAlive
248+
// For client set encoding
249+
function _socket(socket, server) {
250+
socket.setTimeout(0);
251+
socket.setNoDelay(true);
252+
if (server) {
253+
socket.setKeepAlive(true, 0);
254+
}
255+
else {
256+
socket.setEncoding('utf8');
257+
}
258+
}
259+
260+
// Client socket
261+
_socket(socket);
262+
263+
// If host is undefined
264+
// Get it from headers
265+
if (!host) {
266+
host = headers.Host;
267+
}
268+
269+
// Remote host address
270+
var remote_host = server + (port - 80 === 0 ? '' : ':' + port);
271+
272+
// Change headers
273+
headers.Host = remote_host;
274+
headers.Origin = 'http://' + remote_host;
275+
276+
// Open request
277+
var p = manager.getPool(port, server);
278+
279+
p.getClient(function(client) {
280+
// Based on 'pool/main.js'
281+
var request = client.request('GET', req.url, headers);
282+
283+
var errorListener = function (error) {
284+
client.removeListener('error', errorListener);
285+
286+
// Remove the client from the pool's available clients since it has errored
287+
p.clients.splice(p.clients.indexOf(client), 1);
288+
socket.end();
289+
}
290+
291+
// Not disconnect on update
292+
client.on('upgrade', function(request, remote_socket, head) {
293+
// Prepare socket
294+
_socket(remote_socket, true);
295+
296+
// Emit event
297+
onUpgrade(remote_socket);
298+
});
299+
300+
client.on('error', errorListener);
301+
request.on('response', function (response) {
302+
response.on('end', function () {
303+
client.removeListener('error', errorListener);
304+
client.busy = false;
305+
p.onFree(client);
306+
})
307+
})
308+
client.busy = true;
309+
310+
var handshake;
311+
request.socket.on('data', handshake = function(data) {
312+
// Handshaking
313+
314+
// Ok, kind of harmfull part of code
315+
// Socket.IO is sending hash at the end of handshake
316+
// If protocol = 76
317+
// But we need to replace 'host' and 'origin' in response
318+
// So we split data to printable data and to non-printable
319+
// (Non-printable will come after double-CRLF)
320+
var sdata = data.toString();
321+
322+
// Get Printable
323+
sdata = sdata.substr(0, sdata.search(CRLF + CRLF));
324+
325+
// Get Non-Printable
326+
data = data.slice(Buffer.byteLength(sdata), data.length);
327+
328+
// Replace host and origin
329+
sdata = sdata.replace(remote_host, host)
330+
.replace(remote_host, host);
331+
332+
try {
333+
// Write printable
334+
socket.write(sdata);
335+
336+
// Write non-printable
337+
socket.write(data);
338+
}
339+
catch (e) {
340+
request.end();
341+
socket.end();
342+
}
343+
344+
// Catch socket errors
345+
socket.on('error', function() {
346+
request.end();
347+
});
348+
349+
// Remove data listener now that the 'handshake' is complete
350+
request.socket.removeListener('data', handshake);
351+
});
352+
353+
// Write upgrade-head
354+
try {
355+
request.write(head);
356+
}
357+
catch(e) {
358+
request.end();
359+
socket.end();
360+
}
361+
self.unwatch(socket);
362+
});
363+
364+
// Request
365+
366+
function onUpgrade(reverse_proxy) {
367+
var listeners = {};
368+
369+
// We're now connected to the server, so lets change server socket
370+
reverse_proxy.on('data', listeners._r_data = function(data) {
371+
// Pass data to client
372+
if (socket.writable) {
373+
try {
374+
socket.write(data);
375+
}
376+
catch (e) {
377+
socket.end();
378+
reverse_proxy.end();
379+
}
380+
}
381+
});
382+
383+
socket.on('data', listeners._data = function(data){
384+
// Pass data from client to server
385+
try {
386+
reverse_proxy.write(data);
387+
}
388+
catch (e) {
389+
reverse_proxy.end();
390+
socket.end();
391+
}
392+
});
393+
394+
// Detach event listeners from reverse_proxy
395+
function detach() {
396+
reverse_proxy.removeListener('close', listeners._r_close);
397+
reverse_proxy.removeListener('data', listeners._r_data);
398+
socket.removeListener('data', listeners._data);
399+
socket.removeListener('close', listeners._close);
400+
}
401+
402+
// Hook disconnections
403+
reverse_proxy.on('end', listeners._r_close = function() {
404+
socket.end();
405+
detach();
406+
});
407+
408+
socket.on('end', listeners._close = function() {
409+
reverse_proxy.end();
410+
detach();
411+
});
412+
413+
};
414+
193415
}
194416
};
195417

0 commit comments

Comments
 (0)