@nxtedition/nxt-undici 2.0.47 → 2.0.49
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/lib/index.js +24 -8
- package/lib/interceptor/log.js +20 -28
- package/lib/interceptor/redirect.js +58 -54
- package/lib/interceptor/request-id.js +1 -1
- package/lib/interceptor/response-content.js +2 -2
- package/lib/interceptor/response-error.js +18 -10
- package/lib/interceptor/response-retry.js +11 -4
- package/lib/readable.js +1 -1
- package/lib/utils.js +10 -32
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
2
|
import createError from 'http-errors'
|
|
3
3
|
import undici from 'undici'
|
|
4
|
-
import {
|
|
4
|
+
import { parseHeaders, AbortError, headerNameToString, isStream } from './utils.js'
|
|
5
5
|
import { BodyReadable } from './readable.js'
|
|
6
6
|
|
|
7
7
|
const dispatcherCache = new WeakMap()
|
|
@@ -61,17 +61,18 @@ export async function request(url, opts) {
|
|
|
61
61
|
const method = opts.method ?? (opts.body ? 'POST' : 'GET')
|
|
62
62
|
const idempotent = opts.idempotent ?? (method === 'GET' || method === 'HEAD')
|
|
63
63
|
|
|
64
|
-
let headers
|
|
64
|
+
let headers = {}
|
|
65
65
|
if (Array.isArray(opts.headers)) {
|
|
66
66
|
headers = parseHeaders(opts.headers)
|
|
67
67
|
} else if (opts.headers != null) {
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
for (const [key, val] of Object.entries(opts.headers)) {
|
|
69
|
+
headers[headerNameToString(key)] = val
|
|
70
|
+
}
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
const userAgent = opts.userAgent ?? globalThis.userAgent
|
|
73
74
|
if (userAgent && headers?.['user-agent'] !== userAgent) {
|
|
74
|
-
headers
|
|
75
|
+
headers['user-agent'] = userAgent
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
if (method === 'CONNECT') {
|
|
@@ -86,6 +87,14 @@ export async function request(url, opts) {
|
|
|
86
87
|
throw new createError.BadRequest('HEAD and GET cannot have body')
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
if (
|
|
91
|
+
opts.body != null &&
|
|
92
|
+
(opts.body.size > 0 || opts.body.length > 0) &&
|
|
93
|
+
(method === 'HEAD' || method === 'GET')
|
|
94
|
+
) {
|
|
95
|
+
throw new createError.BadRequest('HEAD and GET cannot have body')
|
|
96
|
+
}
|
|
97
|
+
|
|
89
98
|
const expectsPayload = opts.method === 'PUT' || opts.method === 'POST' || opts.method === 'PATCH'
|
|
90
99
|
|
|
91
100
|
if (headers != null && headers['content-length'] === '0' && !expectsPayload) {
|
|
@@ -124,7 +133,7 @@ export async function request(url, opts) {
|
|
|
124
133
|
return await new Promise((resolve, reject) =>
|
|
125
134
|
dispatch(
|
|
126
135
|
{
|
|
127
|
-
id: opts.id ??
|
|
136
|
+
id: opts.id ?? headers['request-id'] ?? headers['Request-Id'] ?? genReqId(),
|
|
128
137
|
url,
|
|
129
138
|
method,
|
|
130
139
|
body: opts.body,
|
|
@@ -187,8 +196,8 @@ export async function request(url, opts) {
|
|
|
187
196
|
) {
|
|
188
197
|
assert(statusCode >= 200)
|
|
189
198
|
|
|
190
|
-
const contentLength =
|
|
191
|
-
const contentType =
|
|
199
|
+
const contentLength = headers['content-length']
|
|
200
|
+
const contentType = headers['content-type']
|
|
192
201
|
|
|
193
202
|
this.body = new BodyReadable(this, {
|
|
194
203
|
resume,
|
|
@@ -201,6 +210,13 @@ export async function request(url, opts) {
|
|
|
201
210
|
size: Number.isFinite(contentLength) ? contentLength : null,
|
|
202
211
|
})
|
|
203
212
|
|
|
213
|
+
if (this.signal) {
|
|
214
|
+
this.body.on('close', () => {
|
|
215
|
+
this.signal?.removeEventListener('abort', this.onAbort)
|
|
216
|
+
this.signal = null
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
204
220
|
this.resolve(this.body)
|
|
205
221
|
this.resolve = null
|
|
206
222
|
this.reject = null
|
package/lib/interceptor/log.js
CHANGED
|
@@ -9,7 +9,8 @@ class Handler extends DecoratorHandler {
|
|
|
9
9
|
#aborted = false
|
|
10
10
|
#logger
|
|
11
11
|
#pos
|
|
12
|
-
#
|
|
12
|
+
#timing
|
|
13
|
+
#startTime
|
|
13
14
|
|
|
14
15
|
constructor(opts, { handler }) {
|
|
15
16
|
super(handler)
|
|
@@ -17,24 +18,18 @@ class Handler extends DecoratorHandler {
|
|
|
17
18
|
this.#handler = handler
|
|
18
19
|
this.#opts = opts
|
|
19
20
|
this.#logger = opts.logger.child({ ureq: { id: opts.id } })
|
|
20
|
-
this.#stats = {
|
|
21
|
-
created: performance.now(),
|
|
22
|
-
start: -1,
|
|
23
|
-
end: -1,
|
|
24
|
-
headers: -1,
|
|
25
|
-
firstBodyReceived: -1,
|
|
26
|
-
lastBodyReceived: -1,
|
|
27
|
-
}
|
|
28
21
|
}
|
|
29
22
|
|
|
30
23
|
onConnect(abort) {
|
|
31
24
|
this.#pos = 0
|
|
32
25
|
this.#abort = abort
|
|
33
|
-
this.#
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
this.#timing = {
|
|
27
|
+
headers: -1,
|
|
28
|
+
data: -1,
|
|
29
|
+
complete: -1,
|
|
30
|
+
error: -1,
|
|
31
|
+
}
|
|
32
|
+
this.#startTime = performance.now()
|
|
38
33
|
|
|
39
34
|
this.#logger.debug({ ureq: this.#opts }, 'upstream request started')
|
|
40
35
|
|
|
@@ -54,12 +49,12 @@ class Handler extends DecoratorHandler {
|
|
|
54
49
|
}
|
|
55
50
|
|
|
56
51
|
onHeaders(statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
|
|
57
|
-
this.#
|
|
52
|
+
this.#timing.headers = performance.now() - this.#startTime
|
|
58
53
|
|
|
59
54
|
this.#logger.debug(
|
|
60
55
|
{
|
|
61
56
|
ures: { statusCode, headers },
|
|
62
|
-
elapsedTime: this.#
|
|
57
|
+
elapsedTime: this.#timing.headers,
|
|
63
58
|
},
|
|
64
59
|
'upstream request response',
|
|
65
60
|
)
|
|
@@ -68,8 +63,8 @@ class Handler extends DecoratorHandler {
|
|
|
68
63
|
}
|
|
69
64
|
|
|
70
65
|
onData(chunk) {
|
|
71
|
-
if (this.#
|
|
72
|
-
this.#
|
|
66
|
+
if (this.#timing.data === -1) {
|
|
67
|
+
this.#timing.data = performance.now() - this.#startTime
|
|
73
68
|
}
|
|
74
69
|
|
|
75
70
|
this.#pos += chunk.length
|
|
@@ -78,11 +73,10 @@ class Handler extends DecoratorHandler {
|
|
|
78
73
|
}
|
|
79
74
|
|
|
80
75
|
onComplete(rawTrailers) {
|
|
81
|
-
this.#
|
|
82
|
-
this.#stats.end = this.#stats.lastBodyReceived
|
|
76
|
+
this.#timing.complete = performance.now() - this.#startTime
|
|
83
77
|
|
|
84
78
|
this.#logger.debug(
|
|
85
|
-
{
|
|
79
|
+
{ elapsedTime: this.#timing.complete, bytesRead: this.#pos, timing: this.#timing },
|
|
86
80
|
'upstream request completed',
|
|
87
81
|
)
|
|
88
82
|
|
|
@@ -90,17 +84,15 @@ class Handler extends DecoratorHandler {
|
|
|
90
84
|
}
|
|
91
85
|
|
|
92
86
|
onError(err) {
|
|
93
|
-
|
|
94
|
-
this.#stats.end = performance.now() - this.#stats.start
|
|
95
|
-
}
|
|
87
|
+
this.#timing.error = performance.now() - this.#startTime
|
|
96
88
|
|
|
97
89
|
if (this.#aborted) {
|
|
98
90
|
this.#logger.debug(
|
|
99
91
|
{
|
|
100
92
|
ureq: this.#opts,
|
|
101
93
|
bytesRead: this.#pos,
|
|
102
|
-
|
|
103
|
-
|
|
94
|
+
timing: this.#timing,
|
|
95
|
+
elapsedTime: this.#timing.error,
|
|
104
96
|
err,
|
|
105
97
|
},
|
|
106
98
|
'upstream request aborted',
|
|
@@ -110,8 +102,8 @@ class Handler extends DecoratorHandler {
|
|
|
110
102
|
{
|
|
111
103
|
ureq: this.#opts,
|
|
112
104
|
bytesRead: this.#pos,
|
|
113
|
-
|
|
114
|
-
|
|
105
|
+
timing: this.#timing,
|
|
106
|
+
elapsedTime: this.#timing.error,
|
|
115
107
|
err,
|
|
116
108
|
},
|
|
117
109
|
'upstream request failed',
|
|
@@ -1,97 +1,101 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
|
-
import {
|
|
2
|
+
import { isDisturbed, parseHeaders, parseURL } from '../utils.js'
|
|
3
3
|
import { DecoratorHandler } from 'undici'
|
|
4
4
|
|
|
5
5
|
const redirectableStatusCodes = [300, 301, 302, 303, 307, 308]
|
|
6
6
|
|
|
7
7
|
class Handler extends DecoratorHandler {
|
|
8
|
+
#dispatch
|
|
9
|
+
#handler
|
|
10
|
+
|
|
11
|
+
#opts
|
|
12
|
+
#abort
|
|
13
|
+
#aborted = false
|
|
14
|
+
#reason
|
|
15
|
+
#maxCount
|
|
16
|
+
#headersSent = false
|
|
17
|
+
#count = 0
|
|
18
|
+
#location
|
|
19
|
+
#history = []
|
|
20
|
+
|
|
8
21
|
constructor(opts, { dispatch, handler }) {
|
|
9
22
|
super(handler)
|
|
10
23
|
|
|
11
|
-
this
|
|
12
|
-
this
|
|
13
|
-
this
|
|
14
|
-
this
|
|
15
|
-
this.aborted = false
|
|
16
|
-
this.reason = null
|
|
17
|
-
this.maxCount = Number.isFinite(opts.follow) ? opts.follow : opts.follow?.count ?? 0
|
|
18
|
-
|
|
19
|
-
this.headersSent = false
|
|
20
|
-
|
|
21
|
-
this.count = 0
|
|
22
|
-
this.location = null
|
|
23
|
-
this.history = []
|
|
24
|
+
this.#dispatch = dispatch
|
|
25
|
+
this.#handler = handler
|
|
26
|
+
this.#opts = opts
|
|
27
|
+
this.#maxCount = Number.isFinite(opts.follow) ? opts.follow : opts.follow?.count ?? 0
|
|
24
28
|
|
|
25
|
-
this
|
|
26
|
-
this
|
|
27
|
-
if (this
|
|
28
|
-
this
|
|
29
|
+
this.#handler.onConnect((reason) => {
|
|
30
|
+
this.#aborted = true
|
|
31
|
+
if (this.#abort) {
|
|
32
|
+
this.#abort(reason)
|
|
29
33
|
} else {
|
|
30
|
-
this
|
|
34
|
+
this.#reason = reason
|
|
31
35
|
}
|
|
32
36
|
})
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
onConnect(abort) {
|
|
36
|
-
if (this
|
|
37
|
-
abort(this
|
|
40
|
+
if (this.#aborted) {
|
|
41
|
+
abort(this.#reason)
|
|
38
42
|
} else {
|
|
39
|
-
this
|
|
43
|
+
this.#abort = abort
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
onUpgrade(statusCode, rawHeaders, socket, headers) {
|
|
44
|
-
return this
|
|
48
|
+
return this.#handler.onUpgrade(statusCode, rawHeaders, socket, headers)
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
onHeaders(statusCode, rawHeaders, resume, statusText, headers) {
|
|
51
|
+
onHeaders(statusCode, rawHeaders, resume, statusText, headers = parseHeaders(rawHeaders)) {
|
|
48
52
|
if (redirectableStatusCodes.indexOf(statusCode) === -1) {
|
|
49
|
-
assert(!this
|
|
50
|
-
this
|
|
51
|
-
return this
|
|
53
|
+
assert(!this.#headersSent)
|
|
54
|
+
this.#headersSent = true
|
|
55
|
+
return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusText, headers)
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
if (isDisturbed(this
|
|
58
|
+
if (isDisturbed(this.#opts.body)) {
|
|
55
59
|
throw new Error(`Disturbed request cannot be redirected.`)
|
|
56
60
|
}
|
|
57
61
|
|
|
58
|
-
this
|
|
62
|
+
this.#location = headers.location
|
|
59
63
|
|
|
60
|
-
if (!this
|
|
64
|
+
if (!this.#location) {
|
|
61
65
|
throw new Error(`Missing redirection location .`)
|
|
62
66
|
}
|
|
63
67
|
|
|
64
|
-
this
|
|
65
|
-
this
|
|
68
|
+
this.#history.push(this.#location)
|
|
69
|
+
this.#count += 1
|
|
66
70
|
|
|
67
|
-
if (typeof this
|
|
68
|
-
if (!this
|
|
69
|
-
assert(!this
|
|
70
|
-
this
|
|
71
|
-
return this
|
|
71
|
+
if (typeof this.#opts.follow === 'function') {
|
|
72
|
+
if (!this.#opts.follow(this.#location, this.#count)) {
|
|
73
|
+
assert(!this.#headersSent)
|
|
74
|
+
this.#headersSent = true
|
|
75
|
+
return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusText, headers)
|
|
72
76
|
}
|
|
73
77
|
} else {
|
|
74
|
-
if (this
|
|
75
|
-
throw Object.assign(new Error(`Max redirections reached: ${this
|
|
76
|
-
history: this
|
|
78
|
+
if (this.#count >= this.#maxCount) {
|
|
79
|
+
throw Object.assign(new Error(`Max redirections reached: ${this.#maxCount}.`), {
|
|
80
|
+
history: this.#history,
|
|
77
81
|
})
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
const { origin, pathname, search } = parseURL(
|
|
82
|
-
new URL(this
|
|
86
|
+
new URL(this.#location, this.#opts.origin && new URL(this.#opts.path, this.#opts.origin)),
|
|
83
87
|
)
|
|
84
88
|
const path = search ? `${pathname}${search}` : pathname
|
|
85
89
|
|
|
86
90
|
// Remove headers referring to the original URL.
|
|
87
91
|
// By default it is Host only, unless it's a 303 (see below), which removes also all Content-* headers.
|
|
88
92
|
// https://tools.ietf.org/html/rfc7231#section-6.4
|
|
89
|
-
this
|
|
90
|
-
...this
|
|
93
|
+
this.#opts = {
|
|
94
|
+
...this.#opts,
|
|
91
95
|
headers: cleanRequestHeaders(
|
|
92
|
-
this
|
|
96
|
+
this.#opts.headers,
|
|
93
97
|
statusCode === 303,
|
|
94
|
-
this
|
|
98
|
+
this.#opts.origin !== origin,
|
|
95
99
|
),
|
|
96
100
|
path,
|
|
97
101
|
origin,
|
|
@@ -100,13 +104,13 @@ class Handler extends DecoratorHandler {
|
|
|
100
104
|
|
|
101
105
|
// https://tools.ietf.org/html/rfc7231#section-6.4.4
|
|
102
106
|
// In case of HTTP 303, always replace method to be either HEAD or GET
|
|
103
|
-
if (statusCode === 303 && this
|
|
104
|
-
this
|
|
107
|
+
if (statusCode === 303 && this.#opts.method !== 'HEAD') {
|
|
108
|
+
this.#opts = { ...this.#opts, method: 'GET', body: null }
|
|
105
109
|
}
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
onData(chunk) {
|
|
109
|
-
if (this
|
|
113
|
+
if (this.#location) {
|
|
110
114
|
/*
|
|
111
115
|
https://tools.ietf.org/html/rfc7231#section-6.4
|
|
112
116
|
|
|
@@ -125,12 +129,12 @@ class Handler extends DecoratorHandler {
|
|
|
125
129
|
servers and browsers implementors, we ignore the body as there is no specified way to eventually parse it.
|
|
126
130
|
*/
|
|
127
131
|
} else {
|
|
128
|
-
return this
|
|
132
|
+
return this.#handler.onData(chunk)
|
|
129
133
|
}
|
|
130
134
|
}
|
|
131
135
|
|
|
132
136
|
onComplete(trailers) {
|
|
133
|
-
if (this
|
|
137
|
+
if (this.#location) {
|
|
134
138
|
/*
|
|
135
139
|
https://tools.ietf.org/html/rfc7231#section-6.4
|
|
136
140
|
|
|
@@ -140,16 +144,16 @@ class Handler extends DecoratorHandler {
|
|
|
140
144
|
See comment on onData method above for more detailed informations.
|
|
141
145
|
*/
|
|
142
146
|
|
|
143
|
-
this
|
|
147
|
+
this.#location = null
|
|
144
148
|
|
|
145
|
-
this
|
|
149
|
+
this.#dispatch(this.#opts, this)
|
|
146
150
|
} else {
|
|
147
|
-
return this
|
|
151
|
+
return this.#handler.onComplete(trailers)
|
|
148
152
|
}
|
|
149
153
|
}
|
|
150
154
|
|
|
151
155
|
onError(error) {
|
|
152
|
-
return this
|
|
156
|
+
return this.#handler.onError(error)
|
|
153
157
|
}
|
|
154
158
|
}
|
|
155
159
|
|
|
@@ -12,7 +12,7 @@ function genReqId() {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export default (dispatch) => (opts, handler) => {
|
|
15
|
-
let id = opts.id ?? opts.headers?.['request-id']
|
|
15
|
+
let id = opts.id ?? opts.headers?.['request-id']
|
|
16
16
|
id = id ? `${id},${genReqId()}` : genReqId()
|
|
17
17
|
|
|
18
18
|
return dispatch(
|
|
@@ -31,8 +31,8 @@ class Handler extends DecoratorHandler {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
onHeaders(statusCode, rawHeaders, resume, statusMessage, headers = parseHeaders(rawHeaders)) {
|
|
34
|
-
this.#contentMD5 = headers
|
|
35
|
-
this.#contentLength = headers
|
|
34
|
+
this.#contentMD5 = headers['content-md5']
|
|
35
|
+
this.#contentLength = headers['content-length']
|
|
36
36
|
this.#hasher = this.#contentMD5 != null ? crypto.createHash('md5') : null
|
|
37
37
|
|
|
38
38
|
return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusMessage, headers)
|
|
@@ -34,14 +34,14 @@ class Handler extends DecoratorHandler {
|
|
|
34
34
|
this.#headers = headers
|
|
35
35
|
this.#contentType = headers['content-type']
|
|
36
36
|
|
|
37
|
-
if (this.#statusCode
|
|
38
|
-
// TODO (fix): Check content length
|
|
39
|
-
if (this.#contentType === 'application/json' || this.#contentType === 'text/plain') {
|
|
40
|
-
this.#decoder = new TextDecoder('utf-8')
|
|
41
|
-
}
|
|
42
|
-
} else {
|
|
37
|
+
if (this.#statusCode < 400) {
|
|
43
38
|
return this.#handler.onHeaders(statusCode, rawHeaders, resume, statusMessage, headers)
|
|
44
39
|
}
|
|
40
|
+
|
|
41
|
+
// TODO (fix): Check content length
|
|
42
|
+
if (this.#contentType === 'application/json' || this.#contentType === 'text/plain') {
|
|
43
|
+
this.#decoder = new TextDecoder('utf-8')
|
|
44
|
+
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
onData(chunk) {
|
|
@@ -65,14 +65,22 @@ class Handler extends DecoratorHandler {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
this.#errored = true
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
|
|
69
|
+
let err
|
|
70
|
+
|
|
71
|
+
const stackTraceLimit = Error.stackTraceLimit
|
|
72
|
+
Error.stackTraceLimit = 0
|
|
73
|
+
try {
|
|
74
|
+
err = Object.assign(createHttpError(this.#statusCode), {
|
|
70
75
|
reason: this.#body?.reason,
|
|
71
76
|
error: this.#body?.error,
|
|
72
77
|
headers: this.#headers,
|
|
73
78
|
body: this.#body,
|
|
74
|
-
})
|
|
75
|
-
|
|
79
|
+
})
|
|
80
|
+
} finally {
|
|
81
|
+
Error.stackTraceLimit = stackTraceLimit
|
|
82
|
+
}
|
|
83
|
+
this.#handler.onError(err)
|
|
76
84
|
} else {
|
|
77
85
|
this.#handler.onComplete(rawTrailers)
|
|
78
86
|
}
|
|
@@ -97,6 +97,13 @@ class Handler extends DecoratorHandler {
|
|
|
97
97
|
return this.#onHeaders(statusCode, rawHeaders, resume, statusMessage, headers)
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
// Weak etags are not useful for comparison nor cache
|
|
101
|
+
// for instance not safe to assume if the response is byte-per-byte
|
|
102
|
+
// equal
|
|
103
|
+
if (this.#etag != null && this.#etag.startsWith('W/')) {
|
|
104
|
+
this.#etag = null
|
|
105
|
+
}
|
|
106
|
+
|
|
100
107
|
assert(Number.isFinite(this.#pos))
|
|
101
108
|
assert(this.#end == null || Number.isFinite(this.#end))
|
|
102
109
|
|
|
@@ -104,8 +111,7 @@ class Handler extends DecoratorHandler {
|
|
|
104
111
|
} else if (statusCode === 206 || (this.#pos === 0 && statusCode === 200)) {
|
|
105
112
|
assert(this.#etag != null || !this.#pos)
|
|
106
113
|
|
|
107
|
-
|
|
108
|
-
if (this.#pos > 0 && this.#etag !== etag) {
|
|
114
|
+
if (this.#pos > 0 && this.#etag !== headers.etag) {
|
|
109
115
|
throw this.#error
|
|
110
116
|
}
|
|
111
117
|
|
|
@@ -138,7 +144,7 @@ class Handler extends DecoratorHandler {
|
|
|
138
144
|
return
|
|
139
145
|
}
|
|
140
146
|
|
|
141
|
-
const retryPromise = retryFn(err, this.#retryCount++, this.#opts)
|
|
147
|
+
const retryPromise = retryFn(err, this.#retryCount++, { ...this.#opts.retry })
|
|
142
148
|
if (retryPromise == null) {
|
|
143
149
|
this.#onError(err)
|
|
144
150
|
return
|
|
@@ -193,7 +199,8 @@ class Handler extends DecoratorHandler {
|
|
|
193
199
|
}
|
|
194
200
|
|
|
195
201
|
export default (dispatch) => (opts, handler) => {
|
|
196
|
-
|
|
202
|
+
// TODO (fix): HEAD, PUT, PATCH, DELETE, OPTIONS?
|
|
203
|
+
return opts.retry && opts.method === 'GET' && !opts.upgrade
|
|
197
204
|
? dispatch(opts, new Handler(opts, { handler, dispatch }))
|
|
198
205
|
: dispatch(opts, handler)
|
|
199
206
|
}
|
package/lib/readable.js
CHANGED
|
@@ -90,7 +90,7 @@ export class BodyReadable extends Readable {
|
|
|
90
90
|
this[kHandler].signal = null
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
if (
|
|
93
|
+
if (!this[kReading]) {
|
|
94
94
|
// Workaround for Node "bug". If the stream is destroyed in same
|
|
95
95
|
// tick as it is created, then a user who is waiting for a
|
|
96
96
|
// promise (i.e micro tick) for installing a 'error' listener will
|
package/lib/utils.js
CHANGED
|
@@ -6,6 +6,10 @@ export function parseCacheControl(str) {
|
|
|
6
6
|
return str ? cacheControlParser.parse(str) : null
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
export function headerNameToString(name) {
|
|
10
|
+
return util.headerNameToString(name)
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
// Parsed accordingly to RFC 9110
|
|
10
14
|
// https://www.rfc-editor.org/rfc/rfc9110#field.content-range
|
|
11
15
|
export function parseRangeHeader(range) {
|
|
@@ -66,50 +70,24 @@ export function parseContentRange(range) {
|
|
|
66
70
|
return { start, end: end ? end + 1 : size, size }
|
|
67
71
|
}
|
|
68
72
|
|
|
69
|
-
export function findHeader(headers, name) {
|
|
70
|
-
const len = name.length
|
|
71
|
-
|
|
72
|
-
if (Array.isArray(headers)) {
|
|
73
|
-
for (let i = 0; i < headers.length; i += 2) {
|
|
74
|
-
const key = headers[i + 0]
|
|
75
|
-
if (key.length === len && util.headerNameToString(key) === name) {
|
|
76
|
-
return headers[i + 1]?.toString()
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
} else if (headers != null) {
|
|
80
|
-
const val = headers[name]
|
|
81
|
-
if (val !== undefined) {
|
|
82
|
-
return val
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
for (const key of Object.keys(headers)) {
|
|
86
|
-
if (key.length === len && util.headerNameToString(key) === name) {
|
|
87
|
-
return headers[key]?.toString()
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return null
|
|
93
|
-
}
|
|
94
|
-
|
|
95
73
|
export function retry(err, retryCount, opts) {
|
|
96
|
-
if (!opts
|
|
74
|
+
if (!opts) {
|
|
97
75
|
return null
|
|
98
76
|
}
|
|
99
77
|
|
|
100
|
-
if (typeof opts
|
|
78
|
+
if (typeof opts === 'function') {
|
|
101
79
|
try {
|
|
102
|
-
return opts
|
|
80
|
+
return opts(err, retryCount, opts, (opts) => retry(err, retryCount, opts))
|
|
103
81
|
} catch (err) {
|
|
104
82
|
return Promise.reject(err)
|
|
105
83
|
}
|
|
106
84
|
}
|
|
107
85
|
|
|
108
|
-
if (typeof opts
|
|
109
|
-
opts = { count: opts
|
|
86
|
+
if (typeof opts === 'number') {
|
|
87
|
+
opts = { count: opts }
|
|
110
88
|
}
|
|
111
89
|
|
|
112
|
-
const retryMax = opts
|
|
90
|
+
const retryMax = opts?.count ?? 8
|
|
113
91
|
|
|
114
92
|
if (retryCount > retryMax) {
|
|
115
93
|
return null
|