@@ -12,9 +12,10 @@ var util = require('util');
12
12
var zlib = require ( 'zlib' ) ;
13
13
14
14
var assert = require ( 'assert-plus' ) ;
15
+ var backoff = require ( 'backoff' ) ;
15
16
var KeepAliveAgent = require ( 'keep-alive-agent' ) ;
16
17
var mime = require ( 'mime' ) ;
17
- var retry = require ( 'retry ' ) ;
18
+ var once = require ( 'once ' ) ;
18
19
var semver = require ( 'semver' ) ;
19
20
var uuid = require ( 'node-uuid' ) ;
20
21
@@ -42,38 +43,84 @@ function defaultUserAgent() {
42
43
}
43
44
44
45
45
- function newRetryOperation ( options ) {
46
- var operation ;
47
- if ( options . retry !== false ) {
48
- operation = retry . operation ( options . retry ) ;
49
- } else {
50
- // Stub out node-retry so the code isn't fugly in the
51
- // mainline client
52
- operation = {
53
- attempt : function ( callback ) {
54
- return ( callback ( 1 ) ) ;
55
- } ,
56
- retry : function ( err ) {
57
- operation . _err = err ;
58
- return ( false ) ;
59
- } ,
60
- mainError : function ( ) {
61
- return ( operation . _err ) ;
46
+ function ConnectTimeoutError ( ms ) {
47
+ if ( Error . captureStackTrace )
48
+ Error . captureStackTrace ( this , ConnectTimeoutError ) ;
49
+
50
+ this . message = 'connect timeout after ' + ms + 'ms' ;
51
+ }
52
+ util . inherits ( ConnectTimeoutError , Error ) ;
53
+
54
+
55
+ function rawRequest ( opts , cb ) {
56
+ assert . object ( opts , 'options' ) ;
57
+ assert . object ( opts . log , 'options.log' ) ;
58
+ assert . func ( cb , 'callback' ) ;
59
+
60
+ cb = once ( cb ) ;
61
+
62
+ var log = opts . log ;
63
+ var proto = opts . protocol === 'https:' ? https : http ;
64
+ var timer ;
65
+
66
+ if ( opts . cert && opts . key )
67
+ opts . agent = false ;
68
+
69
+ if ( opts . connectTimeout ) {
70
+ timer = setTimeout ( function connectTimeout ( ) {
71
+ timer = null ;
72
+ if ( req ) {
73
+ req . abort ( ) ;
62
74
}
63
- } ;
75
+
76
+ cb ( new ConnectTimeoutError ( opts . connectTimeout ) , req ) ;
77
+ } , opts . connectTimeout ) ;
64
78
}
65
79
66
- return ( operation ) ;
67
- }
80
+ var req = proto . request ( opts , function onResponse ( res ) {
81
+ clearTimeout ( timer ) ;
82
+ log . trace ( { client_res : res } , 'Response received' ) ;
68
83
84
+ res . log = log ;
69
85
70
- function ConnectTimeoutError ( ) {
71
- if ( Error . captureStackTrace )
72
- Error . captureStackTrace ( this , ConnectTimeoutError ) ;
86
+ var err ;
87
+ if ( res . statusCode >= 400 )
88
+ err = errors . codeToHttpError ( res . statusCode ) ;
73
89
74
- this . message = util . format . apply ( util , arguments ) ;
75
- }
76
- util . inherits ( ConnectTimeoutError , Error ) ;
90
+ req . removeAllListeners ( 'error' ) ;
91
+ req . removeAllListeners ( 'socket' ) ;
92
+ req . emit ( 'result' , ( err || null ) , res ) ;
93
+ } ) ;
94
+ req . log = log ;
95
+
96
+ req . on ( 'error' , function onError ( err ) {
97
+ log . trace ( { err : err } , 'Request failed' ) ;
98
+ clearTimeout ( timer ) ;
99
+
100
+ cb ( err , req ) ;
101
+ if ( req ) {
102
+ process . nextTick ( function ( ) {
103
+ req . emit ( 'result' , err , null ) ;
104
+ } ) ;
105
+ }
106
+ } ) ;
107
+
108
+ req . once ( 'socket' , function onSocket ( socket ) {
109
+ if ( socket . writable && ! socket . _connecting ) {
110
+ clearTimeout ( timer ) ;
111
+ cb ( null , req ) ;
112
+ return ;
113
+ }
114
+
115
+ socket . once ( 'connect' , function onConnect ( ) {
116
+ clearTimeout ( timer ) ;
117
+ cb ( null , req ) ;
118
+ } ) ;
119
+ } ) ;
120
+
121
+ if ( opts . signRequest )
122
+ opts . signRequest ( req ) ;
123
+ } // end `rawRequest`
77
124
78
125
79
126
@@ -234,115 +281,39 @@ HttpClient.prototype.basicAuth = function basicAuth(username, password) {
234
281
} ;
235
282
236
283
237
- HttpClient . prototype . request = function request ( options , callback ) {
238
- var done = false ;
239
-
240
- function _callback ( err , req ) {
241
- if ( done )
242
- return ( false ) ;
243
-
244
- done = true ;
245
- return ( callback ( err , req ) ) ;
246
- }
284
+ HttpClient . prototype . request = function request ( opts , cb ) {
285
+ assert . object ( opts , 'options' ) ;
286
+ assert . func ( cb , 'callback' ) ;
247
287
248
- // Don't rely on x-request-id, as the server may not accept it
249
- var log = this . log ;
250
- var operation = newRetryOperation ( options ) ;
251
- var proto = options . protocol === 'https:' ? https : http ;
252
- var signRequest = this . signRequest ;
253
-
254
- operation . attempt ( function retryCallback ( currentAttempt ) {
255
- var timer = false ;
256
- function clearTimer ( ) {
257
- if ( timer )
258
- clearTimeout ( timer ) ;
288
+ var self = this ;
259
289
260
- timer = false ;
290
+ // Make one request, then kick off the next call; note that
291
+ // node-backoff always imposes an initial time delay before
292
+ // starting, which we don't want.
293
+ rawRequest ( opts , function _callback ( err , req ) {
294
+ if ( ! err || opts . retry === false ) {
295
+ cb ( err , req ) ;
296
+ return ;
261
297
}
262
298
263
- log . trace ( {
264
- client_req : options ,
265
- attempt : currentAttempt
266
- } , 'sending request' ) ;
299
+ var retry = backoff . call ( rawRequest , opts , cb ) ;
300
+ retry . setStrategy ( new backoff . ExponentialStrategy ( {
301
+ initialDelay : self . retry . minTimeout || 1000 ,
302
+ maxDelay : self . retry . maxTimeout || Infinity
303
+ } ) ) ;
267
304
268
- if ( options . cert && options . key )
269
- options . agent = new proto . Agent ( options ) ;
270
-
271
- var req = proto . request ( options , function requestCallback ( res ) {
272
- clearTimer ( ) ;
273
- log . trace ( { client_res : res } , 'Response received' ) ;
274
-
275
- res . log = log ;
276
-
277
- var err = null ;
278
- if ( res . statusCode >= 400 )
279
- err = errors . codeToHttpError ( res . statusCode ) ;
280
-
281
- req . emit ( 'result' , err , res ) ;
282
- } ) ;
283
-
284
- req . log = log ;
285
- if ( signRequest )
286
- signRequest ( req ) ;
287
-
288
- req . on ( 'error' , function onError ( err ) {
289
- log . trace ( { err : err } , 'Request failed' ) ;
290
- clearTimer ( ) ;
291
- if ( ! done ) {
292
- if ( ! operation . retry ( err ) ) {
293
- err = operation . mainError ( ) || err ;
294
- return ( _callback ( err ) ) ;
295
- }
296
- } else {
297
- // Since we're changing subtly the way the
298
- // client interacts with callbacks to handle
299
- // socket connections, we have to handle the
300
- // case where the caller was told we're
301
- // "good to go", and instead mimic the behavior
302
- // of there being a bad http code. I.e., a
303
- // server may just TCP RST us instead of a 400
304
- req . emit ( 'result' , err , null ) ;
305
- }
306
-
307
- return ( false ) ;
308
- } ) ;
309
-
310
- if ( options . connectTimeout ) {
311
- timer = setTimeout ( function connectTimeout ( ) {
312
- clearTimer ( ) ;
313
- req . abort ( ) ;
314
-
315
- var to = options . connectTimeout ;
316
- var err = new ConnectTimeoutError ( 'timeout ' +
317
- 'after ' +
318
- to +
319
- 'ms' ) ;
320
-
321
- if ( ! operation . retry ( err ) ) {
322
- err = operation . mainError ( ) || err ;
323
- return ( _callback ( err ) ) ;
324
- }
325
-
326
- return ( false ) ;
327
- } , options . connectTimeout ) ;
305
+ if ( typeof ( opts . retry . retries ) === 'number' ) {
306
+ if ( opts . retry . retries !== Infinity )
307
+ retry . failAfter ( opts . retry . retries ) ;
308
+ } else {
309
+ retry . failAfter ( 4 ) ;
328
310
}
329
311
330
- req . once ( 'socket' , function onSocket ( socket ) {
331
- if ( socket . writable && ! socket . _connecting ) {
332
- clearTimer ( ) ;
333
- return ( _callback ( null , req ) ) ;
334
- }
335
-
336
- return socket . once ( 'connect' , function onConnect ( ) {
337
- clearTimer ( ) ;
338
- return ( _callback ( null , req ) ) ;
339
- } ) ;
340
- } ) ;
312
+ retry . on ( 'backoff' , self . emit . bind ( self , 'attempt' ) ) ;
341
313
} ) ;
342
314
} ;
343
315
344
316
345
-
346
317
HttpClient . prototype . _options = function ( method , options ) {
347
318
if ( typeof ( options ) !== 'object' )
348
319
options = { path : options } ;
@@ -354,9 +325,11 @@ HttpClient.prototype._options = function (method, options) {
354
325
connectTimeout : options . connectTimeout || self . connectTimeout ,
355
326
headers : options . headers || { } ,
356
327
key : options . key || self . key ,
328
+ log : options . log || self . log ,
357
329
method : method ,
358
330
path : options . path || self . path ,
359
- retry : options . retry || self . retry
331
+ retry : options . retry || self . retry ,
332
+ signRequest : options . signRequest || self . signRequest
360
333
} ;
361
334
362
335
// Backwards compatibility with restify < 1.0
0 commit comments