@superhero/http-request 4.1.0 → 4.1.1
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 +60 -62
- package/index.test.js +45 -1
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -68,8 +68,8 @@ export default class Request
|
|
|
68
68
|
/**
|
|
69
69
|
* Connects to a HTTP/2 server.
|
|
70
70
|
*
|
|
71
|
-
* @param {String} authority the URL
|
|
72
|
-
* @param {Object} [options]
|
|
71
|
+
* @param {String|Object} [authority] the URL to connect to the server, or the options object
|
|
72
|
+
* @param {Object} [options] @see node:http2.connect options
|
|
73
73
|
*
|
|
74
74
|
* @throws {Error} E_HTTP_REQUEST_CONNECT_INVALID_ARGUMENT
|
|
75
75
|
*
|
|
@@ -77,31 +77,28 @@ export default class Request
|
|
|
77
77
|
*/
|
|
78
78
|
connect(authority, options)
|
|
79
79
|
{
|
|
80
|
-
return new Promise(async
|
|
80
|
+
return new Promise(async resolve =>
|
|
81
81
|
{
|
|
82
82
|
await this.close()
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
if('object' === typeof authority
|
|
85
|
+
&& null !== authority
|
|
86
|
+
&& false === !!options)
|
|
87
|
+
{
|
|
88
|
+
options = authority
|
|
89
|
+
authority = null
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
options = Object.assign(this.config, options)
|
|
93
|
+
const url = new URL(authority || options.authority || options.base || options.url)
|
|
87
94
|
|
|
88
|
-
this.config.
|
|
89
|
-
this.
|
|
90
|
-
this.http2Session = http2.connect(authority, options, () =>
|
|
95
|
+
this.config.authority = url.origin
|
|
96
|
+
this.http2Session = http2.connect(url.origin, options, () =>
|
|
91
97
|
{
|
|
92
98
|
this.http2Session.removeAllListeners('error')
|
|
93
99
|
this.http2Session.on('error', console.error)
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
// If there is a error on connection, reject the promise.
|
|
99
|
-
this.http2Session.once('error', (reason) =>
|
|
100
|
-
{
|
|
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)
|
|
101
|
+
resolve()
|
|
105
102
|
})
|
|
106
103
|
})
|
|
107
104
|
}
|
|
@@ -113,7 +110,7 @@ export default class Request
|
|
|
113
110
|
*/
|
|
114
111
|
close()
|
|
115
112
|
{
|
|
116
|
-
return new Promise((
|
|
113
|
+
return new Promise((resolve, reject) =>
|
|
117
114
|
{
|
|
118
115
|
const http2Session = this.http2Session
|
|
119
116
|
|
|
@@ -129,7 +126,7 @@ export default class Request
|
|
|
129
126
|
|
|
130
127
|
error
|
|
131
128
|
? reject(error)
|
|
132
|
-
:
|
|
129
|
+
: resolve()
|
|
133
130
|
})
|
|
134
131
|
|
|
135
132
|
return // await the close event
|
|
@@ -138,8 +135,8 @@ export default class Request
|
|
|
138
135
|
delete this.http2Session
|
|
139
136
|
}
|
|
140
137
|
|
|
141
|
-
// fallback to
|
|
142
|
-
|
|
138
|
+
// fallback to resolve if nothing to close
|
|
139
|
+
resolve()
|
|
143
140
|
})
|
|
144
141
|
}
|
|
145
142
|
|
|
@@ -282,13 +279,6 @@ export default class Request
|
|
|
282
279
|
options = { url:options }
|
|
283
280
|
}
|
|
284
281
|
|
|
285
|
-
if(this.config.url && options.url)
|
|
286
|
-
{
|
|
287
|
-
options.url = options.url[0] === '/'
|
|
288
|
-
? options.url
|
|
289
|
-
: this.config.url + options.url
|
|
290
|
-
}
|
|
291
|
-
|
|
292
282
|
options = Object.assign(
|
|
293
283
|
{
|
|
294
284
|
method,
|
|
@@ -325,20 +315,20 @@ export default class Request
|
|
|
325
315
|
*/
|
|
326
316
|
#resolve(options)
|
|
327
317
|
{
|
|
328
|
-
return new Promise((
|
|
318
|
+
return new Promise((resolve, reject) =>
|
|
329
319
|
{
|
|
330
320
|
const
|
|
331
321
|
method = options.method,
|
|
332
322
|
headers = this.#normalizeHeaders(options.headers),
|
|
333
323
|
body = this.#normalizeBody(options.body ?? options.data, headers['content-type']),
|
|
334
324
|
delimiter = this.#createBodyHeaderDelimiter(body, !!options.upstream || !!this.http2Session),
|
|
335
|
-
url = this.#normalizeUrl(options.
|
|
325
|
+
url = this.#normalizeUrl(options.authority, options.base, options.url)
|
|
336
326
|
|
|
337
327
|
Object.assign(headers, delimiter)
|
|
338
328
|
|
|
339
329
|
const upstream = this.http2Session
|
|
340
|
-
? this.#resolveHttp2Client(options, method, headers, url,
|
|
341
|
-
: this.#resolveHttp1Client(options, method, headers, url,
|
|
330
|
+
? this.#resolveHttp2Client(options, method, headers, url, resolve, reject)
|
|
331
|
+
: this.#resolveHttp1Client(options, method, headers, url, resolve, reject)
|
|
342
332
|
|
|
343
333
|
options.upstream
|
|
344
334
|
? options.upstream.pipe(upstream)
|
|
@@ -346,7 +336,7 @@ export default class Request
|
|
|
346
336
|
})
|
|
347
337
|
}
|
|
348
338
|
|
|
349
|
-
#resolveHttp2Client(options, method, headers, url,
|
|
339
|
+
#resolveHttp2Client(options, method, headers, url, resolve, reject)
|
|
350
340
|
{
|
|
351
341
|
if(true === this.http2Session.destroyed)
|
|
352
342
|
{
|
|
@@ -394,13 +384,13 @@ export default class Request
|
|
|
394
384
|
delete upstream.headers[HEADER_STATUS]
|
|
395
385
|
Object.defineProperty(upstream.headers, SENSITIVE_HEADERS, { enumerable:false, value:headers[SENSITIVE_HEADERS] })
|
|
396
386
|
|
|
397
|
-
this.#resolveOnResponse(options, method, url,
|
|
387
|
+
this.#resolveOnResponse(options, method, url, resolve, reject, upstream)
|
|
398
388
|
})
|
|
399
389
|
|
|
400
390
|
return upstream
|
|
401
391
|
}
|
|
402
392
|
|
|
403
|
-
#resolveHttp1Client(options, method, headers, url,
|
|
393
|
+
#resolveHttp1Client(options, method, headers, url, resolve, reject)
|
|
404
394
|
{
|
|
405
395
|
const
|
|
406
396
|
request = url.startsWith('https:') ? https.request : http.request,
|
|
@@ -410,7 +400,7 @@ export default class Request
|
|
|
410
400
|
upstream.on('close', this.#connectionClosed .bind(this, upstream, reject))
|
|
411
401
|
upstream.on('error', this.#resolveOnClientError .bind(this, method, url, reject))
|
|
412
402
|
upstream.on('timeout', this.#resolveOnClientTimeout.bind(this, upstream, options.timeout, method, url, reject))
|
|
413
|
-
upstream.on('response', this.#resolveOnResponse .bind(this, options, method, url,
|
|
403
|
+
upstream.on('response', this.#resolveOnResponse .bind(this, options, method, url, resolve, reject))
|
|
414
404
|
|
|
415
405
|
return upstream
|
|
416
406
|
}
|
|
@@ -429,13 +419,13 @@ export default class Request
|
|
|
429
419
|
* @param {RequestOptions} options
|
|
430
420
|
* @param {String} method
|
|
431
421
|
* @param {String} url
|
|
432
|
-
* @param {Function}
|
|
422
|
+
* @param {Function} resolve Promise resolve
|
|
433
423
|
* @param {Function} reject Promise reject
|
|
434
424
|
* @param {http.IncomingMessage} readable
|
|
435
425
|
*
|
|
436
426
|
* @returns {Void}
|
|
437
427
|
*/
|
|
438
|
-
#resolveOnResponse(options, method, url,
|
|
428
|
+
#resolveOnResponse(options, method, url, resolve, reject, readable)
|
|
439
429
|
{
|
|
440
430
|
const response =
|
|
441
431
|
{
|
|
@@ -458,11 +448,11 @@ export default class Request
|
|
|
458
448
|
{
|
|
459
449
|
readable.pipe(options.downstream)
|
|
460
450
|
readable.resume()
|
|
461
|
-
|
|
451
|
+
resolve(response)
|
|
462
452
|
}
|
|
463
453
|
else
|
|
464
454
|
{
|
|
465
|
-
this.#bufferResponseBody(readable, response, method, url,
|
|
455
|
+
this.#bufferResponseBody(readable, response, method, url, resolve, reject)
|
|
466
456
|
}
|
|
467
457
|
}
|
|
468
458
|
|
|
@@ -509,19 +499,16 @@ export default class Request
|
|
|
509
499
|
}
|
|
510
500
|
}
|
|
511
501
|
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
reason
|
|
502
|
+
const
|
|
503
|
+
unique = reasons.filter((reason, i) => [i, -1].includes(reasons.map(e => e.code).lastIndexOf(code => code === reason.code))),
|
|
504
|
+
reason = unique.pop()
|
|
515
505
|
|
|
516
|
-
if(
|
|
506
|
+
if(unique.length)
|
|
517
507
|
{
|
|
518
|
-
|
|
519
|
-
}
|
|
520
|
-
else
|
|
521
|
-
{
|
|
522
|
-
reason.previous = uniqueReasons
|
|
523
|
-
throw reason
|
|
508
|
+
reason.retried = unique
|
|
524
509
|
}
|
|
510
|
+
|
|
511
|
+
throw reason
|
|
525
512
|
}
|
|
526
513
|
|
|
527
514
|
/**
|
|
@@ -662,17 +649,28 @@ export default class Request
|
|
|
662
649
|
|
|
663
650
|
/**
|
|
664
651
|
* Normalizes the URL.
|
|
652
|
+
* One of authority or base must be provided,
|
|
665
653
|
*
|
|
666
|
-
* @param {String}
|
|
667
|
-
* @param {String}
|
|
654
|
+
* @param {String} [base]
|
|
655
|
+
* @param {String} [authority] the URL origin (e.g. https://example.com)
|
|
656
|
+
* @param {String} [url]
|
|
668
657
|
*
|
|
669
658
|
* @returns {String} Normalized URL
|
|
670
659
|
*/
|
|
671
|
-
#normalizeUrl(
|
|
660
|
+
#normalizeUrl(authority, base, url)
|
|
672
661
|
{
|
|
662
|
+
let baseURL = authority && base
|
|
663
|
+
? new URL(base, authority)
|
|
664
|
+
: new URL(base || authority)
|
|
665
|
+
|
|
666
|
+
if(url && false === baseURL.pathname.endsWith('/'))
|
|
667
|
+
{
|
|
668
|
+
baseURL = new URL(baseURL.pathname + '/', baseURL.href)
|
|
669
|
+
}
|
|
670
|
+
|
|
673
671
|
return url
|
|
674
|
-
? new URL(url,
|
|
675
|
-
:
|
|
672
|
+
? new URL(url, baseURL.href).href
|
|
673
|
+
: baseURL.href
|
|
676
674
|
}
|
|
677
675
|
|
|
678
676
|
/**
|
|
@@ -723,12 +721,12 @@ export default class Request
|
|
|
723
721
|
upstream.destroy(error)
|
|
724
722
|
}
|
|
725
723
|
|
|
726
|
-
#bufferResponseBody(readable, response, method, url,
|
|
724
|
+
#bufferResponseBody(readable, response, method, url, resolve, reject)
|
|
727
725
|
{
|
|
728
726
|
response.body = ''
|
|
729
727
|
readable.on('data', (chunk) => response.body += chunk)
|
|
730
728
|
readable.on('error', this.#onStreamError.bind(this, method, url, response))
|
|
731
|
-
readable.on('end', this.#onStreamEnd .bind(this, method, url, response,
|
|
729
|
+
readable.on('end', this.#onStreamEnd .bind(this, method, url, response, resolve, reject))
|
|
732
730
|
readable.resume()
|
|
733
731
|
}
|
|
734
732
|
|
|
@@ -759,14 +757,14 @@ export default class Request
|
|
|
759
757
|
* @param {String} method
|
|
760
758
|
* @param {String} url
|
|
761
759
|
* @param {http.IncomingMessage} response
|
|
762
|
-
* @param {Function}
|
|
760
|
+
* @param {Function} resolve promise resolve
|
|
763
761
|
* @param {Function} reject promise reject
|
|
764
762
|
*
|
|
765
763
|
* @returns {void}
|
|
766
764
|
*
|
|
767
765
|
* @throws {Error} E_HTTP_REQUEST_INVALID_RESPONSE_BODY_FORMAT
|
|
768
766
|
*/
|
|
769
|
-
#onStreamEnd(method, url, response,
|
|
767
|
+
#onStreamEnd(method, url, response, resolve, reject)
|
|
770
768
|
{
|
|
771
769
|
try
|
|
772
770
|
{
|
|
@@ -791,7 +789,7 @@ export default class Request
|
|
|
791
789
|
return
|
|
792
790
|
}
|
|
793
791
|
|
|
794
|
-
|
|
792
|
+
resolve(response)
|
|
795
793
|
}
|
|
796
794
|
|
|
797
795
|
#contentTypeApplicationJson(body)
|
package/index.test.js
CHANGED
|
@@ -53,6 +53,50 @@ suite('@superhero/http-request', () =>
|
|
|
53
53
|
assert.equal(response.body.url, '/', 'Should result to /')
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
+
test('Request using a relative path', async sub =>
|
|
57
|
+
{
|
|
58
|
+
// ...close the server that was started before the
|
|
59
|
+
// sub tests starts another server for each test...
|
|
60
|
+
await request.close()
|
|
61
|
+
await new Promise(resolve => server.close(resolve))
|
|
62
|
+
|
|
63
|
+
await sub.test('Request using an absolute path with a trailing base path slash', async () =>
|
|
64
|
+
{
|
|
65
|
+
const base = request.config.base || ''
|
|
66
|
+
request.config.base = base + '/path/'
|
|
67
|
+
const response = await request.get('/foobar')
|
|
68
|
+
assert.equal(response.status, 200, 'Should return a 200 status code')
|
|
69
|
+
assert.equal(response.body.url, '/foobar', 'Should result to the absolute path')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
await sub.test('Request using a absolute path with out a trailing base path slash', async () =>
|
|
73
|
+
{
|
|
74
|
+
const base = request.config.base || ''
|
|
75
|
+
request.config.base = base + '/path'
|
|
76
|
+
const response = await request.get('/foobar')
|
|
77
|
+
assert.equal(response.status, 200, 'Should return a 200 status code')
|
|
78
|
+
assert.equal(response.body.url, '/foobar', 'Should result to the absolute path')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
await sub.test('Request using a relative path with a trailing base path slash', async () =>
|
|
82
|
+
{
|
|
83
|
+
const base = request.config.base || ''
|
|
84
|
+
request.config.base = base + '/path/'
|
|
85
|
+
const response = await request.get('foobar')
|
|
86
|
+
assert.equal(response.status, 200, 'Should return a 200 status code')
|
|
87
|
+
assert.equal(response.body.url, '/path/foobar', 'Should result to the absolute path')
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
await sub.test('Request using a relative path with out a trailing base path slash', async () =>
|
|
91
|
+
{
|
|
92
|
+
const base = request.config.base || ''
|
|
93
|
+
request.config.base = base + '/path'
|
|
94
|
+
const response = await request.get('foobar')
|
|
95
|
+
assert.equal(response.status, 200, 'Should return a 200 status code')
|
|
96
|
+
assert.equal(response.body.url, '/path/foobar', 'Should result to the absolute path')
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
56
100
|
test('Request using a string body', async () =>
|
|
57
101
|
{
|
|
58
102
|
const response = await request.post({ body: 'test body' })
|
|
@@ -503,7 +547,7 @@ suite('@superhero/http-request', () =>
|
|
|
503
547
|
})
|
|
504
548
|
})
|
|
505
549
|
|
|
506
|
-
afterEach(
|
|
550
|
+
afterEach(done => request.close().then(() => server.close(done)))
|
|
507
551
|
|
|
508
552
|
executeTheSameTestSuitFor_http1_http2()
|
|
509
553
|
|