@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.
Files changed (2) hide show
  1. package/index.js +104 -39
  2. 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, ...args)
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.http2Session = http2.connect(authority, ...args.slice(0, 1), () =>
88
+ this.config.base = authority
89
+ this.config.url = url.pathname + url.search
90
+ this.http2Session = http2.connect(authority, options, () =>
88
91
  {
89
- this.config.base = authority
90
- this.config.url = url.pathname + url.search
91
- this.http2Session.off('error', reject)
92
+ this.http2Session.removeAllListeners('error')
93
+ this.http2Session.on('error', console.error)
94
+
92
95
  accept()
93
96
  })
94
97
 
95
- this.http2Session.once('error', reject)
96
- this.http2Session.once('close', () =>
98
+ // If there is a error on connection, reject the promise.
99
+ this.http2Session.once('error', (reason) =>
97
100
  {
98
- this.http2Session.removeAllListeners()
99
- delete this.http2Session
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
- if(this.http2Session
114
- && this.http2Session.closed === false)
118
+ const http2Session = this.http2Session
119
+
120
+ if(http2Session)
115
121
  {
116
- this.http2Session.close((error) =>
122
+ http2Session.removeAllListeners()
123
+
124
+ if(false === http2Session.closed)
117
125
  {
118
- error
119
- ? reject(error)
120
- : accept()
121
- })
122
- }
123
- else
124
- {
125
- accept()
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('The connection was closed unexpectedly')
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.7",
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",