23
23
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
24
25
25
*/
26
-
26
+
27
27
var sys = require ( 'sys' ) ,
28
28
http = require ( 'http' ) ,
29
29
events = require ( 'events' ) ,
@@ -43,24 +43,36 @@ exports.createServer = function () {
43
43
callback = typeof args [ args . length - 1 ] === 'function' && args . pop ( ) ;
44
44
if ( args [ 0 ] ) port = args [ 0 ] ;
45
45
if ( args [ 1 ] ) host = args [ 1 ] ;
46
-
46
+
47
47
var server = http . createServer ( function ( req , res ) {
48
48
var proxy = new HttpProxy ( req , res ) ;
49
-
49
+
50
50
proxy . emitter . on ( 'proxy' , function ( err , body ) {
51
51
server . emit ( 'proxy' , err , body ) ;
52
52
} ) ;
53
-
53
+
54
54
// If we were passed a callback to process the request
55
55
// or response in some way, then call it.
56
56
if ( callback ) {
57
57
callback ( req , res , proxy ) ;
58
58
}
59
- else {
59
+ else {
60
60
proxy . proxyRequest ( port , server ) ;
61
61
}
62
62
} ) ;
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
+
64
76
return server ;
65
77
} ;
66
78
@@ -74,12 +86,21 @@ exports.setMax = function (value) {
74
86
manager . setMaxClients ( max ) ;
75
87
} ;
76
88
77
- var HttpProxy = function ( req , res ) {
89
+ var HttpProxy = function ( req , res , head ) {
78
90
this . emitter = new ( events . EventEmitter ) ;
79
91
this . events = { } ;
80
92
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
+ }
83
104
} ;
84
105
85
106
HttpProxy . prototype = {
@@ -91,7 +112,7 @@ HttpProxy.prototype = {
91
112
}
92
113
return arr ;
93
114
} ,
94
-
115
+
95
116
watch : function ( req ) {
96
117
this . events = [ ] ;
97
118
var self = this ;
@@ -106,11 +127,11 @@ HttpProxy.prototype = {
106
127
req . addListener ( 'data' , this . onData ) ;
107
128
req . addListener ( 'end' , this . onEnd ) ;
108
129
} ,
109
-
130
+
110
131
unwatch : function ( req ) {
111
132
req . removeListener ( 'data' , this . onData ) ;
112
133
req . removeListener ( 'end' , this . onEnd ) ;
113
-
134
+
114
135
// Rebroadcast any events that have been buffered
115
136
for ( var i = 0 , len = this . events . length ; i < len ; ++ i ) {
116
137
req . emit . apply ( req , this . events [ i ] ) ;
@@ -121,33 +142,31 @@ HttpProxy.prototype = {
121
142
// Remark: nodeProxy.body exists solely for testability
122
143
var self = this , req = this . req , res = this . res ;
123
144
self . body = '' ;
124
-
145
+
125
146
// Open new HTTP request to internal resource with will act as a reverse proxy pass
126
147
var p = manager . getPool ( port , server ) ;
127
-
128
148
sys . puts ( 'current pool count for ' + req . headers . host + ":" + port + ' ' + p . clients . length ) ;
129
-
149
+
130
150
p . on ( 'error' , function ( err ) {
131
151
// 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'
133
153
// should be emitting this event.
134
- sys . puts ( 'p.on, error fired' . red ) ;
135
- eyes . inspect ( err ) ;
136
- this . res . end ( ) ;
137
154
} ) ;
138
155
139
156
p . request ( req . method , req . url , req . headers , function ( reverse_proxy ) {
140
157
// Create an error handler so we can use it temporarily
141
158
var error = function ( err ) {
142
- res . writeHead ( 200 , { 'Content-Type' : 'text/plain' } ) ;
159
+ res . writeHead ( 500 , { 'Content-Type' : 'text/plain' } ) ;
143
160
144
161
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 ) ) ;
146
163
}
147
-
164
+
165
+ // Response end may never come so removeListener here
166
+ reverse_proxy . removeListener ( 'error' , error ) ;
148
167
res . end ( ) ;
149
168
} ;
150
-
169
+
151
170
// Add a listener for the connection timeout event
152
171
reverse_proxy . addListener ( 'error' , error ) ;
153
172
@@ -161,6 +180,13 @@ HttpProxy.prototype = {
161
180
// Set the response headers of the client response
162
181
res . writeHead ( response . statusCode , response . headers ) ;
163
182
183
+ // Status code = 304
184
+ // No 'data' event and no 'end'
185
+ if ( response . statusCode === 304 ) {
186
+ res . end ( ) ;
187
+ return ;
188
+ }
189
+
164
190
// Add event handler for the proxied response in chunks
165
191
response . addListener ( 'data' , function ( chunk ) {
166
192
if ( req . method !== 'HEAD' ) {
@@ -173,6 +199,7 @@ HttpProxy.prototype = {
173
199
response . addListener ( 'end' , function ( ) {
174
200
// Remark: Emit the end event for testability
175
201
self . emitter . emit ( 'proxy' , null , self . body ) ;
202
+ reverse_proxy . removeListener ( 'error' , error ) ;
176
203
res . end ( ) ;
177
204
} ) ;
178
205
} ) ;
@@ -185,11 +212,206 @@ HttpProxy.prototype = {
185
212
// At the end of the client request, we are going to stop the proxied request
186
213
req . addListener ( 'end' , function ( ) {
187
214
reverse_proxy . end ( ) ;
188
- reverse_proxy . removeListener ( 'error' , error ) ;
189
215
} ) ;
190
216
191
217
self . unwatch ( req ) ;
192
218
} ) ;
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
+
193
415
}
194
416
} ;
195
417
0 commit comments