@nxtedition/nxt-undici 2.0.16 → 2.0.18
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 +7 -8
- package/lib/interceptor/cache.js +2 -2
- package/lib/interceptor/log.js +3 -3
- package/lib/interceptor/redirect.js +2 -2
- package/lib/interceptor/request-body.js +2 -2
- package/lib/interceptor/request-content.js +12 -11
- package/lib/interceptor/response-content.js +11 -13
- package/lib/interceptor/response-error.js +15 -8
- package/lib/interceptor/response-retry-body.js +169 -0
- package/lib/interceptor/response-retry.js +23 -99
- package/lib/utils.js +20 -5
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -84,6 +84,7 @@ class Readable extends stream.Readable {
|
|
|
84
84
|
async text() {
|
|
85
85
|
const dec = new TextDecoder()
|
|
86
86
|
let str = ''
|
|
87
|
+
|
|
87
88
|
for await (const chunk of this) {
|
|
88
89
|
if (typeof chunk === 'string') {
|
|
89
90
|
str += chunk
|
|
@@ -91,6 +92,7 @@ class Readable extends stream.Readable {
|
|
|
91
92
|
str += dec.decode(chunk, { stream: true })
|
|
92
93
|
}
|
|
93
94
|
}
|
|
95
|
+
|
|
94
96
|
// Flush the streaming TextDecoder so that any pending
|
|
95
97
|
// incomplete multibyte characters are handled.
|
|
96
98
|
str += dec.decode(undefined, { stream: false })
|
|
@@ -142,6 +144,7 @@ const dispatchers = {
|
|
|
142
144
|
log: (await import('./interceptor/log.js')).default,
|
|
143
145
|
redirect: (await import('./interceptor/redirect.js')).default,
|
|
144
146
|
responseRetry: (await import('./interceptor/response-retry.js')).default,
|
|
147
|
+
responseRetryBody: (await import('./interceptor/response-retry-body.js')).default,
|
|
145
148
|
proxy: (await import('./interceptor/proxy.js')).default,
|
|
146
149
|
cache: (await import('./interceptor/cache.js')).default,
|
|
147
150
|
requestId: (await import('./interceptor/request-id.js')).default,
|
|
@@ -232,10 +235,11 @@ export async function request(url, opts) {
|
|
|
232
235
|
dispatch = dispatchers.log(dispatch)
|
|
233
236
|
dispatch = dispatchers.requestId(dispatch)
|
|
234
237
|
dispatch = dispatchers.responseRetry(dispatch)
|
|
238
|
+
dispatch = dispatchers.responseRetryBody(dispatch)
|
|
235
239
|
dispatch = dispatchers.responseContent(dispatch)
|
|
236
240
|
dispatch = dispatchers.requestContent(dispatch)
|
|
237
|
-
dispatch = dispatchers.redirect(dispatch)
|
|
238
241
|
dispatch = dispatchers.cache(dispatch)
|
|
242
|
+
dispatch = dispatchers.redirect(dispatch)
|
|
239
243
|
dispatch = dispatchers.proxy(dispatch)
|
|
240
244
|
dispatch = dispatchers.requestBody(dispatch)
|
|
241
245
|
dispatcherCache.set(dispatcher, dispatch)
|
|
@@ -244,7 +248,7 @@ export async function request(url, opts) {
|
|
|
244
248
|
return await new Promise((resolve, reject) =>
|
|
245
249
|
dispatch(
|
|
246
250
|
{
|
|
247
|
-
id: opts.id ?? headers
|
|
251
|
+
id: opts.id ?? findHeader(headers, 'request-id') ?? genReqId(),
|
|
248
252
|
url,
|
|
249
253
|
method,
|
|
250
254
|
body: opts.body,
|
|
@@ -289,9 +293,7 @@ export async function request(url, opts) {
|
|
|
289
293
|
}
|
|
290
294
|
}
|
|
291
295
|
},
|
|
292
|
-
onUpgrade(statusCode, rawHeaders, socket) {
|
|
293
|
-
const headers = parseHeaders(rawHeaders)
|
|
294
|
-
|
|
296
|
+
onUpgrade(statusCode, rawHeaders, socket, headers = parseHeaders(rawHeaders)) {
|
|
295
297
|
if (statusCode !== 101) {
|
|
296
298
|
this.abort(createError(statusCode, { headers }))
|
|
297
299
|
} else {
|
|
@@ -332,9 +334,6 @@ export async function request(url, opts) {
|
|
|
332
334
|
return this.body.push(chunk)
|
|
333
335
|
},
|
|
334
336
|
onComplete() {
|
|
335
|
-
this.signal?.removeEventListener('abort', this.onAbort)
|
|
336
|
-
this.signal = null
|
|
337
|
-
|
|
338
337
|
this.body.push(null)
|
|
339
338
|
},
|
|
340
339
|
onError(err) {
|
package/lib/interceptor/cache.js
CHANGED
|
@@ -14,8 +14,8 @@ class CacheHandler {
|
|
|
14
14
|
return this.handler.onConnect(abort)
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
onUpgrade(statusCode, rawHeaders, socket) {
|
|
18
|
-
return this.handler.onUpgrade(statusCode, rawHeaders, socket)
|
|
17
|
+
onUpgrade(statusCode, rawHeaders, socket, headers) {
|
|
18
|
+
return this.handler.onUpgrade(statusCode, rawHeaders, socket, headers)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
onBodySent(chunk) {
|
package/lib/interceptor/log.js
CHANGED
|
@@ -31,13 +31,13 @@ class Handler {
|
|
|
31
31
|
})
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
onUpgrade(statusCode, rawHeaders, socket) {
|
|
34
|
+
onUpgrade(statusCode, rawHeaders, socket, headers) {
|
|
35
35
|
this.logger.debug('upstream request upgraded')
|
|
36
36
|
socket.on('close', () => {
|
|
37
37
|
this.logger.debug('upstream request socket closed')
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
-
return this.handler.onUpgrade(statusCode, rawHeaders, socket)
|
|
40
|
+
return this.handler.onUpgrade(statusCode, rawHeaders, socket, headers)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
onBodySent(chunk) {
|
|
@@ -100,7 +100,7 @@ class Handler {
|
|
|
100
100
|
)
|
|
101
101
|
} else {
|
|
102
102
|
this.logger.error(
|
|
103
|
-
{ bytesRead: this.pos, elapsedTime: this.stats.end, err },
|
|
103
|
+
{ ureq: this.opts, bytesRead: this.pos, elapsedTime: this.stats.end, err },
|
|
104
104
|
'upstream request failed',
|
|
105
105
|
)
|
|
106
106
|
}
|
|
@@ -34,8 +34,8 @@ class Handler {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
onUpgrade(statusCode,
|
|
38
|
-
return this.handler.onUpgrade(statusCode,
|
|
37
|
+
onUpgrade(statusCode, rawHeaders, socket, headers) {
|
|
38
|
+
return this.handler.onUpgrade(statusCode, rawHeaders, socket, headers)
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
onBodySent(chunk) {
|
|
@@ -27,8 +27,8 @@ class Handler {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
onUpgrade(statusCode, rawHeaders, socket) {
|
|
31
|
-
return this.handler.onUpgrade(statusCode, rawHeaders, socket)
|
|
30
|
+
onUpgrade(statusCode, rawHeaders, socket, headers) {
|
|
31
|
+
return this.handler.onUpgrade(statusCode, rawHeaders, socket, headers)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
onBodySent(chunk) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import crypto from 'node:crypto'
|
|
2
|
+
import { findHeader } from '../utils.js'
|
|
2
3
|
|
|
3
4
|
class Handler {
|
|
4
5
|
constructor(opts, { handler, md5, length }) {
|
|
@@ -16,8 +17,8 @@ class Handler {
|
|
|
16
17
|
return this.handler.onConnect(abort)
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
onUpgrade(statusCode, rawHeaders, socket) {
|
|
20
|
-
return this.handler.onUpgrade(statusCode, rawHeaders, socket)
|
|
20
|
+
onUpgrade(statusCode, rawHeaders, socket, headers) {
|
|
21
|
+
return this.handler.onUpgrade(statusCode, rawHeaders, socket, headers)
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
onBodySent(chunk) {
|
|
@@ -30,20 +31,20 @@ class Handler {
|
|
|
30
31
|
onRequestSent() {
|
|
31
32
|
const hash = this.hasher?.digest('base64')
|
|
32
33
|
|
|
33
|
-
if (this.
|
|
34
|
+
if (this.length != null && this.pos !== Number(this.length)) {
|
|
34
35
|
this.abort(
|
|
35
36
|
Object.assign(new Error('Request Content-Length mismatch'), {
|
|
36
|
-
expected: this.
|
|
37
|
-
actual:
|
|
37
|
+
expected: Number(this.length),
|
|
38
|
+
actual: this.pos,
|
|
38
39
|
}),
|
|
39
40
|
)
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
if (this.
|
|
43
|
+
if (this.md5 != null && hash !== this.md5) {
|
|
43
44
|
this.abort(
|
|
44
|
-
Object.assign(new Error('Request Content-
|
|
45
|
-
expected:
|
|
46
|
-
actual:
|
|
45
|
+
Object.assign(new Error('Request Content-MD5 mismatch'), {
|
|
46
|
+
expected: this.md5,
|
|
47
|
+
actual: hash,
|
|
47
48
|
}),
|
|
48
49
|
)
|
|
49
50
|
}
|
|
@@ -74,8 +75,8 @@ export default (dispatch) => (opts, handler) => {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
// TODO (fix): case-insensitive check?
|
|
77
|
-
const md5 = opts.headers
|
|
78
|
-
const length = opts.headers
|
|
78
|
+
const md5 = findHeader(opts.headers, 'content-md5')
|
|
79
|
+
const length = findHeader(opts.headers, 'content-length')
|
|
79
80
|
|
|
80
81
|
return md5 != null || length != null
|
|
81
82
|
? dispatch(opts, new Handler(opts, { handler, md5, length }))
|
|
@@ -14,8 +14,8 @@ class Handler {
|
|
|
14
14
|
return this.handler.onConnect(abort)
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
onUpgrade(statusCode, rawHeaders, socket) {
|
|
18
|
-
return this.handler.onUpgrade(statusCode, rawHeaders, socket)
|
|
17
|
+
onUpgrade(statusCode, rawHeaders, socket, headers) {
|
|
18
|
+
return this.handler.onUpgrade(statusCode, rawHeaders, socket, headers)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
onBodySent(chunk) {
|
|
@@ -44,15 +44,6 @@ class Handler {
|
|
|
44
44
|
onComplete(rawTrailers) {
|
|
45
45
|
const hash = this.hasher?.digest('base64')
|
|
46
46
|
|
|
47
|
-
if (this.md5 != null && hash !== this.md5) {
|
|
48
|
-
return this.handler.onError(
|
|
49
|
-
Object.assign(new Error('Request Content-Length mismatch'), {
|
|
50
|
-
expected: this.md5,
|
|
51
|
-
actual: hash,
|
|
52
|
-
}),
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
47
|
if (this.length != null && this.pos !== Number(this.length)) {
|
|
57
48
|
return this.handler.onError(
|
|
58
49
|
Object.assign(new Error('Request Content-Length mismatch'), {
|
|
@@ -60,9 +51,16 @@ class Handler {
|
|
|
60
51
|
actual: this.pos,
|
|
61
52
|
}),
|
|
62
53
|
)
|
|
54
|
+
} else if (this.md5 != null && hash !== this.md5) {
|
|
55
|
+
return this.handler.onError(
|
|
56
|
+
Object.assign(new Error('Request Content-MD5 mismatch'), {
|
|
57
|
+
expected: this.md5,
|
|
58
|
+
actual: hash,
|
|
59
|
+
}),
|
|
60
|
+
)
|
|
61
|
+
} else {
|
|
62
|
+
return this.handler.onComplete(rawTrailers)
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
return this.handler.onComplete(rawTrailers)
|
|
66
64
|
}
|
|
67
65
|
|
|
68
66
|
onError(err) {
|
|
@@ -15,8 +15,8 @@ class Handler {
|
|
|
15
15
|
return this.handler.onConnect(abort)
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
onUpgrade(statusCode, rawHeaders, socket) {
|
|
19
|
-
return this.handler.onUpgrade(statusCode, rawHeaders, socket)
|
|
18
|
+
onUpgrade(statusCode, rawHeaders, socket, headers) {
|
|
19
|
+
return this.handler.onUpgrade(statusCode, rawHeaders, socket, headers)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
onBodySent(chunk) {
|
|
@@ -63,26 +63,33 @@ class Handler {
|
|
|
63
63
|
|
|
64
64
|
onFinally(err, rawTrailers) {
|
|
65
65
|
if (this.statusCode) {
|
|
66
|
+
if (this.decoder != null) {
|
|
67
|
+
this.body += this.decoder.decode(undefined, { stream: false })
|
|
68
|
+
if (this.contentType === 'application/json') {
|
|
69
|
+
this.body = JSON.parse(this.body)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
66
73
|
const stackTraceLimit = Error.stackTraceLimit
|
|
67
74
|
Error.stackTraceLimit = 0
|
|
68
75
|
try {
|
|
69
|
-
if (this.decoder != null) {
|
|
70
|
-
this.body += this.decoder.decode(undefined, { stream: false })
|
|
71
|
-
if (this.contentType === 'application/json') {
|
|
72
|
-
this.body = JSON.parse(this.body)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
76
|
this.handler.onError(
|
|
76
77
|
createHttpError(this.statusCode, { headers: this.headers, body: this.body }),
|
|
77
78
|
)
|
|
78
79
|
} finally {
|
|
79
80
|
Error.stackTraceLimit = stackTraceLimit
|
|
80
81
|
}
|
|
82
|
+
|
|
83
|
+
this.decoder = null
|
|
84
|
+
this.contentType = null
|
|
85
|
+
this.body = null
|
|
81
86
|
} else if (err) {
|
|
82
87
|
this.handler.onError(err)
|
|
83
88
|
} else {
|
|
84
89
|
this.handler.onComplete(rawTrailers)
|
|
85
90
|
}
|
|
91
|
+
|
|
92
|
+
this.handler = null
|
|
86
93
|
}
|
|
87
94
|
}
|
|
88
95
|
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import { parseContentRange, isDisturbed, findHeader, retry as retryFn } from '../utils.js'
|
|
3
|
+
|
|
4
|
+
class Handler {
|
|
5
|
+
constructor(opts, { dispatch, handler }) {
|
|
6
|
+
this.dispatch = dispatch
|
|
7
|
+
this.handler = handler
|
|
8
|
+
this.opts = opts
|
|
9
|
+
|
|
10
|
+
this.count = 0
|
|
11
|
+
this.pos = 0
|
|
12
|
+
this.end = null
|
|
13
|
+
this.error = null
|
|
14
|
+
this.etag = null
|
|
15
|
+
|
|
16
|
+
this.reason = null
|
|
17
|
+
this.aborted = false
|
|
18
|
+
|
|
19
|
+
this.handler.onConnect((reason) => {
|
|
20
|
+
this.aborted = true
|
|
21
|
+
if (this.abort) {
|
|
22
|
+
this.abort(reason)
|
|
23
|
+
} else {
|
|
24
|
+
this.reason = reason
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
onConnect(abort) {
|
|
30
|
+
if (this.aborted) {
|
|
31
|
+
abort(this.reason)
|
|
32
|
+
} else {
|
|
33
|
+
this.abort = abort
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
onUpgrade(statusCode, rawHeaders, socket, headers) {
|
|
38
|
+
return this.handler.onUpgrade(statusCode, rawHeaders, socket, headers)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
onBodySent(chunk) {
|
|
42
|
+
return this.handler.onBodySent(chunk)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
onRequestSent() {
|
|
46
|
+
return this.handler.onRequestSent()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
onHeaders(statusCode, rawHeaders, resume, statusMessage, headers) {
|
|
50
|
+
const etag = findHeader(rawHeaders, 'etag')
|
|
51
|
+
|
|
52
|
+
if (this.resume) {
|
|
53
|
+
this.resume = null
|
|
54
|
+
|
|
55
|
+
// TODO (fix): Support other statusCode with skip?
|
|
56
|
+
if (statusCode !== 206) {
|
|
57
|
+
throw this.error
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// TODO (fix): strict vs weak etag?
|
|
61
|
+
if (this.etag == null || this.etag !== etag) {
|
|
62
|
+
throw this.error
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const contentRange = parseContentRange(findHeader(rawHeaders, 'content-range'))
|
|
66
|
+
if (!contentRange) {
|
|
67
|
+
throw this.error
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { start, size, end = size } = contentRange
|
|
71
|
+
|
|
72
|
+
assert(this.pos === start, 'content-range mismatch')
|
|
73
|
+
assert(this.end == null || this.end === end, 'content-range mismatch')
|
|
74
|
+
|
|
75
|
+
this.resume = resume
|
|
76
|
+
return true
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (this.end == null) {
|
|
80
|
+
if (statusCode === 206) {
|
|
81
|
+
const contentRange = parseContentRange(findHeader(rawHeaders, 'content-range'))
|
|
82
|
+
if (!contentRange) {
|
|
83
|
+
return this.handler.onHeaders(
|
|
84
|
+
statusCode,
|
|
85
|
+
rawHeaders,
|
|
86
|
+
() => this.resume(),
|
|
87
|
+
statusMessage,
|
|
88
|
+
headers,
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const { start, size, end = size } = contentRange
|
|
93
|
+
|
|
94
|
+
this.end = end
|
|
95
|
+
this.pos = Number(start)
|
|
96
|
+
} else {
|
|
97
|
+
const contentLength = findHeader(rawHeaders, 'content-length')
|
|
98
|
+
if (contentLength) {
|
|
99
|
+
this.end = Number(contentLength)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
assert(Number.isFinite(this.pos))
|
|
104
|
+
assert(this.end == null || Number.isFinite(this.end), 'invalid content-length')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
this.etag = etag
|
|
108
|
+
this.resume = resume
|
|
109
|
+
|
|
110
|
+
return this.handler.onHeaders(
|
|
111
|
+
statusCode,
|
|
112
|
+
rawHeaders,
|
|
113
|
+
() => this.resume(),
|
|
114
|
+
statusMessage,
|
|
115
|
+
headers,
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
onData(chunk) {
|
|
120
|
+
this.pos += chunk.length
|
|
121
|
+
this.count = 0
|
|
122
|
+
return this.handler.onData(chunk)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
onComplete(rawTrailers) {
|
|
126
|
+
return this.handler.onComplete(rawTrailers)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
onError(err) {
|
|
130
|
+
if (this.aborted || !this.etag || isDisturbed(this.opts.body)) {
|
|
131
|
+
return this.handler.onError(err)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const retryPromise = retryFn(err, this.count++, this.opts)
|
|
135
|
+
if (retryPromise == null) {
|
|
136
|
+
return this.handler.onError(err)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.error = err
|
|
140
|
+
this.opts = {
|
|
141
|
+
...this.opts,
|
|
142
|
+
headers: {
|
|
143
|
+
...this.opts.headers,
|
|
144
|
+
'if-match': this.etag,
|
|
145
|
+
range: `bytes=${this.pos}-${this.end ?? ''}`,
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.opts.logger?.debug('retrying response body')
|
|
150
|
+
|
|
151
|
+
retryPromise
|
|
152
|
+
.then(() => {
|
|
153
|
+
if (!this.aborted) {
|
|
154
|
+
this.dispatch(this.opts, this)
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
.catch((err) => {
|
|
158
|
+
if (!this.aborted) {
|
|
159
|
+
this.handler.onError(err)
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export default (dispatch) => (opts, handler) => {
|
|
166
|
+
return opts.idempotent && opts.retry && opts.method === 'GET' && !opts.upgrade
|
|
167
|
+
? dispatch(opts, new Handler(opts, { handler, dispatch }))
|
|
168
|
+
: dispatch(opts, handler)
|
|
169
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { parseContentRange, isDisturbed, findHeader, retry as retryFn } from '../utils.js'
|
|
1
|
+
import { isDisturbed, retry as retryFn } from '../utils.js'
|
|
3
2
|
|
|
4
3
|
class Handler {
|
|
5
4
|
constructor(opts, { dispatch, handler }) {
|
|
@@ -8,24 +7,31 @@ class Handler {
|
|
|
8
7
|
this.opts = opts
|
|
9
8
|
|
|
10
9
|
this.hasBody = false
|
|
11
|
-
this.aborted = false
|
|
12
10
|
this.count = 0
|
|
13
|
-
this.pos = 0
|
|
14
|
-
this.end = null
|
|
15
|
-
this.error = null
|
|
16
|
-
this.etag = null
|
|
17
|
-
}
|
|
18
11
|
|
|
19
|
-
|
|
12
|
+
this.reason = null
|
|
20
13
|
this.aborted = false
|
|
21
|
-
|
|
14
|
+
|
|
15
|
+
this.handler.onConnect((reason) => {
|
|
22
16
|
this.aborted = true
|
|
23
|
-
abort
|
|
17
|
+
if (this.abort) {
|
|
18
|
+
this.abort(reason)
|
|
19
|
+
} else {
|
|
20
|
+
this.reason = reason
|
|
21
|
+
}
|
|
24
22
|
})
|
|
25
23
|
}
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
onConnect(abort) {
|
|
26
|
+
if (this.aborted) {
|
|
27
|
+
abort(this.reason)
|
|
28
|
+
} else {
|
|
29
|
+
this.abort = abort
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
onUpgrade(statusCode, rawHeaders, socket, headers) {
|
|
34
|
+
return this.handler.onUpgrade(statusCode, rawHeaders, socket, headers)
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
onBodySent(chunk) {
|
|
@@ -37,80 +43,11 @@ class Handler {
|
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
onHeaders(statusCode, rawHeaders, resume, statusMessage, headers) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (this.resume) {
|
|
43
|
-
this.resume = null
|
|
44
|
-
|
|
45
|
-
// TODO (fix): Support other statusCode with skip?
|
|
46
|
-
if (statusCode !== 206) {
|
|
47
|
-
throw this.error
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// TODO (fix): strict vs weak etag?
|
|
51
|
-
if (this.etag == null || this.etag !== etag) {
|
|
52
|
-
throw this.error
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const contentRange = parseContentRange(findHeader(rawHeaders, 'content-range'))
|
|
56
|
-
if (!contentRange) {
|
|
57
|
-
throw this.error
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const { start, size, end = size } = contentRange
|
|
61
|
-
|
|
62
|
-
assert(this.pos === start, 'content-range mismatch')
|
|
63
|
-
assert(this.end == null || this.end === end, 'content-range mismatch')
|
|
64
|
-
|
|
65
|
-
this.resume = resume
|
|
66
|
-
return true
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (this.end == null) {
|
|
70
|
-
if (statusCode === 206) {
|
|
71
|
-
const contentRange = parseContentRange(findHeader(rawHeaders, 'content-range'))
|
|
72
|
-
if (!contentRange) {
|
|
73
|
-
return this.handler.onHeaders(
|
|
74
|
-
statusCode,
|
|
75
|
-
rawHeaders,
|
|
76
|
-
() => this.resume(),
|
|
77
|
-
statusMessage,
|
|
78
|
-
headers,
|
|
79
|
-
)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const { start, size, end = size } = contentRange
|
|
83
|
-
|
|
84
|
-
this.end = end
|
|
85
|
-
this.pos = Number(start)
|
|
86
|
-
} else {
|
|
87
|
-
const contentLength = findHeader(rawHeaders, 'content-length')
|
|
88
|
-
if (contentLength) {
|
|
89
|
-
this.end = Number(contentLength)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
assert(Number.isFinite(this.pos))
|
|
94
|
-
assert(this.end == null || Number.isFinite(this.end), 'invalid content-length')
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
this.etag = etag
|
|
98
|
-
this.resume = resume
|
|
99
|
-
|
|
100
|
-
return this.handler.onHeaders(
|
|
101
|
-
statusCode,
|
|
102
|
-
rawHeaders,
|
|
103
|
-
() => this.resume(),
|
|
104
|
-
statusMessage,
|
|
105
|
-
headers,
|
|
106
|
-
)
|
|
46
|
+
return this.handler.onHeaders(statusCode, rawHeaders, resume, statusMessage, headers)
|
|
107
47
|
}
|
|
108
48
|
|
|
109
49
|
onData(chunk) {
|
|
110
|
-
this.pos += chunk.length
|
|
111
|
-
this.count = 0
|
|
112
50
|
this.hasBody = true
|
|
113
|
-
|
|
114
51
|
return this.handler.onData(chunk)
|
|
115
52
|
}
|
|
116
53
|
|
|
@@ -119,7 +56,7 @@ class Handler {
|
|
|
119
56
|
}
|
|
120
57
|
|
|
121
58
|
onError(err) {
|
|
122
|
-
if (this.aborted ||
|
|
59
|
+
if (this.aborted || this.hasBody || isDisturbed(this.opts.body)) {
|
|
123
60
|
return this.handler.onError(err)
|
|
124
61
|
}
|
|
125
62
|
|
|
@@ -128,20 +65,7 @@ class Handler {
|
|
|
128
65
|
return this.handler.onError(err)
|
|
129
66
|
}
|
|
130
67
|
|
|
131
|
-
this.
|
|
132
|
-
|
|
133
|
-
if (this.hasBody) {
|
|
134
|
-
this.opts = {
|
|
135
|
-
...this.opts,
|
|
136
|
-
headers: {
|
|
137
|
-
...this.opts.headers,
|
|
138
|
-
'if-match': this.etag,
|
|
139
|
-
range: `bytes=${this.pos}-${this.end ?? ''}`,
|
|
140
|
-
},
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
this.opts.logger?.debug('retrying response body')
|
|
68
|
+
this.opts.logger?.debug('retrying response')
|
|
145
69
|
|
|
146
70
|
retryPromise
|
|
147
71
|
.then(() => {
|
|
@@ -158,7 +82,7 @@ class Handler {
|
|
|
158
82
|
}
|
|
159
83
|
|
|
160
84
|
export default (dispatch) => (opts, handler) => {
|
|
161
|
-
return opts.
|
|
85
|
+
return opts.retry
|
|
162
86
|
? dispatch(opts, new Handler(opts, { handler, dispatch }))
|
|
163
87
|
: dispatch(opts, handler)
|
|
164
88
|
}
|
package/lib/utils.js
CHANGED
|
@@ -60,13 +60,28 @@ export function parseContentRange(range) {
|
|
|
60
60
|
return { start, end: end ? end + 1 : size, size }
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
export function findHeader(
|
|
63
|
+
export function findHeader(headers, name) {
|
|
64
64
|
const len = name.length
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
if (Array.isArray(headers)) {
|
|
67
|
+
for (let i = 0; i < headers.length; i += 2) {
|
|
68
|
+
const key = headers[i + 0]
|
|
69
|
+
if (key.length === len && toLowerCase(key.toString()) === name) {
|
|
70
|
+
return headers[i + 1].toString()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} else if (headers != null) {
|
|
74
|
+
{
|
|
75
|
+
const val = headers[name]
|
|
76
|
+
if (val !== undefined) {
|
|
77
|
+
return val
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const key of Object.keys(headers)) {
|
|
82
|
+
if (key.length === len && toLowerCase(key) === name) {
|
|
83
|
+
return headers[key].toString()
|
|
84
|
+
}
|
|
70
85
|
}
|
|
71
86
|
}
|
|
72
87
|
|