@superhero/http-request 4.0.0
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/README.md +351 -0
- package/index.js +704 -0
- package/index.test.js +555 -0
- package/package.json +27 -0
package/index.test.js
ADDED
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import http from 'node:http'
|
|
3
|
+
import http2 from 'node:http2'
|
|
4
|
+
import Request from '@superhero/http-request'
|
|
5
|
+
import { once } from 'events'
|
|
6
|
+
import { Readable, Writable } from 'node:stream'
|
|
7
|
+
import { suite, test, beforeEach, afterEach } from 'node:test'
|
|
8
|
+
|
|
9
|
+
suite('@superhero/http-request', () =>
|
|
10
|
+
{
|
|
11
|
+
let server, request, baseUrl
|
|
12
|
+
|
|
13
|
+
function executeTheSameTestSuitFor_http1_http2()
|
|
14
|
+
{
|
|
15
|
+
suite('Request using method', () =>
|
|
16
|
+
{
|
|
17
|
+
const methods =
|
|
18
|
+
[
|
|
19
|
+
{ method: 'POST', body: { key: 'foo' } },
|
|
20
|
+
{ method: 'PUT', body: { key: 'bar' } },
|
|
21
|
+
{ method: 'PATCH', body: { key: 'baz' } },
|
|
22
|
+
{ method: 'GET' },
|
|
23
|
+
{ method: 'DELETE' },
|
|
24
|
+
{ method: 'HEAD' },
|
|
25
|
+
{ method: 'OPTIONS' },
|
|
26
|
+
{ method: 'TRACE' }
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
for (const { method, body } of methods)
|
|
30
|
+
{
|
|
31
|
+
test(method, async () =>
|
|
32
|
+
{
|
|
33
|
+
const
|
|
34
|
+
options = body ? { body, headers: { 'Content-Type': 'application/json' } } : {},
|
|
35
|
+
response = await request[method.toLowerCase()]({ url: `/test-${method}`, ...options })
|
|
36
|
+
|
|
37
|
+
assert.equal(response.status, 200, `Should return a 200 status code for ${method}`)
|
|
38
|
+
|
|
39
|
+
if(method === 'HEAD') return // HEAD requests does not have a body to validate
|
|
40
|
+
|
|
41
|
+
assert.equal(response.body.method, method, `Should report method as ${method}`)
|
|
42
|
+
assert.equal(response.body.url, `/test-${method}`, `Should request /test-${method}`)
|
|
43
|
+
|
|
44
|
+
body && assert.deepEqual(response.body.body, body, `Should send correct body for ${method}`)
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('Request using the URL as the option parameter', async () =>
|
|
50
|
+
{
|
|
51
|
+
const response = await request.get('/')
|
|
52
|
+
assert.equal(response.status, 200, 'Should return a 200 status code')
|
|
53
|
+
assert.equal(response.body.url, '/', 'Should result to /')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('Request using a string body', async () =>
|
|
57
|
+
{
|
|
58
|
+
const response = await request.post({ body: 'test body' })
|
|
59
|
+
assert.equal(response.status, 200, 'Should return a 200 status code')
|
|
60
|
+
assert.equal(response.body.body, 'test body', 'Should result to the value of the request body')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('Request using a custom header', async () =>
|
|
64
|
+
{
|
|
65
|
+
const response = await request.get(
|
|
66
|
+
{
|
|
67
|
+
url : '/header-test',
|
|
68
|
+
headers : { 'custom-header': 'test-value' },
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
assert.equal(response.status, 200, 'Should return a 200 status code')
|
|
72
|
+
assert.equal(response.body.headers['custom-header'], 'test-value', 'Should return the custom header')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('Normalizes headers and body', async () =>
|
|
76
|
+
{
|
|
77
|
+
const response = await request.post(
|
|
78
|
+
{
|
|
79
|
+
url : '/normalize-test',
|
|
80
|
+
headers : { 'Custom-Header': 'foo' },
|
|
81
|
+
body : { bar: 'baz' }
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
assert.equal(response.status, 200, 'Should return a 200 status code')
|
|
85
|
+
assert.equal(response.body.headers['custom-header'], 'foo', 'Should normalize headers')
|
|
86
|
+
assert.deepEqual(response.body.body, 'bar=baz', 'Should normalize body')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('Can pipe upstream body through the request from a readable stream', async () =>
|
|
90
|
+
{
|
|
91
|
+
const
|
|
92
|
+
upstream = Readable.from(['test upstream body']),
|
|
93
|
+
response = await request.post(
|
|
94
|
+
{
|
|
95
|
+
url: '/upstream-test',
|
|
96
|
+
upstream
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
assert.equal(response.status, 200)
|
|
100
|
+
assert.equal(response.body.body, 'test upstream body')
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test('Can pipe downstream response from the request to a writable stream', async () =>
|
|
104
|
+
{
|
|
105
|
+
let body = ''
|
|
106
|
+
|
|
107
|
+
const
|
|
108
|
+
downstream = new Writable(
|
|
109
|
+
{
|
|
110
|
+
write(chunk, encoding, callback)
|
|
111
|
+
{
|
|
112
|
+
body += chunk.toString()
|
|
113
|
+
callback()
|
|
114
|
+
}
|
|
115
|
+
}),
|
|
116
|
+
finished = once(downstream, 'finish'),
|
|
117
|
+
response = await request.get(
|
|
118
|
+
{
|
|
119
|
+
url: '/downstream-test',
|
|
120
|
+
downstream
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
await finished
|
|
124
|
+
|
|
125
|
+
assert.equal(response.status, 200, 'Should return a 200 status code')
|
|
126
|
+
assert.doesNotThrow(() => body = JSON.parse(body), 'Should be able to parse body as JSON')
|
|
127
|
+
assert.equal(body.url, '/downstream-test', 'Should be able to pipe downstream body')
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
suite('HTTP 1.1', () =>
|
|
132
|
+
{
|
|
133
|
+
beforeEach(async () =>
|
|
134
|
+
{
|
|
135
|
+
server = http.createServer((req, res) =>
|
|
136
|
+
{
|
|
137
|
+
const dto =
|
|
138
|
+
{
|
|
139
|
+
method : req.method,
|
|
140
|
+
url : req.url,
|
|
141
|
+
headers : req.headers,
|
|
142
|
+
body : ''
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
req.on('error', console.error)
|
|
146
|
+
res.on('error', console.error)
|
|
147
|
+
|
|
148
|
+
req.on('data', (chunk) => dto.body += chunk)
|
|
149
|
+
req.on('end', () =>
|
|
150
|
+
{
|
|
151
|
+
if(req.headers['content-type'] === 'application/json')
|
|
152
|
+
{
|
|
153
|
+
dto.body = JSON.parse(dto.body ?? '{}')
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
157
|
+
res.end(JSON.stringify(dto))
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
await new Promise((accept, reject) =>
|
|
162
|
+
{
|
|
163
|
+
server.on('error', reject)
|
|
164
|
+
server.listen(() =>
|
|
165
|
+
{
|
|
166
|
+
const { port } = server.address()
|
|
167
|
+
baseUrl = `http://localhost:${port}`
|
|
168
|
+
|
|
169
|
+
server.off('error', reject)
|
|
170
|
+
request = new Request({ base:baseUrl })
|
|
171
|
+
accept()
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
afterEach((done) => server.close(done))
|
|
177
|
+
|
|
178
|
+
executeTheSameTestSuitFor_http1_http2()
|
|
179
|
+
|
|
180
|
+
suite('Tests that require an altered server response', () =>
|
|
181
|
+
{
|
|
182
|
+
test('Supports request timeout', async () =>
|
|
183
|
+
{
|
|
184
|
+
// Alter the server to delay the response by 0.5 seconds
|
|
185
|
+
server.removeAllListeners('request')
|
|
186
|
+
server.on('request', (req, res) =>
|
|
187
|
+
{
|
|
188
|
+
setTimeout(() =>
|
|
189
|
+
{
|
|
190
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
191
|
+
res.end(JSON.stringify({ success: true }))
|
|
192
|
+
}, 500)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
const options =
|
|
196
|
+
{
|
|
197
|
+
url : '/timeout-test',
|
|
198
|
+
timeout : 100
|
|
199
|
+
}
|
|
200
|
+
await assert.rejects(
|
|
201
|
+
request.get(options),
|
|
202
|
+
(error) => error.code === 'E_HTTP_REQUEST_CLIENT_TIMEOUT',
|
|
203
|
+
'Should throw a timeout error')
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('Rejects invalid JSON response accurately', async () =>
|
|
207
|
+
{
|
|
208
|
+
// Alter the server to respond with invalid JSON
|
|
209
|
+
server.removeAllListeners('request')
|
|
210
|
+
server.on('request', (req, res) =>
|
|
211
|
+
{
|
|
212
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
213
|
+
res.end('Invalid JSON')
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
// Make the request
|
|
217
|
+
await assert.rejects(
|
|
218
|
+
request.get({ url: '/invalid-json' }),
|
|
219
|
+
(error) => error.code === 'E_HTTP_REQUEST_INVALID_RESPONSE_BODY_FORMAT',
|
|
220
|
+
'Should throw a parse error')
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
test('Retry on client error', async () =>
|
|
224
|
+
{
|
|
225
|
+
let attempt = 0
|
|
226
|
+
|
|
227
|
+
// Alter the server to not accept the first two requests
|
|
228
|
+
server.removeAllListeners('request')
|
|
229
|
+
server.on('request', (req, res) =>
|
|
230
|
+
{
|
|
231
|
+
if(++attempt < 3)
|
|
232
|
+
{
|
|
233
|
+
res.destroy()
|
|
234
|
+
}
|
|
235
|
+
else
|
|
236
|
+
{
|
|
237
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
238
|
+
res.end(JSON.stringify({ success: true }))
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
const response = await request.get(
|
|
243
|
+
{
|
|
244
|
+
url : `/retry-test`,
|
|
245
|
+
retry : 3,
|
|
246
|
+
retryDelay : 100
|
|
247
|
+
})
|
|
248
|
+
assert.equal(response.status, 200)
|
|
249
|
+
assert.deepEqual(response.body, { success: true })
|
|
250
|
+
assert.equal(attempt, 3)
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
test('Retry on client timeout', async () =>
|
|
254
|
+
{
|
|
255
|
+
let attempt = 0
|
|
256
|
+
|
|
257
|
+
// Alter the server to delay the response by 0.5 seconds the first attempt
|
|
258
|
+
server.removeAllListeners('request')
|
|
259
|
+
server.on('request', (req, res) =>
|
|
260
|
+
{
|
|
261
|
+
attempt++;
|
|
262
|
+
|
|
263
|
+
if(attempt < 2)
|
|
264
|
+
{
|
|
265
|
+
setTimeout(() =>
|
|
266
|
+
{
|
|
267
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
268
|
+
res.end(JSON.stringify({ success: true }))
|
|
269
|
+
}, 500)
|
|
270
|
+
}
|
|
271
|
+
else
|
|
272
|
+
{
|
|
273
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
274
|
+
res.end(JSON.stringify({ success: true }))
|
|
275
|
+
}
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
const response = await request.get(
|
|
279
|
+
{
|
|
280
|
+
url : '/timeout-test',
|
|
281
|
+
timeout : 100,
|
|
282
|
+
retry : 2,
|
|
283
|
+
retryOnClientTimeout: true
|
|
284
|
+
})
|
|
285
|
+
assert.equal(response.status, 200, 'Should eventually return 200 status')
|
|
286
|
+
assert.deepEqual(response.body, { success: true }, 'Should return the correct success body')
|
|
287
|
+
assert.equal(attempt, 2, 'Should make exactly 3 attempts')
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
test('Retry on invalid response body format', async () =>
|
|
291
|
+
{
|
|
292
|
+
let attempt = 0
|
|
293
|
+
|
|
294
|
+
// Alter the server to initially return invalid JSON body
|
|
295
|
+
server.removeAllListeners('request')
|
|
296
|
+
server.on('request', (req, res) =>
|
|
297
|
+
{
|
|
298
|
+
attempt++;
|
|
299
|
+
|
|
300
|
+
if (attempt < 3)
|
|
301
|
+
{
|
|
302
|
+
// Return invalid JSON
|
|
303
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
304
|
+
res.end('Invalid JSON')
|
|
305
|
+
}
|
|
306
|
+
else
|
|
307
|
+
{
|
|
308
|
+
// Return valid JSON after retries
|
|
309
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
310
|
+
res.end(JSON.stringify({ success: true }))
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
const response = await request.get(
|
|
315
|
+
{
|
|
316
|
+
url : '/retry-invalid-json',
|
|
317
|
+
retry : 3,
|
|
318
|
+
retryDelay : 100,
|
|
319
|
+
retryOnInvalidResponseBodyFormat: true
|
|
320
|
+
})
|
|
321
|
+
assert.equal(response.status, 200, 'Should eventually return 200 status')
|
|
322
|
+
assert.deepEqual(response.body, { success: true }, 'Should return the correct success body')
|
|
323
|
+
assert.equal(attempt, 3, 'Should make exactly 3 attempts')
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
test('Retry on response status', async (sub) =>
|
|
327
|
+
{
|
|
328
|
+
// Close the server that was started before the
|
|
329
|
+
// sub tests starts another server for each test.
|
|
330
|
+
await new Promise((accept) => server.close(accept))
|
|
331
|
+
|
|
332
|
+
let attempt
|
|
333
|
+
|
|
334
|
+
sub.beforeEach(() =>
|
|
335
|
+
{
|
|
336
|
+
// Reset the attempt counter
|
|
337
|
+
attempt = 0
|
|
338
|
+
|
|
339
|
+
// Alter the server to return a retryable statuses before success
|
|
340
|
+
server.removeAllListeners('request')
|
|
341
|
+
server.on('request', (req, res) =>
|
|
342
|
+
{
|
|
343
|
+
attempt++;
|
|
344
|
+
|
|
345
|
+
if(attempt < 3)
|
|
346
|
+
{
|
|
347
|
+
// Respond with the retryable status
|
|
348
|
+
res.writeHead(503, { 'Content-Type': 'application/json' })
|
|
349
|
+
res.end(JSON.stringify({ message: 'Service Unavailable' }))
|
|
350
|
+
}
|
|
351
|
+
else
|
|
352
|
+
{
|
|
353
|
+
// Respond with a success status after the third attempt
|
|
354
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
355
|
+
res.end(JSON.stringify({ success: true }))
|
|
356
|
+
}
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
await sub.test('Retry on status: 503 - should succeed after re-attempts', async () =>
|
|
361
|
+
{
|
|
362
|
+
const response = await request.get(
|
|
363
|
+
{
|
|
364
|
+
url : '/retry-on-status',
|
|
365
|
+
retry : 3,
|
|
366
|
+
retryDelay : 100,
|
|
367
|
+
retryOnStatus : [ 503 ]
|
|
368
|
+
})
|
|
369
|
+
assert.equal(response.status, 200, 'Should eventually return 200 status')
|
|
370
|
+
assert.deepEqual(response.body, { success: true }, 'Should return the correct success body')
|
|
371
|
+
assert.equal(attempt, 3, 'Should make exactly 3 attempts')
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
await sub.test('Retry on status: 503 - should reject after to few re-attempts', async () =>
|
|
375
|
+
{
|
|
376
|
+
const options =
|
|
377
|
+
{
|
|
378
|
+
url : '/retry-on-status',
|
|
379
|
+
retry : 2,
|
|
380
|
+
retryDelay : 100,
|
|
381
|
+
retryOnStatus : [ 503 ]
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
await assert.rejects(
|
|
385
|
+
request.get(options),
|
|
386
|
+
(error) => error.code === 'E_HTTP_REQUEST_INVALID_RESPONSE_STATUS',
|
|
387
|
+
'Should throw an error after the second attempt')
|
|
388
|
+
|
|
389
|
+
assert.equal(attempt, 2, 'Should make exactly 2 attempts')
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
await sub.test('Retry on status: 500 - should reject on first attempt', async () =>
|
|
393
|
+
{
|
|
394
|
+
const options =
|
|
395
|
+
{
|
|
396
|
+
url : '/retry-on-status',
|
|
397
|
+
retry : 3,
|
|
398
|
+
retryDelay : 100,
|
|
399
|
+
retryOnStatus : [ 500 ]
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
await assert.rejects(
|
|
403
|
+
request.get(options),
|
|
404
|
+
(error) => error.code === 'E_HTTP_REQUEST_INVALID_RESPONSE_STATUS',
|
|
405
|
+
'Should throw an error right away')
|
|
406
|
+
|
|
407
|
+
assert.equal(attempt, 1, 'Should make exactly 1 attempt')
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
await sub.test('Retry on error response status', async (sub) =>
|
|
411
|
+
{
|
|
412
|
+
// Close the server that was started before the
|
|
413
|
+
// sub tests starts another server for each test.
|
|
414
|
+
await new Promise((accept) => server.close(accept))
|
|
415
|
+
|
|
416
|
+
await sub.test('Should succeed after re-attempts', async () =>
|
|
417
|
+
{
|
|
418
|
+
const response = await request.get(
|
|
419
|
+
{
|
|
420
|
+
url : '/retry-on-status',
|
|
421
|
+
retry : 3,
|
|
422
|
+
retryDelay : 100,
|
|
423
|
+
retryOnErrorResponseStatus : true
|
|
424
|
+
})
|
|
425
|
+
assert.equal(response.status, 200, 'Should eventually return 200 status')
|
|
426
|
+
assert.deepEqual(response.body, { success: true }, 'Should return the correct success body')
|
|
427
|
+
assert.equal(attempt, 3, 'Should make exactly 3 attempts')
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
await sub.test('Should succeed after re-attempts when "doNotThrowOnErrorStatus"', async () =>
|
|
431
|
+
{
|
|
432
|
+
const response = await request.get(
|
|
433
|
+
{
|
|
434
|
+
url : '/retry-on-status',
|
|
435
|
+
retry : 3,
|
|
436
|
+
retryDelay : 100,
|
|
437
|
+
retryOnErrorResponseStatus : true,
|
|
438
|
+
doNotThrowOnErrorStatus : true
|
|
439
|
+
})
|
|
440
|
+
assert.equal(response.status, 200, 'Should eventually return 200 status')
|
|
441
|
+
assert.deepEqual(response.body, { success: true }, 'Should return the correct success body')
|
|
442
|
+
assert.equal(attempt, 3, 'Should make exactly 3 attempts')
|
|
443
|
+
})
|
|
444
|
+
})
|
|
445
|
+
})
|
|
446
|
+
})
|
|
447
|
+
})
|
|
448
|
+
|
|
449
|
+
// ---------
|
|
450
|
+
// HTTP2 2.0
|
|
451
|
+
// ---------
|
|
452
|
+
|
|
453
|
+
suite('HTTP 2.0', () =>
|
|
454
|
+
{
|
|
455
|
+
// let server, baseUrl, request
|
|
456
|
+
|
|
457
|
+
beforeEach(async () =>
|
|
458
|
+
{
|
|
459
|
+
server = http2.createServer()
|
|
460
|
+
|
|
461
|
+
server.on('stream', (stream, headers) =>
|
|
462
|
+
{
|
|
463
|
+
const
|
|
464
|
+
method = headers[':method'],
|
|
465
|
+
path = headers[':path']
|
|
466
|
+
|
|
467
|
+
let body = ''
|
|
468
|
+
|
|
469
|
+
stream.on('error', console.error)
|
|
470
|
+
stream.on('data', (chunk) => body += chunk)
|
|
471
|
+
stream.on('end', () =>
|
|
472
|
+
{
|
|
473
|
+
const response =
|
|
474
|
+
{
|
|
475
|
+
method,
|
|
476
|
+
headers,
|
|
477
|
+
url : path,
|
|
478
|
+
body : headers['content-type'] === 'application/json'
|
|
479
|
+
? JSON.parse(body || '{}')
|
|
480
|
+
: body
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
stream.respond(
|
|
484
|
+
{
|
|
485
|
+
':status' : 200,
|
|
486
|
+
'content-type' : 'application/json',
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
stream.writable && stream.end(JSON.stringify(response))
|
|
490
|
+
})
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
await new Promise((accept) =>
|
|
494
|
+
{
|
|
495
|
+
server.listen(async () =>
|
|
496
|
+
{
|
|
497
|
+
const { port } = server.address()
|
|
498
|
+
baseUrl = `http://localhost:${port}`
|
|
499
|
+
request = new Request()
|
|
500
|
+
await request.connect(baseUrl)
|
|
501
|
+
accept()
|
|
502
|
+
})
|
|
503
|
+
})
|
|
504
|
+
})
|
|
505
|
+
|
|
506
|
+
afterEach((done) => request.close().then(() => server.close(done)))
|
|
507
|
+
|
|
508
|
+
executeTheSameTestSuitFor_http1_http2()
|
|
509
|
+
|
|
510
|
+
suite('Tests that require an altered server response', () =>
|
|
511
|
+
{
|
|
512
|
+
test('Retry on connection error', async () =>
|
|
513
|
+
{
|
|
514
|
+
let attempts = 0
|
|
515
|
+
|
|
516
|
+
// Alter the server to not accept the first two requests
|
|
517
|
+
server.removeAllListeners('stream')
|
|
518
|
+
server.on('stream', (stream, headers) =>
|
|
519
|
+
{
|
|
520
|
+
attempts++
|
|
521
|
+
|
|
522
|
+
if(attempts < 3)
|
|
523
|
+
{
|
|
524
|
+
stream.destroy()
|
|
525
|
+
}
|
|
526
|
+
else
|
|
527
|
+
{
|
|
528
|
+
stream.respond({ ':status': 200, 'content-type': 'application/json' })
|
|
529
|
+
stream.end(JSON.stringify({ success: true }))
|
|
530
|
+
}
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
const response = await request.get({ url:'/retry-test', retry: 3, retryDelay: 100 })
|
|
534
|
+
assert.equal(response.status, 200, 'Should return a 200 status code after retries')
|
|
535
|
+
assert.deepEqual(response.body, { success: true }, 'Should return the correct body')
|
|
536
|
+
assert.equal(attempts, 3, 'Should make exactly 3 attempts')
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
test('Supports request timeout', async () =>
|
|
540
|
+
{
|
|
541
|
+
// Alter the server to delay the response by 0.5 seconds
|
|
542
|
+
server.removeAllListeners('stream')
|
|
543
|
+
server.on('stream', (stream) =>
|
|
544
|
+
{
|
|
545
|
+
setTimeout(() => stream.destroy(), 500)
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
await assert.rejects(
|
|
549
|
+
request.get({ url: '/timeout-test', timeout: 100 }),
|
|
550
|
+
(error) => error.code === 'E_HTTP_REQUEST_CLIENT_TIMEOUT',
|
|
551
|
+
'Should throw a timeout error')
|
|
552
|
+
})
|
|
553
|
+
})
|
|
554
|
+
})
|
|
555
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@superhero/http-request",
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "HTTP request supporting HTTP 1.1 and HTTP 2.0",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"http request",
|
|
7
|
+
"http 1.1",
|
|
8
|
+
"http 2.0"
|
|
9
|
+
],
|
|
10
|
+
"main": "index.js",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./index.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node --trace-warnings --test --experimental-test-coverage"
|
|
18
|
+
},
|
|
19
|
+
"author": {
|
|
20
|
+
"name": "Erik Landvall",
|
|
21
|
+
"email": "erik@landvall.se"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/superhero/http-request"
|
|
26
|
+
}
|
|
27
|
+
}
|