@superhero/http-request 4.0.8 → 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 +96 -39
  2. package/package.json +2 -2
package/index.js CHANGED
@@ -83,16 +83,19 @@ 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
+ options = Object.assign(this.config, options)
87
87
 
88
+ this.config.base = authority
89
+ this.config.url = url.pathname + url.search
88
90
  this.http2Session = http2.connect(authority, options, () =>
89
91
  {
90
- this.config.base = authority
91
- this.config.url = url.pathname + url.search
92
92
  this.http2Session.removeAllListeners('error')
93
+ this.http2Session.on('error', console.error)
94
+
93
95
  accept()
94
96
  })
95
97
 
98
+ // If there is a error on connection, reject the promise.
96
99
  this.http2Session.once('error', (reason) =>
97
100
  {
98
101
  const error = new Error(`Failed to connect to server over HTTP2 using authority: ${authority}`)
@@ -100,12 +103,6 @@ export default class Request
100
103
  error.cause = reason
101
104
  reject(error)
102
105
  })
103
-
104
- this.http2Session.once('close', () =>
105
- {
106
- this.http2Session.removeAllListeners()
107
- delete this.http2Session
108
- })
109
106
  })
110
107
  }
111
108
 
@@ -118,23 +115,43 @@ export default class Request
118
115
  {
119
116
  return new Promise((accept, reject) =>
120
117
  {
121
- if(this.http2Session
122
- && this.http2Session.closed === false)
118
+ const http2Session = this.http2Session
119
+
120
+ if(http2Session)
123
121
  {
124
- this.http2Session.close((error) =>
122
+ http2Session.removeAllListeners()
123
+
124
+ if(false === http2Session.closed)
125
125
  {
126
- error
127
- ? reject(error)
128
- : accept()
129
- })
130
- }
131
- else
132
- {
133
- 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
134
139
  }
140
+
141
+ // fallback to accept if nothing to close
142
+ accept()
135
143
  })
136
144
  }
137
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
+
138
155
  /**
139
156
  * GET – Read a resource or a collection of resources.
140
157
  * Used to retrieve data without modifying it.
@@ -245,7 +262,18 @@ export default class Request
245
262
  *
246
263
  * @param {string} method
247
264
  * @param {RequestOptions} options
265
+ *
248
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
249
277
  */
250
278
  #fetch(method, options)
251
279
  {
@@ -284,11 +312,6 @@ export default class Request
284
312
  * @param {RequestOptions} options
285
313
  *
286
314
  * @returns {RequestResponse}
287
- *
288
- * @throws {Error} E_HTTP_REQUEST_CLIENT_TIMEOUT
289
- * @throws {Error} E_HTTP_REQUEST_CLIENT_ERROR
290
- * @throws {Error} E_HTTP_REQUEST_DOWNSTREAM_ERROR
291
- * @throws {Error} E_HTTP_REQUEST_INVALID_RESPONSE_BODY_FORMAT
292
315
  */
293
316
  #resolve(options)
294
317
  {
@@ -315,6 +338,22 @@ export default class Request
315
338
 
316
339
  #resolveHttp2Client(options, method, headers, url, accept, reject)
317
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
+
318
357
  delete headers['transfer-encoding']
319
358
 
320
359
  const { pathname, search } = new URL(url)
@@ -369,7 +408,7 @@ export default class Request
369
408
  #connectionClosed(upstream, reject)
370
409
  {
371
410
  upstream.removeAllListeners()
372
- const error = new Error('The connection was closed unexpectedly')
411
+ const error = new Error('Connection was closed unexpectedly')
373
412
  error.code = 'E_HTTP_REQUEST_CLIENT_ERROR'
374
413
  setImmediate(() => reject(error)) // this error is a fallback if the promise is not already resolved
375
414
  }
@@ -419,18 +458,14 @@ export default class Request
419
458
 
420
459
  /**
421
460
  * Resolves the request in a retry loop.
422
- *
423
461
  * @param {RequestOptions} options
424
- *
425
462
  * @returns {RequestResponse}
426
- *
427
- * @throws {Error} E_HTTP_REQUEST_RETRY
428
463
  */
429
464
  async #resolveRetryLoop(options)
430
465
  {
431
466
  const errorTrace = []
432
467
 
433
- let retry = Math.abs(Math.floor(options.retry))
468
+ let retry = Math.abs(Math.floor(options.retry)) + 1
434
469
 
435
470
  while(retry--)
436
471
  {
@@ -458,11 +493,23 @@ export default class Request
458
493
  }
459
494
  else
460
495
  {
461
- this.#resolveRetryLoopError(options, errorTrace, error)
496
+ await this.#resolveRetryLoopError(options, errorTrace, error)
462
497
  await wait(options.retryDelay)
463
498
  }
464
499
  }
465
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
+ }
466
513
  }
467
514
 
468
515
  /**
@@ -473,14 +520,8 @@ export default class Request
473
520
  * @param {Error} error
474
521
  *
475
522
  * @returns {Void}
476
- *
477
- * @throws {Error} E_HTTP_REQUEST_CLIENT_ERROR
478
- * @throws {Error} E_HTTP_REQUEST_CLIENT_TIMEOUT
479
- * @throws {Error} E_HTTP_REQUEST_DOWNSTREAM_ERROR
480
- * @throws {Error} E_HTTP_REQUEST_INVALID_RESPONSE_BODY_FORMAT
481
- * @throws {Error} E_HTTP_REQUEST_INVALID_RESPONSE_STATUS
482
523
  */
483
- #resolveRetryLoopError(options, errorTrace, error)
524
+ async #resolveRetryLoopError(options, errorTrace, error)
484
525
  {
485
526
  switch(error.code)
486
527
  {
@@ -488,6 +529,22 @@ export default class Request
488
529
  {
489
530
  return errorTrace.push(error)
490
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
+ }
491
548
  case 'E_HTTP_REQUEST_CLIENT_TIMEOUT':
492
549
  {
493
550
  if(options.retryOnClientTimeout)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@superhero/http-request",
3
- "version": "4.0.8",
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",