@superhero/http-request 4.0.7 → 4.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +104 -39
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -75,7 +75,7 @@ export default class Request
|
|
|
75
75
|
*
|
|
76
76
|
* @see {@link https://nodejs.org/api/http2.html#http2connectauthority-options-listener}
|
|
77
77
|
*/
|
|
78
|
-
connect(authority,
|
|
78
|
+
connect(authority, options)
|
|
79
79
|
{
|
|
80
80
|
return new Promise(async (accept, reject) =>
|
|
81
81
|
{
|
|
@@ -83,20 +83,25 @@ export default class Request
|
|
|
83
83
|
|
|
84
84
|
const url = new URL(authority || this.config.base)
|
|
85
85
|
authority = url.protocol + '//' + url.host
|
|
86
|
+
options = Object.assign(this.config, options)
|
|
86
87
|
|
|
87
|
-
this.
|
|
88
|
+
this.config.base = authority
|
|
89
|
+
this.config.url = url.pathname + url.search
|
|
90
|
+
this.http2Session = http2.connect(authority, options, () =>
|
|
88
91
|
{
|
|
89
|
-
this.
|
|
90
|
-
this.
|
|
91
|
-
|
|
92
|
+
this.http2Session.removeAllListeners('error')
|
|
93
|
+
this.http2Session.on('error', console.error)
|
|
94
|
+
|
|
92
95
|
accept()
|
|
93
96
|
})
|
|
94
97
|
|
|
95
|
-
|
|
96
|
-
this.http2Session.once('
|
|
98
|
+
// If there is a error on connection, reject the promise.
|
|
99
|
+
this.http2Session.once('error', (reason) =>
|
|
97
100
|
{
|
|
98
|
-
|
|
99
|
-
|
|
101
|
+
const error = new Error(`Failed to connect to server over HTTP2 using authority: ${authority}`)
|
|
102
|
+
error.code = 'E_HTTP_REQUEST_CONNECT_ERROR'
|
|
103
|
+
error.cause = reason
|
|
104
|
+
reject(error)
|
|
100
105
|
})
|
|
101
106
|
})
|
|
102
107
|
}
|
|
@@ -110,23 +115,43 @@ export default class Request
|
|
|
110
115
|
{
|
|
111
116
|
return new Promise((accept, reject) =>
|
|
112
117
|
{
|
|
113
|
-
|
|
114
|
-
|
|
118
|
+
const http2Session = this.http2Session
|
|
119
|
+
|
|
120
|
+
if(http2Session)
|
|
115
121
|
{
|
|
116
|
-
|
|
122
|
+
http2Session.removeAllListeners()
|
|
123
|
+
|
|
124
|
+
if(false === http2Session.closed)
|
|
117
125
|
{
|
|
118
|
-
error
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
http2Session.close((error) =>
|
|
127
|
+
{
|
|
128
|
+
delete this.http2Session
|
|
129
|
+
|
|
130
|
+
error
|
|
131
|
+
? reject(error)
|
|
132
|
+
: accept()
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
return // await the close event
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
delete this.http2Session
|
|
126
139
|
}
|
|
140
|
+
|
|
141
|
+
// fallback to accept if nothing to close
|
|
142
|
+
accept()
|
|
127
143
|
})
|
|
128
144
|
}
|
|
129
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Reonnects to a HTTP/2 server using the last used configurations.
|
|
148
|
+
*/
|
|
149
|
+
async reconnect()
|
|
150
|
+
{
|
|
151
|
+
await this.close()
|
|
152
|
+
await this.connect()
|
|
153
|
+
}
|
|
154
|
+
|
|
130
155
|
/**
|
|
131
156
|
* GET – Read a resource or a collection of resources.
|
|
132
157
|
* Used to retrieve data without modifying it.
|
|
@@ -237,7 +262,18 @@ export default class Request
|
|
|
237
262
|
*
|
|
238
263
|
* @param {string} method
|
|
239
264
|
* @param {RequestOptions} options
|
|
265
|
+
*
|
|
240
266
|
* @returns {RequestResponse}
|
|
267
|
+
*
|
|
268
|
+
* @throws {Error} E_HTTP_REQUEST_CLIENT_ERROR
|
|
269
|
+
* @throws {Error} E_HTTP_REQUEST_CLIENT_TIMEOUT
|
|
270
|
+
* @throws {Error} E_HTTP_REQUEST_DOWNSTREAM_ERROR
|
|
271
|
+
* @throws {Error} E_HTTP_REQUEST_INVALID_RESPONSE_BODY_FORMAT
|
|
272
|
+
* @throws {Error} E_HTTP_REQUEST_INVALID_RESPONSE_STATUS
|
|
273
|
+
* @throws {Error} E_HTTP_REQUEST_HTTP2_SESSION_DESTROYED
|
|
274
|
+
* @throws {Error} E_HTTP_REQUEST_HTTP2_SESSION_CLOSED
|
|
275
|
+
* @throws {Error} E_HTTP_REQUEST_RETRY_HTTP2_RECONNECT
|
|
276
|
+
* @throws {Error} E_HTTP_REQUEST_RETRY_ERROR
|
|
241
277
|
*/
|
|
242
278
|
#fetch(method, options)
|
|
243
279
|
{
|
|
@@ -276,11 +312,6 @@ export default class Request
|
|
|
276
312
|
* @param {RequestOptions} options
|
|
277
313
|
*
|
|
278
314
|
* @returns {RequestResponse}
|
|
279
|
-
*
|
|
280
|
-
* @throws {Error} E_HTTP_REQUEST_CLIENT_TIMEOUT
|
|
281
|
-
* @throws {Error} E_HTTP_REQUEST_CLIENT_ERROR
|
|
282
|
-
* @throws {Error} E_HTTP_REQUEST_DOWNSTREAM_ERROR
|
|
283
|
-
* @throws {Error} E_HTTP_REQUEST_INVALID_RESPONSE_BODY_FORMAT
|
|
284
315
|
*/
|
|
285
316
|
#resolve(options)
|
|
286
317
|
{
|
|
@@ -307,6 +338,22 @@ export default class Request
|
|
|
307
338
|
|
|
308
339
|
#resolveHttp2Client(options, method, headers, url, accept, reject)
|
|
309
340
|
{
|
|
341
|
+
if(true === this.http2Session.destroyed)
|
|
342
|
+
{
|
|
343
|
+
const error = new Error('Session destroyed')
|
|
344
|
+
error.code = 'E_HTTP_REQUEST_HTTP2_SESSION_DESTROYED'
|
|
345
|
+
error.cause = `Can not perform request over a destroyed HTTP2 session to: ${url}`
|
|
346
|
+
return reject(error)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if(true === this.http2Session.closed)
|
|
350
|
+
{
|
|
351
|
+
const error = new Error('Session closed')
|
|
352
|
+
error.code = 'E_HTTP_REQUEST_HTTP2_SESSION_CLOSED'
|
|
353
|
+
error.cause = `Can not perform request over a closed HTTP2 session to: ${url}`
|
|
354
|
+
return reject(error)
|
|
355
|
+
}
|
|
356
|
+
|
|
310
357
|
delete headers['transfer-encoding']
|
|
311
358
|
|
|
312
359
|
const { pathname, search } = new URL(url)
|
|
@@ -361,7 +408,7 @@ export default class Request
|
|
|
361
408
|
#connectionClosed(upstream, reject)
|
|
362
409
|
{
|
|
363
410
|
upstream.removeAllListeners()
|
|
364
|
-
const error = new Error('
|
|
411
|
+
const error = new Error('Connection was closed unexpectedly')
|
|
365
412
|
error.code = 'E_HTTP_REQUEST_CLIENT_ERROR'
|
|
366
413
|
setImmediate(() => reject(error)) // this error is a fallback if the promise is not already resolved
|
|
367
414
|
}
|
|
@@ -411,18 +458,14 @@ export default class Request
|
|
|
411
458
|
|
|
412
459
|
/**
|
|
413
460
|
* Resolves the request in a retry loop.
|
|
414
|
-
*
|
|
415
461
|
* @param {RequestOptions} options
|
|
416
|
-
*
|
|
417
462
|
* @returns {RequestResponse}
|
|
418
|
-
*
|
|
419
|
-
* @throws {Error} E_HTTP_REQUEST_RETRY
|
|
420
463
|
*/
|
|
421
464
|
async #resolveRetryLoop(options)
|
|
422
465
|
{
|
|
423
466
|
const errorTrace = []
|
|
424
467
|
|
|
425
|
-
let retry = Math.abs(Math.floor(options.retry))
|
|
468
|
+
let retry = Math.abs(Math.floor(options.retry)) + 1
|
|
426
469
|
|
|
427
470
|
while(retry--)
|
|
428
471
|
{
|
|
@@ -450,11 +493,23 @@ export default class Request
|
|
|
450
493
|
}
|
|
451
494
|
else
|
|
452
495
|
{
|
|
453
|
-
this.#resolveRetryLoopError(options, errorTrace, error)
|
|
496
|
+
await this.#resolveRetryLoopError(options, errorTrace, error)
|
|
454
497
|
await wait(options.retryDelay)
|
|
455
498
|
}
|
|
456
499
|
}
|
|
457
500
|
}
|
|
501
|
+
|
|
502
|
+
if(errorTrace.every((error) => error.code === errorTrace[0].code))
|
|
503
|
+
{
|
|
504
|
+
throw errorTrace.pop()
|
|
505
|
+
}
|
|
506
|
+
else
|
|
507
|
+
{
|
|
508
|
+
const error = new Error('Multiple types of errors occurred during the retry loop')
|
|
509
|
+
error.code = 'E_HTTP_REQUEST_RETRY_ERROR'
|
|
510
|
+
error.cause = errorTrace
|
|
511
|
+
throw error
|
|
512
|
+
}
|
|
458
513
|
}
|
|
459
514
|
|
|
460
515
|
/**
|
|
@@ -465,14 +520,8 @@ export default class Request
|
|
|
465
520
|
* @param {Error} error
|
|
466
521
|
*
|
|
467
522
|
* @returns {Void}
|
|
468
|
-
*
|
|
469
|
-
* @throws {Error} E_HTTP_REQUEST_CLIENT_ERROR
|
|
470
|
-
* @throws {Error} E_HTTP_REQUEST_CLIENT_TIMEOUT
|
|
471
|
-
* @throws {Error} E_HTTP_REQUEST_DOWNSTREAM_ERROR
|
|
472
|
-
* @throws {Error} E_HTTP_REQUEST_INVALID_RESPONSE_BODY_FORMAT
|
|
473
|
-
* @throws {Error} E_HTTP_REQUEST_INVALID_RESPONSE_STATUS
|
|
474
523
|
*/
|
|
475
|
-
#resolveRetryLoopError(options, errorTrace, error)
|
|
524
|
+
async #resolveRetryLoopError(options, errorTrace, error)
|
|
476
525
|
{
|
|
477
526
|
switch(error.code)
|
|
478
527
|
{
|
|
@@ -480,6 +529,22 @@ export default class Request
|
|
|
480
529
|
{
|
|
481
530
|
return errorTrace.push(error)
|
|
482
531
|
}
|
|
532
|
+
case 'E_HTTP_REQUEST_HTTP2_SESSION_DESTROYED':
|
|
533
|
+
case 'E_HTTP_REQUEST_HTTP2_SESSION_CLOSED':
|
|
534
|
+
{
|
|
535
|
+
try
|
|
536
|
+
{
|
|
537
|
+
await this.reconnect()
|
|
538
|
+
}
|
|
539
|
+
catch(reason)
|
|
540
|
+
{
|
|
541
|
+
const reconnectError = new Error(`${reason.message}, retry to reconnect to the server failed`)
|
|
542
|
+
reconnectError.code = 'E_HTTP_REQUEST_RETRY_HTTP2_RECONNECT'
|
|
543
|
+
reconnectError.cause = reason
|
|
544
|
+
throw reason
|
|
545
|
+
}
|
|
546
|
+
return errorTrace.push(error)
|
|
547
|
+
}
|
|
483
548
|
case 'E_HTTP_REQUEST_CLIENT_TIMEOUT':
|
|
484
549
|
{
|
|
485
550
|
if(options.retryOnClientTimeout)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superhero/http-request",
|
|
3
|
-
"version": "4.0.
|
|
4
|
-
"description": "HTTP request component supporting HTTP 1.1 and HTTP 2.0",
|
|
3
|
+
"version": "4.0.9",
|
|
4
|
+
"description": "HTTP(S) request component supporting HTTP 1.1 and HTTP 2.0",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"http request",
|
|
7
7
|
"http client",
|