undici 7.0.0-alpha.1 → 7.0.0-alpha.10
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 +24 -38
- package/docs/docs/api/Agent.md +14 -14
- package/docs/docs/api/BalancedPool.md +16 -16
- package/docs/docs/api/CacheStore.md +131 -0
- package/docs/docs/api/Client.md +12 -12
- package/docs/docs/api/Debug.md +1 -1
- package/docs/docs/api/Dispatcher.md +98 -193
- package/docs/docs/api/EnvHttpProxyAgent.md +12 -12
- package/docs/docs/api/MockAgent.md +5 -3
- package/docs/docs/api/MockClient.md +5 -5
- package/docs/docs/api/MockPool.md +4 -3
- package/docs/docs/api/Pool.md +15 -15
- package/docs/docs/api/PoolStats.md +1 -1
- package/docs/docs/api/ProxyAgent.md +3 -3
- package/docs/docs/api/RedirectHandler.md +1 -1
- package/docs/docs/api/RetryAgent.md +1 -1
- package/docs/docs/api/RetryHandler.md +4 -4
- package/docs/docs/api/WebSocket.md +46 -4
- package/docs/docs/api/api-lifecycle.md +11 -11
- package/docs/docs/best-practices/mocking-request.md +2 -2
- package/docs/docs/best-practices/proxy.md +1 -1
- package/index.d.ts +1 -1
- package/index.js +23 -3
- package/lib/api/abort-signal.js +2 -0
- package/lib/api/api-pipeline.js +4 -2
- package/lib/api/api-request.js +6 -4
- package/lib/api/api-stream.js +3 -1
- package/lib/api/api-upgrade.js +2 -2
- package/lib/api/readable.js +200 -47
- package/lib/api/util.js +2 -0
- package/lib/cache/memory-cache-store.js +177 -0
- package/lib/cache/sqlite-cache-store.js +446 -0
- package/lib/core/connect.js +54 -22
- package/lib/core/constants.js +35 -10
- package/lib/core/diagnostics.js +122 -128
- package/lib/core/errors.js +2 -2
- package/lib/core/request.js +6 -6
- package/lib/core/symbols.js +2 -0
- package/lib/core/tree.js +4 -2
- package/lib/core/util.js +238 -40
- package/lib/dispatcher/client-h1.js +405 -142
- package/lib/dispatcher/client-h2.js +212 -109
- package/lib/dispatcher/client.js +24 -7
- package/lib/dispatcher/dispatcher-base.js +4 -1
- package/lib/dispatcher/dispatcher.js +4 -0
- package/lib/dispatcher/fixed-queue.js +91 -49
- package/lib/dispatcher/pool-base.js +3 -3
- package/lib/dispatcher/pool-stats.js +2 -0
- package/lib/dispatcher/proxy-agent.js +3 -1
- package/lib/handler/cache-handler.js +393 -0
- package/lib/handler/cache-revalidation-handler.js +124 -0
- package/lib/handler/decorator-handler.js +3 -0
- package/lib/handler/redirect-handler.js +45 -59
- package/lib/handler/retry-handler.js +68 -109
- package/lib/handler/unwrap-handler.js +96 -0
- package/lib/handler/wrap-handler.js +98 -0
- package/lib/interceptor/cache.js +350 -0
- package/lib/interceptor/dns.js +375 -0
- package/lib/interceptor/response-error.js +15 -7
- package/lib/mock/mock-agent.js +5 -8
- package/lib/mock/mock-client.js +7 -2
- package/lib/mock/mock-errors.js +3 -1
- package/lib/mock/mock-interceptor.js +8 -6
- package/lib/mock/mock-pool.js +7 -2
- package/lib/mock/mock-symbols.js +2 -1
- package/lib/mock/mock-utils.js +33 -5
- package/lib/util/cache.js +360 -0
- package/lib/util/timers.js +50 -6
- package/lib/web/cache/cache.js +25 -21
- package/lib/web/cache/cachestorage.js +3 -1
- package/lib/web/cookies/index.js +18 -5
- package/lib/web/cookies/parse.js +6 -1
- package/lib/web/eventsource/eventsource.js +2 -0
- package/lib/web/fetch/body.js +43 -39
- package/lib/web/fetch/constants.js +45 -29
- package/lib/web/fetch/data-url.js +2 -2
- package/lib/web/fetch/formdata-parser.js +84 -46
- package/lib/web/fetch/formdata.js +42 -20
- package/lib/web/fetch/headers.js +119 -85
- package/lib/web/fetch/index.js +69 -65
- package/lib/web/fetch/request.js +132 -55
- package/lib/web/fetch/response.js +81 -36
- package/lib/web/fetch/util.js +274 -103
- package/lib/web/fetch/webidl.js +54 -18
- package/lib/web/websocket/connection.js +92 -15
- package/lib/web/websocket/constants.js +69 -9
- package/lib/web/websocket/events.js +8 -2
- package/lib/web/websocket/receiver.js +20 -26
- package/lib/web/websocket/stream/websocketerror.js +83 -0
- package/lib/web/websocket/stream/websocketstream.js +485 -0
- package/lib/web/websocket/util.js +115 -10
- package/lib/web/websocket/websocket.js +47 -170
- package/package.json +15 -11
- package/types/agent.d.ts +1 -1
- package/types/cache-interceptor.d.ts +172 -0
- package/types/cookies.d.ts +2 -0
- package/types/dispatcher.d.ts +29 -4
- package/types/env-http-proxy-agent.d.ts +1 -1
- package/types/fetch.d.ts +9 -8
- package/types/handlers.d.ts +4 -4
- package/types/index.d.ts +3 -1
- package/types/interceptors.d.ts +18 -1
- package/types/mock-agent.d.ts +4 -1
- package/types/mock-client.d.ts +1 -1
- package/types/mock-pool.d.ts +1 -1
- package/types/proxy-agent.d.ts +1 -1
- package/types/readable.d.ts +10 -7
- package/types/retry-handler.d.ts +3 -3
- package/types/webidl.d.ts +30 -4
- package/types/websocket.d.ts +33 -0
- package/lib/mock/pluralizer.js +0 -29
- package/lib/web/cache/symbols.js +0 -5
- package/lib/web/fetch/symbols.js +0 -8
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const assert = require('node:assert')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This takes care of revalidation requests we send to the origin. If we get
|
|
7
|
+
* a response indicating that what we have is cached (via a HTTP 304), we can
|
|
8
|
+
* continue using the cached value. Otherwise, we'll receive the new response
|
|
9
|
+
* here, which we then just pass on to the next handler (most likely a
|
|
10
|
+
* CacheHandler). Note that this assumes the proper headers were already
|
|
11
|
+
* included in the request to tell the origin that we want to revalidate the
|
|
12
|
+
* response (i.e. if-modified-since).
|
|
13
|
+
*
|
|
14
|
+
* @see https://www.rfc-editor.org/rfc/rfc9111.html#name-validation
|
|
15
|
+
*
|
|
16
|
+
* @implements {import('../../types/dispatcher.d.ts').default.DispatchHandler}
|
|
17
|
+
*/
|
|
18
|
+
class CacheRevalidationHandler {
|
|
19
|
+
#successful = false
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @type {((boolean, any) => void) | null}
|
|
23
|
+
*/
|
|
24
|
+
#callback
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @type {(import('../../types/dispatcher.d.ts').default.DispatchHandler)}
|
|
28
|
+
*/
|
|
29
|
+
#handler
|
|
30
|
+
|
|
31
|
+
#context
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @type {boolean}
|
|
35
|
+
*/
|
|
36
|
+
#allowErrorStatusCodes
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {(boolean) => void} callback Function to call if the cached value is valid
|
|
40
|
+
* @param {import('../../types/dispatcher.d.ts').default.DispatchHandlers} handler
|
|
41
|
+
* @param {boolean} allowErrorStatusCodes
|
|
42
|
+
*/
|
|
43
|
+
constructor (callback, handler, allowErrorStatusCodes) {
|
|
44
|
+
if (typeof callback !== 'function') {
|
|
45
|
+
throw new TypeError('callback must be a function')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.#callback = callback
|
|
49
|
+
this.#handler = handler
|
|
50
|
+
this.#allowErrorStatusCodes = allowErrorStatusCodes
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
onRequestStart (_, context) {
|
|
54
|
+
this.#successful = false
|
|
55
|
+
this.#context = context
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
onRequestUpgrade (controller, statusCode, headers, socket) {
|
|
59
|
+
this.#handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
onResponseStart (
|
|
63
|
+
controller,
|
|
64
|
+
statusCode,
|
|
65
|
+
headers,
|
|
66
|
+
statusMessage
|
|
67
|
+
) {
|
|
68
|
+
assert(this.#callback != null)
|
|
69
|
+
|
|
70
|
+
// https://www.rfc-editor.org/rfc/rfc9111.html#name-handling-a-validation-respo
|
|
71
|
+
// https://datatracker.ietf.org/doc/html/rfc5861#section-4
|
|
72
|
+
this.#successful = statusCode === 304 ||
|
|
73
|
+
(this.#allowErrorStatusCodes && statusCode >= 500 && statusCode <= 504)
|
|
74
|
+
this.#callback(this.#successful, this.#context)
|
|
75
|
+
this.#callback = null
|
|
76
|
+
|
|
77
|
+
if (this.#successful) {
|
|
78
|
+
return true
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.#handler.onRequestStart?.(controller, this.#context)
|
|
82
|
+
this.#handler.onResponseStart?.(
|
|
83
|
+
controller,
|
|
84
|
+
statusCode,
|
|
85
|
+
headers,
|
|
86
|
+
statusMessage
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
onResponseData (controller, chunk) {
|
|
91
|
+
if (this.#successful) {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return this.#handler.onResponseData?.(controller, chunk)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
onResponseEnd (controller, trailers) {
|
|
99
|
+
if (this.#successful) {
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.#handler.onResponseEnd?.(controller, trailers)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
onResponseError (controller, err) {
|
|
107
|
+
if (this.#successful) {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (this.#callback) {
|
|
112
|
+
this.#callback(false)
|
|
113
|
+
this.#callback = null
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (typeof this.#handler.onResponseError === 'function') {
|
|
117
|
+
this.#handler.onResponseError(controller, err)
|
|
118
|
+
} else {
|
|
119
|
+
throw err
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = CacheRevalidationHandler
|
|
@@ -10,6 +10,8 @@ const redirectableStatusCodes = [300, 301, 302, 303, 307, 308]
|
|
|
10
10
|
|
|
11
11
|
const kBody = Symbol('body')
|
|
12
12
|
|
|
13
|
+
const noop = () => {}
|
|
14
|
+
|
|
13
15
|
class BodyAsyncIterable {
|
|
14
16
|
constructor (body) {
|
|
15
17
|
this[kBody] = body
|
|
@@ -38,16 +40,12 @@ class RedirectHandler {
|
|
|
38
40
|
throw new InvalidArgumentError('maxRedirections must be a positive number')
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
util.validateHandler(handler, opts.method, opts.upgrade)
|
|
42
|
-
|
|
43
43
|
this.dispatch = dispatch
|
|
44
44
|
this.location = null
|
|
45
|
-
this.abort = null
|
|
46
45
|
this.opts = { ...opts, maxRedirections: 0 } // opts must be a copy
|
|
47
46
|
this.maxRedirections = maxRedirections
|
|
48
47
|
this.handler = handler
|
|
49
48
|
this.history = []
|
|
50
|
-
this.redirectionLimitReached = false
|
|
51
49
|
|
|
52
50
|
if (util.isStream(this.opts.body)) {
|
|
53
51
|
// TODO (fix): Provide some way for the user to cache the file to e.g. /tmp
|
|
@@ -75,7 +73,8 @@ class RedirectHandler {
|
|
|
75
73
|
this.opts.body &&
|
|
76
74
|
typeof this.opts.body !== 'string' &&
|
|
77
75
|
!ArrayBuffer.isView(this.opts.body) &&
|
|
78
|
-
util.isIterable(this.opts.body)
|
|
76
|
+
util.isIterable(this.opts.body) &&
|
|
77
|
+
!util.isFormDataLike(this.opts.body)
|
|
79
78
|
) {
|
|
80
79
|
// TODO: Should we allow re-using iterable if !this.opts.idempotent
|
|
81
80
|
// or through some other flag?
|
|
@@ -83,40 +82,51 @@ class RedirectHandler {
|
|
|
83
82
|
}
|
|
84
83
|
}
|
|
85
84
|
|
|
86
|
-
|
|
87
|
-
this.
|
|
88
|
-
this.handler.onConnect(abort, { history: this.history })
|
|
85
|
+
onRequestStart (controller, context) {
|
|
86
|
+
this.handler.onRequestStart?.(controller, { ...context, history: this.history })
|
|
89
87
|
}
|
|
90
88
|
|
|
91
|
-
|
|
92
|
-
this.handler.
|
|
89
|
+
onRequestUpgrade (controller, statusCode, headers, socket) {
|
|
90
|
+
this.handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
|
|
93
91
|
}
|
|
94
92
|
|
|
95
|
-
|
|
96
|
-
this.handler.onError(error)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
onHeaders (statusCode, headers, resume, statusText) {
|
|
100
|
-
this.location = this.history.length >= this.maxRedirections || util.isDisturbed(this.opts.body)
|
|
101
|
-
? null
|
|
102
|
-
: parseLocation(statusCode, headers)
|
|
103
|
-
|
|
93
|
+
onResponseStart (controller, statusCode, headers, statusMessage) {
|
|
104
94
|
if (this.opts.throwOnMaxRedirect && this.history.length >= this.maxRedirections) {
|
|
105
|
-
|
|
106
|
-
|
|
95
|
+
throw new Error('max redirects')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// https://tools.ietf.org/html/rfc7231#section-6.4.2
|
|
99
|
+
// https://fetch.spec.whatwg.org/#http-redirect-fetch
|
|
100
|
+
// In case of HTTP 301 or 302 with POST, change the method to GET
|
|
101
|
+
if ((statusCode === 301 || statusCode === 302) && this.opts.method === 'POST') {
|
|
102
|
+
this.opts.method = 'GET'
|
|
103
|
+
if (util.isStream(this.opts.body)) {
|
|
104
|
+
util.destroy(this.opts.body.on('error', noop))
|
|
107
105
|
}
|
|
106
|
+
this.opts.body = null
|
|
107
|
+
}
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
// https://tools.ietf.org/html/rfc7231#section-6.4.4
|
|
110
|
+
// In case of HTTP 303, always replace method to be either HEAD or GET
|
|
111
|
+
if (statusCode === 303 && this.opts.method !== 'HEAD') {
|
|
112
|
+
this.opts.method = 'GET'
|
|
113
|
+
if (util.isStream(this.opts.body)) {
|
|
114
|
+
util.destroy(this.opts.body.on('error', noop))
|
|
115
|
+
}
|
|
116
|
+
this.opts.body = null
|
|
112
117
|
}
|
|
113
118
|
|
|
119
|
+
this.location = this.history.length >= this.maxRedirections || util.isDisturbed(this.opts.body) || redirectableStatusCodes.indexOf(statusCode) === -1
|
|
120
|
+
? null
|
|
121
|
+
: headers.location
|
|
122
|
+
|
|
114
123
|
if (this.opts.origin) {
|
|
115
124
|
this.history.push(new URL(this.opts.path, this.opts.origin))
|
|
116
125
|
}
|
|
117
126
|
|
|
118
127
|
if (!this.location) {
|
|
119
|
-
|
|
128
|
+
this.handler.onResponseStart?.(controller, statusCode, headers, statusMessage)
|
|
129
|
+
return
|
|
120
130
|
}
|
|
121
131
|
|
|
122
132
|
const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)))
|
|
@@ -130,23 +140,16 @@ class RedirectHandler {
|
|
|
130
140
|
this.opts.origin = origin
|
|
131
141
|
this.opts.maxRedirections = 0
|
|
132
142
|
this.opts.query = null
|
|
133
|
-
|
|
134
|
-
// https://tools.ietf.org/html/rfc7231#section-6.4.4
|
|
135
|
-
// In case of HTTP 303, always replace method to be either HEAD or GET
|
|
136
|
-
if (statusCode === 303 && this.opts.method !== 'HEAD') {
|
|
137
|
-
this.opts.method = 'GET'
|
|
138
|
-
this.opts.body = null
|
|
139
|
-
}
|
|
140
143
|
}
|
|
141
144
|
|
|
142
|
-
|
|
145
|
+
onResponseData (controller, chunk) {
|
|
143
146
|
if (this.location) {
|
|
144
147
|
/*
|
|
145
148
|
https://tools.ietf.org/html/rfc7231#section-6.4
|
|
146
149
|
|
|
147
150
|
TLDR: undici always ignores 3xx response bodies.
|
|
148
151
|
|
|
149
|
-
Redirection is used to serve the requested resource from another URL, so it
|
|
152
|
+
Redirection is used to serve the requested resource from another URL, so it assumes that
|
|
150
153
|
no body is generated (and thus can be ignored). Even though generating a body is not prohibited.
|
|
151
154
|
|
|
152
155
|
For status 301, 302, 303, 307 and 308 (the latter from RFC 7238), the specs mention that the body usually
|
|
@@ -159,11 +162,11 @@ class RedirectHandler {
|
|
|
159
162
|
servers and browsers implementors, we ignore the body as there is no specified way to eventually parse it.
|
|
160
163
|
*/
|
|
161
164
|
} else {
|
|
162
|
-
|
|
165
|
+
this.handler.onResponseData?.(controller, chunk)
|
|
163
166
|
}
|
|
164
167
|
}
|
|
165
168
|
|
|
166
|
-
|
|
169
|
+
onResponseEnd (controller, trailers) {
|
|
167
170
|
if (this.location) {
|
|
168
171
|
/*
|
|
169
172
|
https://tools.ietf.org/html/rfc7231#section-6.4
|
|
@@ -173,32 +176,14 @@ class RedirectHandler {
|
|
|
173
176
|
|
|
174
177
|
See comment on onData method above for more detailed information.
|
|
175
178
|
*/
|
|
176
|
-
|
|
177
|
-
this.location = null
|
|
178
|
-
this.abort = null
|
|
179
|
-
|
|
180
179
|
this.dispatch(this.opts, this)
|
|
181
180
|
} else {
|
|
182
|
-
this.handler.
|
|
181
|
+
this.handler.onResponseEnd(controller, trailers)
|
|
183
182
|
}
|
|
184
183
|
}
|
|
185
184
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
this.handler.onBodySent(chunk)
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function parseLocation (statusCode, headers) {
|
|
194
|
-
if (redirectableStatusCodes.indexOf(statusCode) === -1) {
|
|
195
|
-
return null
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
for (let i = 0; i < headers.length; i += 2) {
|
|
199
|
-
if (headers[i].length === 8 && util.headerNameToString(headers[i]) === 'location') {
|
|
200
|
-
return headers[i + 1]
|
|
201
|
-
}
|
|
185
|
+
onResponseError (controller, error) {
|
|
186
|
+
this.handler.onResponseError?.(controller, error)
|
|
202
187
|
}
|
|
203
188
|
}
|
|
204
189
|
|
|
@@ -227,9 +212,10 @@ function cleanRequestHeaders (headers, removeContent, unknownOrigin) {
|
|
|
227
212
|
}
|
|
228
213
|
}
|
|
229
214
|
} else if (headers && typeof headers === 'object') {
|
|
230
|
-
|
|
215
|
+
const entries = typeof headers[Symbol.iterator] === 'function' ? headers : Object.entries(headers)
|
|
216
|
+
for (const [key, value] of entries) {
|
|
231
217
|
if (!shouldRemoveHeader(key, removeContent, unknownOrigin)) {
|
|
232
|
-
ret.push(key,
|
|
218
|
+
ret.push(key, value)
|
|
233
219
|
}
|
|
234
220
|
}
|
|
235
221
|
} else {
|
|
@@ -3,9 +3,9 @@ const assert = require('node:assert')
|
|
|
3
3
|
|
|
4
4
|
const { kRetryHandlerDefaultRetry } = require('../core/symbols')
|
|
5
5
|
const { RequestRetryError } = require('../core/errors')
|
|
6
|
+
const WrapHandler = require('./wrap-handler')
|
|
6
7
|
const {
|
|
7
8
|
isDisturbed,
|
|
8
|
-
parseHeaders,
|
|
9
9
|
parseRangeHeader,
|
|
10
10
|
wrapRequestBody
|
|
11
11
|
} = require('../core/util')
|
|
@@ -16,7 +16,7 @@ function calculateRetryAfterHeader (retryAfter) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
class RetryHandler {
|
|
19
|
-
constructor (opts,
|
|
19
|
+
constructor (opts, { dispatch, handler }) {
|
|
20
20
|
const { retryOptions, ...dispatchOpts } = opts
|
|
21
21
|
const {
|
|
22
22
|
// Retry scoped
|
|
@@ -32,11 +32,9 @@ class RetryHandler {
|
|
|
32
32
|
statusCodes
|
|
33
33
|
} = retryOptions ?? {}
|
|
34
34
|
|
|
35
|
-
this.dispatch =
|
|
36
|
-
this.handler =
|
|
35
|
+
this.dispatch = dispatch
|
|
36
|
+
this.handler = WrapHandler.wrap(handler)
|
|
37
37
|
this.opts = { ...dispatchOpts, body: wrapRequestBody(opts.body) }
|
|
38
|
-
this.abort = null
|
|
39
|
-
this.aborted = false
|
|
40
38
|
this.retryOpts = {
|
|
41
39
|
retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry],
|
|
42
40
|
retryAfter: retryAfter ?? true,
|
|
@@ -64,44 +62,20 @@ class RetryHandler {
|
|
|
64
62
|
|
|
65
63
|
this.retryCount = 0
|
|
66
64
|
this.retryCountCheckpoint = 0
|
|
65
|
+
this.headersSent = false
|
|
67
66
|
this.start = 0
|
|
68
67
|
this.end = null
|
|
69
68
|
this.etag = null
|
|
70
|
-
this.resume = null
|
|
71
|
-
|
|
72
|
-
// Handle possible onConnect duplication
|
|
73
|
-
this.handler.onConnect(reason => {
|
|
74
|
-
this.aborted = true
|
|
75
|
-
if (this.abort) {
|
|
76
|
-
this.abort(reason)
|
|
77
|
-
} else {
|
|
78
|
-
this.reason = reason
|
|
79
|
-
}
|
|
80
|
-
})
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
onRequestSent () {
|
|
84
|
-
if (this.handler.onRequestSent) {
|
|
85
|
-
this.handler.onRequestSent()
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
onUpgrade (statusCode, headers, socket) {
|
|
90
|
-
if (this.handler.onUpgrade) {
|
|
91
|
-
this.handler.onUpgrade(statusCode, headers, socket)
|
|
92
|
-
}
|
|
93
69
|
}
|
|
94
70
|
|
|
95
|
-
|
|
96
|
-
if (this.
|
|
97
|
-
|
|
98
|
-
} else {
|
|
99
|
-
this.abort = abort
|
|
71
|
+
onRequestStart (controller, context) {
|
|
72
|
+
if (!this.headersSent) {
|
|
73
|
+
this.handler.onRequestStart?.(controller, context)
|
|
100
74
|
}
|
|
101
75
|
}
|
|
102
76
|
|
|
103
|
-
|
|
104
|
-
|
|
77
|
+
onRequestUpgrade (controller, statusCode, headers, socket) {
|
|
78
|
+
this.handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
|
|
105
79
|
}
|
|
106
80
|
|
|
107
81
|
static [kRetryHandlerDefaultRetry] (err, { state, opts }, cb) {
|
|
@@ -159,83 +133,68 @@ class RetryHandler {
|
|
|
159
133
|
? Math.min(retryAfterHeader, maxTimeout)
|
|
160
134
|
: Math.min(minTimeout * timeoutFactor ** (counter - 1), maxTimeout)
|
|
161
135
|
|
|
162
|
-
setTimeout(() => cb(null), retryTimeout)
|
|
136
|
+
setTimeout(() => cb(null), retryTimeout).unref()
|
|
163
137
|
}
|
|
164
138
|
|
|
165
|
-
|
|
166
|
-
const headers = parseHeaders(rawHeaders)
|
|
167
|
-
|
|
139
|
+
onResponseStart (controller, statusCode, headers, statusMessage) {
|
|
168
140
|
this.retryCount += 1
|
|
169
141
|
|
|
170
142
|
if (statusCode >= 300) {
|
|
171
143
|
if (this.retryOpts.statusCodes.includes(statusCode) === false) {
|
|
172
|
-
|
|
144
|
+
this.headersSent = true
|
|
145
|
+
this.handler.onResponseStart?.(
|
|
146
|
+
controller,
|
|
173
147
|
statusCode,
|
|
174
|
-
|
|
175
|
-
resume,
|
|
148
|
+
headers,
|
|
176
149
|
statusMessage
|
|
177
150
|
)
|
|
151
|
+
return
|
|
178
152
|
} else {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
})
|
|
186
|
-
)
|
|
187
|
-
return false
|
|
153
|
+
throw new RequestRetryError('Request failed', statusCode, {
|
|
154
|
+
headers,
|
|
155
|
+
data: {
|
|
156
|
+
count: this.retryCount
|
|
157
|
+
}
|
|
158
|
+
})
|
|
188
159
|
}
|
|
189
160
|
}
|
|
190
161
|
|
|
191
162
|
// Checkpoint for resume from where we left it
|
|
192
|
-
if (this.
|
|
193
|
-
this.resume = null
|
|
194
|
-
|
|
163
|
+
if (this.headersSent) {
|
|
195
164
|
// Only Partial Content 206 supposed to provide Content-Range,
|
|
196
165
|
// any other status code that partially consumed the payload
|
|
197
|
-
// should not be
|
|
198
|
-
// wrongly
|
|
166
|
+
// should not be retried because it would result in downstream
|
|
167
|
+
// wrongly concatenate multiple responses.
|
|
199
168
|
if (statusCode !== 206 && (this.start > 0 || statusCode !== 200)) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
})
|
|
205
|
-
)
|
|
206
|
-
return false
|
|
169
|
+
throw new RequestRetryError('server does not support the range header and the payload was partially consumed', statusCode, {
|
|
170
|
+
headers,
|
|
171
|
+
data: { count: this.retryCount }
|
|
172
|
+
})
|
|
207
173
|
}
|
|
208
174
|
|
|
209
175
|
const contentRange = parseRangeHeader(headers['content-range'])
|
|
210
176
|
// If no content range
|
|
211
177
|
if (!contentRange) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
})
|
|
217
|
-
)
|
|
218
|
-
return false
|
|
178
|
+
throw new RequestRetryError('Content-Range mismatch', statusCode, {
|
|
179
|
+
headers,
|
|
180
|
+
data: { count: this.retryCount }
|
|
181
|
+
})
|
|
219
182
|
}
|
|
220
183
|
|
|
221
184
|
// Let's start with a weak etag check
|
|
222
185
|
if (this.etag != null && this.etag !== headers.etag) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
})
|
|
228
|
-
)
|
|
229
|
-
return false
|
|
186
|
+
throw new RequestRetryError('ETag mismatch', statusCode, {
|
|
187
|
+
headers,
|
|
188
|
+
data: { count: this.retryCount }
|
|
189
|
+
})
|
|
230
190
|
}
|
|
231
191
|
|
|
232
|
-
const { start, size, end = size } = contentRange
|
|
192
|
+
const { start, size, end = size ? size - 1 : null } = contentRange
|
|
233
193
|
|
|
234
194
|
assert(this.start === start, 'content-range mismatch')
|
|
235
195
|
assert(this.end == null || this.end === end, 'content-range mismatch')
|
|
236
196
|
|
|
237
|
-
|
|
238
|
-
return true
|
|
197
|
+
return
|
|
239
198
|
}
|
|
240
199
|
|
|
241
200
|
if (this.end == null) {
|
|
@@ -244,15 +203,17 @@ class RetryHandler {
|
|
|
244
203
|
const range = parseRangeHeader(headers['content-range'])
|
|
245
204
|
|
|
246
205
|
if (range == null) {
|
|
247
|
-
|
|
206
|
+
this.headersSent = true
|
|
207
|
+
this.handler.onResponseStart?.(
|
|
208
|
+
controller,
|
|
248
209
|
statusCode,
|
|
249
|
-
|
|
250
|
-
resume,
|
|
210
|
+
headers,
|
|
251
211
|
statusMessage
|
|
252
212
|
)
|
|
213
|
+
return
|
|
253
214
|
}
|
|
254
215
|
|
|
255
|
-
const { start, size, end = size } = range
|
|
216
|
+
const { start, size, end = size ? size - 1 : null } = range
|
|
256
217
|
assert(
|
|
257
218
|
start != null && Number.isFinite(start),
|
|
258
219
|
'content-range mismatch'
|
|
@@ -266,7 +227,7 @@ class RetryHandler {
|
|
|
266
227
|
// We make our best to checkpoint the body for further range headers
|
|
267
228
|
if (this.end == null) {
|
|
268
229
|
const contentLength = headers['content-length']
|
|
269
|
-
this.end = contentLength != null ? Number(contentLength) : null
|
|
230
|
+
this.end = contentLength != null ? Number(contentLength) - 1 : null
|
|
270
231
|
}
|
|
271
232
|
|
|
272
233
|
assert(Number.isFinite(this.start))
|
|
@@ -275,7 +236,7 @@ class RetryHandler {
|
|
|
275
236
|
'invalid content-length'
|
|
276
237
|
)
|
|
277
238
|
|
|
278
|
-
this.resume =
|
|
239
|
+
this.resume = true
|
|
279
240
|
this.etag = headers.etag != null ? headers.etag : null
|
|
280
241
|
|
|
281
242
|
// Weak etags are not useful for comparison nor cache
|
|
@@ -289,38 +250,36 @@ class RetryHandler {
|
|
|
289
250
|
this.etag = null
|
|
290
251
|
}
|
|
291
252
|
|
|
292
|
-
|
|
253
|
+
this.headersSent = true
|
|
254
|
+
this.handler.onResponseStart?.(
|
|
255
|
+
controller,
|
|
293
256
|
statusCode,
|
|
294
|
-
|
|
295
|
-
resume,
|
|
257
|
+
headers,
|
|
296
258
|
statusMessage
|
|
297
259
|
)
|
|
260
|
+
} else {
|
|
261
|
+
throw new RequestRetryError('Request failed', statusCode, {
|
|
262
|
+
headers,
|
|
263
|
+
data: { count: this.retryCount }
|
|
264
|
+
})
|
|
298
265
|
}
|
|
299
|
-
|
|
300
|
-
const err = new RequestRetryError('Request failed', statusCode, {
|
|
301
|
-
headers,
|
|
302
|
-
data: { count: this.retryCount }
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
this.abort(err)
|
|
306
|
-
|
|
307
|
-
return false
|
|
308
266
|
}
|
|
309
267
|
|
|
310
|
-
|
|
268
|
+
onResponseData (controller, chunk) {
|
|
311
269
|
this.start += chunk.length
|
|
312
270
|
|
|
313
|
-
|
|
271
|
+
this.handler.onResponseData?.(controller, chunk)
|
|
314
272
|
}
|
|
315
273
|
|
|
316
|
-
|
|
274
|
+
onResponseEnd (controller, trailers) {
|
|
317
275
|
this.retryCount = 0
|
|
318
|
-
return this.handler.
|
|
276
|
+
return this.handler.onResponseEnd?.(controller, trailers)
|
|
319
277
|
}
|
|
320
278
|
|
|
321
|
-
|
|
322
|
-
if (
|
|
323
|
-
|
|
279
|
+
onResponseError (controller, err) {
|
|
280
|
+
if (!controller || controller.aborted || isDisturbed(this.opts.body)) {
|
|
281
|
+
this.handler.onResponseError?.(controller, err)
|
|
282
|
+
return
|
|
324
283
|
}
|
|
325
284
|
|
|
326
285
|
// We reconcile in case of a mix between network errors
|
|
@@ -349,8 +308,8 @@ class RetryHandler {
|
|
|
349
308
|
* @returns
|
|
350
309
|
*/
|
|
351
310
|
function onRetry (err) {
|
|
352
|
-
if (err != null ||
|
|
353
|
-
return this.handler.
|
|
311
|
+
if (err != null || controller?.aborted || isDisturbed(this.opts.body)) {
|
|
312
|
+
return this.handler.onResponseError?.(controller, err)
|
|
354
313
|
}
|
|
355
314
|
|
|
356
315
|
if (this.start !== 0) {
|
|
@@ -374,7 +333,7 @@ class RetryHandler {
|
|
|
374
333
|
this.retryCountCheckpoint = this.retryCount
|
|
375
334
|
this.dispatch(this.opts, this)
|
|
376
335
|
} catch (err) {
|
|
377
|
-
this.handler.
|
|
336
|
+
this.handler.onResponseError?.(controller, err)
|
|
378
337
|
}
|
|
379
338
|
}
|
|
380
339
|
}
|