@@ -32,7 +32,13 @@ var util = require('util'),
32
32
ProxyTable = require ( './proxy-table' ) . ProxyTable ,
33
33
maxSockets = 100 ;
34
34
35
-
35
+ //
36
+ // ### function _getAgent (host, port)
37
+ // #### @host {string} Host of the agent to get
38
+ // #### @port {number} Port of the agent to get
39
+ // Retreives an agent from the `http` module
40
+ // and sets the `maxSockets` property appropriately.
41
+ //
36
42
function _getAgent ( host , port ) {
37
43
//
38
44
// TODO (indexzero): Make this configurable for http / https
@@ -42,17 +48,33 @@ function _getAgent (host, port) {
42
48
return agent ;
43
49
}
44
50
51
+ //
52
+ // ### function getMaxSockets ()
53
+ // Returns the maximum number of sockets
54
+ // allowed on __every__ outgoing request
55
+ // made by __all__ instances of `HttpProxy`
56
+ //
45
57
exports . getMaxSockets = function ( ) {
46
58
return maxSockets ;
47
59
} ;
48
60
61
+ //
62
+ // ### function setMaxSockets ()
63
+ // Sets the maximum number of sockets
64
+ // allowed on __every__ outgoing request
65
+ // made by __all__ instances of `HttpProxy`
66
+ //
49
67
exports . setMaxSockets = function ( value ) {
50
68
maxSockets = value ;
51
69
} ;
52
70
71
+ //
72
+ // ### function createServer ([port, host, options], handler)
73
+ //
74
+ //
53
75
exports . createServer = function ( ) {
54
76
var args , callback , port , host , forward ,
55
- silent , proxyTable , options = { } ;
77
+ silent , options , proxy , server ;
56
78
57
79
args = Array . prototype . slice . call ( arguments ) ;
58
80
callback = typeof args [ args . length - 1 ] === 'function' && args . pop ( ) ;
@@ -69,9 +91,8 @@ exports.createServer = function () {
69
91
}
70
92
}
71
93
72
- var proxy = new HttpProxy ( options ) ;
73
-
74
- var server = http . createServer ( function ( req , res ) {
94
+ proxy = new HttpProxy ( options ) ;
95
+ server = http . createServer ( function ( req , res ) {
75
96
winston . verbose ( 'Incoming HTTP request to: ' + req . headers . host + req . url ) ;
76
97
77
98
// If we were passed a callback to process the request
@@ -98,20 +119,40 @@ exports.createServer = function () {
98
119
99
120
proxy . on ( 'routes' , function ( routes ) {
100
121
server . emit ( 'routes' , routes ) ;
101
- } )
122
+ } ) ;
102
123
103
124
if ( ! callback ) {
104
125
// WebSocket support: if callback is empty tunnel
105
126
// websocket request automatically
106
127
server . on ( 'upgrade' , function ( req , socket , head ) {
107
128
// Tunnel websocket requests too
129
+
108
130
proxy . proxyWebSocketRequest ( port , host ) ;
109
131
} ) ;
110
132
}
111
133
112
134
return server ;
113
135
} ;
114
136
137
+ //
138
+ // ### function HttpProxy (options)
139
+ // #### @options {Object} Options for this instance.
140
+ // Constructor function for new instances of HttpProxy responsible
141
+ // for managing the life-cycle of streaming reverse proxyied HTTP requests.
142
+ //
143
+ // Example options:
144
+ //
145
+ // {
146
+ // router: {
147
+ // 'foo.com': 'localhost:8080',
148
+ // 'bar.com': 'localhost:8081'
149
+ // },
150
+ // forward: {
151
+ // host: 'localhost',
152
+ // port: 9001
153
+ // }
154
+ // }
155
+ //
115
156
var HttpProxy = exports . HttpProxy = function ( options ) {
116
157
events . EventEmitter . call ( this ) ;
117
158
@@ -127,38 +168,36 @@ var HttpProxy = exports.HttpProxy = function (options) {
127
168
}
128
169
} ;
129
170
171
+ // Inherit from events.EventEmitter
130
172
util . inherits ( HttpProxy , events . EventEmitter ) ;
131
173
132
- HttpProxy . prototype . close = function ( ) {
133
- if ( this . proxyTable ) this . proxyTable . close ( ) ;
134
- } ;
135
-
136
- /**
137
- * Pause `data` and `end` events on the given `obj`.
138
- * Middleware performing async tasks _should_ utilize
139
- * this utility (or similar), to re-emit data once
140
- * the async operation has completed, otherwise these
141
- * events may be lost.
142
- *
143
- * var pause = utils.pause(req);
144
- * fs.readFile(path, function(){
145
- * next();
146
- * pause.resume();
147
- * });
148
- *
149
- * @param {Object } obj
150
- * @return {Object }
151
- * @api public
152
- */
174
+ //
175
+ // ### function pause (obj)
176
+ // #### @obj {Object} Object to pause events from
177
+ // Pause `data` and `end` events on the given `obj`.
178
+ // Consumers of HttpProxy performing async tasks
179
+ // __must__ utilize this utility, to re-emit data once
180
+ // the async operation has completed, otherwise these
181
+ // __events will be lost.__
182
+ //
183
+ // var pause = httpProxy.pause(req);
184
+ // fs.readFile(path, function(){
185
+ // httpProxy.proxyRequest(req, res, host, port, paused);
186
+ // });
187
+ //
188
+ // __Attribution:__ This approach is based heavily on
189
+ // [Connect](https://github.com/senchalabs/connect/blob/master/lib/utils.js#L157).
190
+ // However, this is not a big leap from the implementation in node-http-proxy < 0.4.0.
191
+ // This simply chooses to manage the scope of the events on a new Object literal as opposed to
192
+ // [on the HttpProxy instance](https://github.com/nodejitsu/node-http-proxy/blob/v0.3.1/lib/node-http-proxy.js#L154).
193
+ //
153
194
HttpProxy . prototype . pause = function ( obj ) {
154
195
var onData , onEnd , events = [ ] ;
155
196
156
- // buffer data
157
197
obj . on ( 'data' , onData = function ( data , encoding ) {
158
198
events . push ( [ 'data' , data , encoding ] ) ;
159
199
} ) ;
160
200
161
- // buffer end
162
201
obj . on ( 'end' , onEnd = function ( data , encoding ) {
163
202
events . push ( [ 'end' , data , encoding ] ) ;
164
203
} ) ;
@@ -177,8 +216,25 @@ HttpProxy.prototype.pause = function (obj) {
177
216
} ;
178
217
} ;
179
218
219
+ //
220
+ // ### function close ()
221
+ // Frees the resources associated with this instance,
222
+ // if they exist.
223
+ //
224
+ HttpProxy . prototype . close = function ( ) {
225
+ if ( this . proxyTable ) this . proxyTable . close ( ) ;
226
+ } ;
227
+
228
+ //
229
+ // ### function proxyRequest (req, res, [port, host, paused])
230
+ // #### @req {ServerRequest} Incoming HTTP Request to proxy.
231
+ // #### @res {ServerResponse} Outgoing HTTP Request to write proxied data to.
232
+ // #### @port {number} **Optional** Port to use on the proxy target host.
233
+ // #### @host {string} **Optional** Host of the proxy target.
234
+ // #### @paused {Object} **Optional** Result from `httpProxy.pause(req)`
235
+ //
180
236
HttpProxy . prototype . proxyRequest = function ( req , res , port , host , paused ) {
181
- var self = this , reverseProxy , location ;
237
+ var self = this , reverseProxy , location , errState = false ;
182
238
183
239
//
184
240
// Check the proxy table for this instance to see if we need
@@ -189,6 +245,10 @@ HttpProxy.prototype.proxyRequest = function (req, res, port, host, paused) {
189
245
if ( this . proxyTable && ! host ) {
190
246
location = this . proxyTable . getProxyLocation ( req ) ;
191
247
248
+ //
249
+ // If no location is returned from the ProxyTable instance
250
+ // then respond with `404` since we do not have a valid proxy target.
251
+ //
192
252
if ( ! location ) {
193
253
res . writeHead ( 404 ) ;
194
254
return res . end ( ) ;
@@ -198,36 +258,34 @@ HttpProxy.prototype.proxyRequest = function (req, res, port, host, paused) {
198
258
// When using the ProxyTable in conjunction with an HttpProxy instance
199
259
// only the following arguments are valid:
200
260
//
201
- // * proxy.proxyRequest(req, res, port, host, paused): This will be skipped
202
- // * proxy.proxyRequest(req, res, paused): Paused will get updated appropriately
203
- // * proxy.proxyRequest(req, res): No effect `undefined = undefined`
261
+ // * ` proxy.proxyRequest(req, res, port, host, paused)` : This will be skipped
262
+ // * ` proxy.proxyRequest(req, res, paused)` : Paused will get updated appropriately
263
+ // * ` proxy.proxyRequest(req, res)` : No effect `undefined = undefined`
204
264
//
205
265
paused = port ;
206
266
port = location . port ;
207
267
host = location . host ;
208
268
}
209
269
270
+ //
271
+ // If forwarding is enabled for this instance, foward proxy the
272
+ // specified request to the address provided in `this.options.forward`
273
+ //
210
274
if ( this . options . forward ) {
211
275
winston . verbose ( 'Forwarding HTTP request to: ' + this . options . forward . host + ':' + this . options . forward . port ) ;
212
276
this . _forwardRequest ( req ) ;
213
277
}
214
278
215
- // Create an error handler so we can use it temporarily
216
- function error ( obj ) {
217
- var fn = function ( err ) {
218
- res . writeHead ( 500 , { 'Content-Type' : 'text/plain' } ) ;
279
+ function proxyError ( err ) {
280
+ errState = true ;
281
+ res . writeHead ( 500 , { 'Content-Type' : 'text/plain' } ) ;
219
282
220
- if ( req . method !== 'HEAD' ) {
221
- res . write ( 'An error has occurred: ' + JSON . stringify ( err ) ) ;
222
- }
223
-
224
- // Response end may never come so removeListener here
225
- obj . removeListener ( 'error' , fn ) ;
226
- res . end ( ) ;
227
- } ;
283
+ if ( req . method !== 'HEAD' ) {
284
+ res . write ( 'An error has occurred: ' + JSON . stringify ( err ) ) ;
285
+ }
228
286
229
- return fn ;
230
- } ;
287
+ res . end ( ) ;
288
+ }
231
289
232
290
// Open new HTTP request to internal resource with will act as a reverse proxy pass
233
291
reverseProxy = http . request ( {
@@ -263,26 +321,31 @@ HttpProxy.prototype.proxyRequest = function (req, res, port, host, paused) {
263
321
264
322
// Add event listener for end of proxied response
265
323
response . on ( 'end' , function ( ) {
266
- reverseProxy . removeListener ( 'error' , reverseProxyError ) ;
267
- res . end ( ) ;
324
+ if ( ! errState ) {
325
+ reverseProxy . removeListener ( 'error' , proxyError ) ;
326
+ res . end ( ) ;
327
+ }
268
328
} ) ;
269
329
} ) ;
270
330
271
331
// Add a listener for the connection timeout event
272
- var reverseProxyError = error ( reverseProxy ) ;
273
- reverseProxy . on ( 'error' , reverseProxyError ) ;
332
+ reverseProxy . once ( 'error' , proxyError ) ;
274
333
275
334
// Chunk the client request body as chunks from the proxied request come in
276
335
req . on ( 'data' , function ( chunk ) {
277
- reverseProxy . write ( chunk ) ;
336
+ if ( ! errState ) {
337
+ reverseProxy . write ( chunk ) ;
338
+ }
278
339
} ) ;
279
340
280
341
// At the end of the client request, we are going to stop the proxied request
281
342
req . on ( 'end' , function ( ) {
282
- reverseProxy . end ( ) ;
343
+ if ( ! errState ) {
344
+ reverseProxy . end ( ) ;
345
+ }
283
346
} ) ;
284
347
285
- if ( paused ) {
348
+ if ( paused && ! errState ) {
286
349
paused . resume ( ) ;
287
350
}
288
351
} ;
@@ -308,11 +371,12 @@ HttpProxy.prototype._forwardRequest = function (req) {
308
371
//
309
372
} ) ;
310
373
311
- // Add a listener for the connection timeout event
312
- forwardProxy . on ( 'error' , function ( err ) {
313
- // Remark: Ignoring this error in the event
314
- // forward target doesn't exist.
315
- } ) ;
374
+ // Add a listener for the connection timeout event.
375
+ //
376
+ // Remark: Ignoring this error in the event
377
+ // forward target doesn't exist.
378
+ //
379
+ forwardProxy . on ( 'error' , function ( err ) { } ) ;
316
380
317
381
// Chunk the client request body as chunks from the proxied request come in
318
382
req . on ( 'data' , function ( chunk ) {
0 commit comments