undici 7.10.0 → 7.12.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 +159 -0
- package/docs/docs/api/CacheStore.md +3 -3
- package/docs/docs/api/Debug.md +13 -13
- package/docs/docs/api/DiagnosticsChannel.md +32 -4
- package/docs/docs/api/Dispatcher.md +22 -3
- package/docs/docs/api/GlobalInstallation.md +91 -0
- package/docs/docs/api/MockClient.md +4 -0
- package/docs/docs/api/MockPool.md +6 -0
- package/docs/docs/api/ProxyAgent.md +2 -0
- package/docs/docs/api/RetryAgent.md +6 -1
- package/docs/docs/api/RetryHandler.md +1 -0
- package/docs/docs/api/WebSocket.md +27 -0
- package/index.js +18 -1
- package/lib/api/api-stream.js +1 -1
- package/lib/api/readable.js +1 -3
- package/lib/cache/memory-cache-store.js +3 -3
- package/lib/cache/sqlite-cache-store.js +1 -1
- package/lib/core/connect.js +21 -51
- package/lib/core/diagnostics.js +6 -4
- package/lib/core/request.js +12 -1
- package/lib/core/tree.js +1 -1
- package/lib/core/util.js +0 -45
- package/lib/dispatcher/client-h1.js +9 -18
- package/lib/dispatcher/proxy-agent.js +2 -1
- package/lib/handler/cache-handler.js +4 -1
- package/lib/handler/redirect-handler.js +2 -2
- package/lib/handler/retry-handler.js +110 -56
- package/lib/interceptor/cache.js +2 -2
- package/lib/interceptor/redirect.js +1 -1
- package/lib/mock/mock-client.js +4 -0
- package/lib/mock/mock-pool.js +4 -0
- package/lib/util/cache.js +12 -2
- package/lib/util/promise.js +28 -0
- package/lib/util/timers.js +11 -9
- package/lib/web/cache/cache.js +11 -9
- package/lib/web/cache/cachestorage.js +1 -1
- package/lib/web/cookies/index.js +1 -1
- package/lib/web/eventsource/eventsource.js +3 -6
- package/lib/web/eventsource/util.js +1 -1
- package/lib/web/fetch/body.js +36 -24
- package/lib/web/fetch/formdata-parser.js +4 -4
- package/lib/web/fetch/formdata.js +1 -1
- package/lib/web/fetch/headers.js +1 -1
- package/lib/web/fetch/index.js +228 -226
- package/lib/web/fetch/request.js +16 -8
- package/lib/web/fetch/response.js +6 -4
- package/lib/web/fetch/util.js +23 -25
- package/lib/web/{fetch/webidl.js → webidl/index.js} +57 -9
- package/lib/web/websocket/connection.js +4 -12
- package/lib/web/websocket/events.js +1 -1
- package/lib/web/websocket/frame.js +2 -1
- package/lib/web/websocket/receiver.js +2 -12
- package/lib/web/websocket/stream/websocketerror.js +1 -1
- package/lib/web/websocket/stream/websocketstream.js +8 -5
- package/lib/web/websocket/websocket.js +61 -5
- package/package.json +5 -5
- package/types/diagnostics-channel.d.ts +9 -0
- package/types/dispatcher.d.ts +3 -2
- package/types/env-http-proxy-agent.d.ts +2 -1
- package/types/eventsource.d.ts +3 -3
- package/types/fetch.d.ts +1 -0
- package/types/handlers.d.ts +1 -1
- package/types/mock-client.d.ts +2 -0
- package/types/mock-interceptor.d.ts +2 -0
- package/types/mock-pool.d.ts +2 -0
- package/types/retry-handler.d.ts +9 -0
- package/types/webidl.d.ts +29 -15
- package/types/websocket.d.ts +3 -1
- package/lib/web/fetch/dispatcher-weakref.js +0 -46
package/lib/core/connect.js
CHANGED
|
@@ -12,64 +12,34 @@ let tls // include tls conditionally since it is not always available
|
|
|
12
12
|
// resolve the same servername multiple times even when
|
|
13
13
|
// re-use is enabled.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
this._maxCachedSessions = maxCachedSessions
|
|
22
|
-
this._sessionCache = new Map()
|
|
23
|
-
this._sessionRegistry = new global.FinalizationRegistry((key) => {
|
|
24
|
-
if (this._sessionCache.size < this._maxCachedSessions) {
|
|
25
|
-
return
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const ref = this._sessionCache.get(key)
|
|
29
|
-
if (ref !== undefined && ref.deref() === undefined) {
|
|
30
|
-
this._sessionCache.delete(key)
|
|
31
|
-
}
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
get (sessionKey) {
|
|
36
|
-
const ref = this._sessionCache.get(sessionKey)
|
|
37
|
-
return ref ? ref.deref() : null
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
set (sessionKey, session) {
|
|
41
|
-
if (this._maxCachedSessions === 0) {
|
|
15
|
+
const SessionCache = class WeakSessionCache {
|
|
16
|
+
constructor (maxCachedSessions) {
|
|
17
|
+
this._maxCachedSessions = maxCachedSessions
|
|
18
|
+
this._sessionCache = new Map()
|
|
19
|
+
this._sessionRegistry = new FinalizationRegistry((key) => {
|
|
20
|
+
if (this._sessionCache.size < this._maxCachedSessions) {
|
|
42
21
|
return
|
|
43
22
|
}
|
|
44
23
|
|
|
45
|
-
this._sessionCache.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
} else {
|
|
50
|
-
SessionCache = class SimpleSessionCache {
|
|
51
|
-
constructor (maxCachedSessions) {
|
|
52
|
-
this._maxCachedSessions = maxCachedSessions
|
|
53
|
-
this._sessionCache = new Map()
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
get (sessionKey) {
|
|
57
|
-
return this._sessionCache.get(sessionKey)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
set (sessionKey, session) {
|
|
61
|
-
if (this._maxCachedSessions === 0) {
|
|
62
|
-
return
|
|
24
|
+
const ref = this._sessionCache.get(key)
|
|
25
|
+
if (ref !== undefined && ref.deref() === undefined) {
|
|
26
|
+
this._sessionCache.delete(key)
|
|
63
27
|
}
|
|
28
|
+
})
|
|
29
|
+
}
|
|
64
30
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
31
|
+
get (sessionKey) {
|
|
32
|
+
const ref = this._sessionCache.get(sessionKey)
|
|
33
|
+
return ref ? ref.deref() : null
|
|
34
|
+
}
|
|
70
35
|
|
|
71
|
-
|
|
36
|
+
set (sessionKey, session) {
|
|
37
|
+
if (this._maxCachedSessions === 0) {
|
|
38
|
+
return
|
|
72
39
|
}
|
|
40
|
+
|
|
41
|
+
this._sessionCache.set(sessionKey, new WeakRef(session))
|
|
42
|
+
this._sessionRegistry.register(session, sessionKey)
|
|
73
43
|
}
|
|
74
44
|
}
|
|
75
45
|
|
package/lib/core/diagnostics.js
CHANGED
|
@@ -16,6 +16,8 @@ const channels = {
|
|
|
16
16
|
// Request
|
|
17
17
|
create: diagnosticsChannel.channel('undici:request:create'),
|
|
18
18
|
bodySent: diagnosticsChannel.channel('undici:request:bodySent'),
|
|
19
|
+
bodyChunkSent: diagnosticsChannel.channel('undici:request:bodyChunkSent'),
|
|
20
|
+
bodyChunkReceived: diagnosticsChannel.channel('undici:request:bodyChunkReceived'),
|
|
19
21
|
headers: diagnosticsChannel.channel('undici:request:headers'),
|
|
20
22
|
trailers: diagnosticsChannel.channel('undici:request:trailers'),
|
|
21
23
|
error: diagnosticsChannel.channel('undici:request:error'),
|
|
@@ -85,7 +87,7 @@ function trackClientEvents (debugLog = undiciDebugLog) {
|
|
|
85
87
|
const {
|
|
86
88
|
request: { method, path, origin }
|
|
87
89
|
} = evt
|
|
88
|
-
debugLog('sending request to %s %s
|
|
90
|
+
debugLog('sending request to %s %s%s', method, origin, path)
|
|
89
91
|
})
|
|
90
92
|
}
|
|
91
93
|
|
|
@@ -105,7 +107,7 @@ function trackRequestEvents (debugLog = undiciDebugLog) {
|
|
|
105
107
|
response: { statusCode }
|
|
106
108
|
} = evt
|
|
107
109
|
debugLog(
|
|
108
|
-
'received response to %s %s
|
|
110
|
+
'received response to %s %s%s - HTTP %d',
|
|
109
111
|
method,
|
|
110
112
|
origin,
|
|
111
113
|
path,
|
|
@@ -118,7 +120,7 @@ function trackRequestEvents (debugLog = undiciDebugLog) {
|
|
|
118
120
|
const {
|
|
119
121
|
request: { method, path, origin }
|
|
120
122
|
} = evt
|
|
121
|
-
debugLog('trailers received from %s %s
|
|
123
|
+
debugLog('trailers received from %s %s%s', method, origin, path)
|
|
122
124
|
})
|
|
123
125
|
|
|
124
126
|
diagnosticsChannel.subscribe('undici:request:error',
|
|
@@ -128,7 +130,7 @@ function trackRequestEvents (debugLog = undiciDebugLog) {
|
|
|
128
130
|
error
|
|
129
131
|
} = evt
|
|
130
132
|
debugLog(
|
|
131
|
-
'request to %s %s
|
|
133
|
+
'request to %s %s%s errored - %s',
|
|
132
134
|
method,
|
|
133
135
|
origin,
|
|
134
136
|
path,
|
package/lib/core/request.js
CHANGED
|
@@ -42,7 +42,8 @@ class Request {
|
|
|
42
42
|
reset,
|
|
43
43
|
expectContinue,
|
|
44
44
|
servername,
|
|
45
|
-
throwOnError
|
|
45
|
+
throwOnError,
|
|
46
|
+
maxRedirections
|
|
46
47
|
}, handler) {
|
|
47
48
|
if (typeof path !== 'string') {
|
|
48
49
|
throw new InvalidArgumentError('path must be a string')
|
|
@@ -86,6 +87,10 @@ class Request {
|
|
|
86
87
|
throw new InvalidArgumentError('invalid throwOnError')
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
if (maxRedirections != null && maxRedirections !== 0) {
|
|
91
|
+
throw new InvalidArgumentError('maxRedirections is not supported, use the redirect interceptor')
|
|
92
|
+
}
|
|
93
|
+
|
|
89
94
|
this.headersTimeout = headersTimeout
|
|
90
95
|
|
|
91
96
|
this.bodyTimeout = bodyTimeout
|
|
@@ -194,6 +199,9 @@ class Request {
|
|
|
194
199
|
}
|
|
195
200
|
|
|
196
201
|
onBodySent (chunk) {
|
|
202
|
+
if (channels.bodyChunkSent.hasSubscribers) {
|
|
203
|
+
channels.bodyChunkSent.publish({ request: this, chunk })
|
|
204
|
+
}
|
|
197
205
|
if (this[kHandler].onBodySent) {
|
|
198
206
|
try {
|
|
199
207
|
return this[kHandler].onBodySent(chunk)
|
|
@@ -252,6 +260,9 @@ class Request {
|
|
|
252
260
|
assert(!this.aborted)
|
|
253
261
|
assert(!this.completed)
|
|
254
262
|
|
|
263
|
+
if (channels.bodyChunkReceived.hasSubscribers) {
|
|
264
|
+
channels.bodyChunkReceived.publish({ request: this, chunk })
|
|
265
|
+
}
|
|
255
266
|
try {
|
|
256
267
|
return this[kHandler].onData(chunk)
|
|
257
268
|
} catch (err) {
|
package/lib/core/tree.js
CHANGED
package/lib/core/util.js
CHANGED
|
@@ -6,7 +6,6 @@ const { IncomingMessage } = require('node:http')
|
|
|
6
6
|
const stream = require('node:stream')
|
|
7
7
|
const net = require('node:net')
|
|
8
8
|
const { Blob } = require('node:buffer')
|
|
9
|
-
const nodeUtil = require('node:util')
|
|
10
9
|
const { stringify } = require('node:querystring')
|
|
11
10
|
const { EventEmitter: EE } = require('node:events')
|
|
12
11
|
const timers = require('../util/timers')
|
|
@@ -660,48 +659,6 @@ function addAbortListener (signal, listener) {
|
|
|
660
659
|
return () => signal.removeListener('abort', listener)
|
|
661
660
|
}
|
|
662
661
|
|
|
663
|
-
/**
|
|
664
|
-
* @function
|
|
665
|
-
* @param {string} value
|
|
666
|
-
* @returns {string}
|
|
667
|
-
*/
|
|
668
|
-
const toUSVString = (() => {
|
|
669
|
-
if (typeof String.prototype.toWellFormed === 'function') {
|
|
670
|
-
/**
|
|
671
|
-
* @param {string} value
|
|
672
|
-
* @returns {string}
|
|
673
|
-
*/
|
|
674
|
-
return (value) => `${value}`.toWellFormed()
|
|
675
|
-
} else {
|
|
676
|
-
/**
|
|
677
|
-
* @param {string} value
|
|
678
|
-
* @returns {string}
|
|
679
|
-
*/
|
|
680
|
-
return nodeUtil.toUSVString
|
|
681
|
-
}
|
|
682
|
-
})()
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* @param {*} value
|
|
686
|
-
* @returns {boolean}
|
|
687
|
-
*/
|
|
688
|
-
// TODO: move this to webidl
|
|
689
|
-
const isUSVString = (() => {
|
|
690
|
-
if (typeof String.prototype.isWellFormed === 'function') {
|
|
691
|
-
/**
|
|
692
|
-
* @param {*} value
|
|
693
|
-
* @returns {boolean}
|
|
694
|
-
*/
|
|
695
|
-
return (value) => `${value}`.isWellFormed()
|
|
696
|
-
} else {
|
|
697
|
-
/**
|
|
698
|
-
* @param {*} value
|
|
699
|
-
* @returns {boolean}
|
|
700
|
-
*/
|
|
701
|
-
return (value) => toUSVString(value) === `${value}`
|
|
702
|
-
}
|
|
703
|
-
})()
|
|
704
|
-
|
|
705
662
|
/**
|
|
706
663
|
* @see https://tools.ietf.org/html/rfc7230#section-3.2.6
|
|
707
664
|
* @param {number} c
|
|
@@ -943,8 +900,6 @@ Object.setPrototypeOf(normalizedMethodRecords, null)
|
|
|
943
900
|
module.exports = {
|
|
944
901
|
kEnumerableProperty,
|
|
945
902
|
isDisturbed,
|
|
946
|
-
toUSVString,
|
|
947
|
-
isUSVString,
|
|
948
903
|
isBlobLike,
|
|
949
904
|
parseOrigin,
|
|
950
905
|
parseURL,
|
|
@@ -60,12 +60,12 @@ const removeAllListeners = util.removeAllListeners
|
|
|
60
60
|
|
|
61
61
|
let extractBody
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
function lazyllhttp () {
|
|
64
64
|
const llhttpWasmData = process.env.JEST_WORKER_ID ? require('../llhttp/llhttp-wasm.js') : undefined
|
|
65
65
|
|
|
66
66
|
let mod
|
|
67
67
|
try {
|
|
68
|
-
mod =
|
|
68
|
+
mod = new WebAssembly.Module(require('../llhttp/llhttp_simd-wasm.js'))
|
|
69
69
|
} catch (e) {
|
|
70
70
|
/* istanbul ignore next */
|
|
71
71
|
|
|
@@ -73,10 +73,10 @@ async function lazyllhttp () {
|
|
|
73
73
|
// being enabled, but the occurring of this other error
|
|
74
74
|
// * https://github.com/emscripten-core/emscripten/issues/11495
|
|
75
75
|
// got me to remove that check to avoid breaking Node 12.
|
|
76
|
-
mod =
|
|
76
|
+
mod = new WebAssembly.Module(llhttpWasmData || require('../llhttp/llhttp-wasm.js'))
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
return
|
|
79
|
+
return new WebAssembly.Instance(mod, {
|
|
80
80
|
env: {
|
|
81
81
|
/**
|
|
82
82
|
* @param {number} p
|
|
@@ -165,11 +165,6 @@ async function lazyllhttp () {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
let llhttpInstance = null
|
|
168
|
-
/**
|
|
169
|
-
* @type {Promise<WebAssembly.Instance>|null}
|
|
170
|
-
*/
|
|
171
|
-
let llhttpPromise = lazyllhttp()
|
|
172
|
-
llhttpPromise.catch()
|
|
173
168
|
|
|
174
169
|
/**
|
|
175
170
|
* @type {Parser|null}
|
|
@@ -249,7 +244,7 @@ class Parser {
|
|
|
249
244
|
this.timeout = timers.setFastTimeout(onParserTimeout, delay, new WeakRef(this))
|
|
250
245
|
} else {
|
|
251
246
|
this.timeout = setTimeout(onParserTimeout, delay, new WeakRef(this))
|
|
252
|
-
this.timeout
|
|
247
|
+
this.timeout?.unref()
|
|
253
248
|
}
|
|
254
249
|
}
|
|
255
250
|
|
|
@@ -732,7 +727,7 @@ class Parser {
|
|
|
732
727
|
// We must wait a full event loop cycle to reuse this socket to make sure
|
|
733
728
|
// that non-spec compliant servers are not closing the connection even if they
|
|
734
729
|
// said they won't.
|
|
735
|
-
setImmediate(
|
|
730
|
+
setImmediate(client[kResume])
|
|
736
731
|
} else {
|
|
737
732
|
client[kResume]()
|
|
738
733
|
}
|
|
@@ -769,11 +764,7 @@ async function connectH1 (client, socket) {
|
|
|
769
764
|
client[kSocket] = socket
|
|
770
765
|
|
|
771
766
|
if (!llhttpInstance) {
|
|
772
|
-
|
|
773
|
-
socket.on('error', noop)
|
|
774
|
-
llhttpInstance = await llhttpPromise
|
|
775
|
-
llhttpPromise = null
|
|
776
|
-
socket.off('error', noop)
|
|
767
|
+
llhttpInstance = lazyllhttp()
|
|
777
768
|
}
|
|
778
769
|
|
|
779
770
|
if (socket.errored) {
|
|
@@ -1297,9 +1288,9 @@ function writeStream (abort, body, client, request, socket, contentLength, heade
|
|
|
1297
1288
|
.on('error', onFinished)
|
|
1298
1289
|
|
|
1299
1290
|
if (body.errorEmitted ?? body.errored) {
|
|
1300
|
-
setImmediate(
|
|
1291
|
+
setImmediate(onFinished, body.errored)
|
|
1301
1292
|
} else if (body.endEmitted ?? body.readableEnded) {
|
|
1302
|
-
setImmediate(
|
|
1293
|
+
setImmediate(onFinished, null)
|
|
1303
1294
|
}
|
|
1304
1295
|
|
|
1305
1296
|
if (body.closeEmitted ?? body.closed) {
|
|
@@ -144,7 +144,8 @@ class ProxyAgent extends DispatcherBase {
|
|
|
144
144
|
signal: opts.signal,
|
|
145
145
|
headers: {
|
|
146
146
|
...this[kProxyHeaders],
|
|
147
|
-
host: opts.host
|
|
147
|
+
host: opts.host,
|
|
148
|
+
...(opts.connections == null || opts.connections > 0 ? { 'proxy-connection': 'keep-alive' } : {})
|
|
148
149
|
},
|
|
149
150
|
servername: this[kProxyTls]?.servername || proxyHostname
|
|
150
151
|
})
|
|
@@ -241,7 +241,10 @@ class CacheHandler {
|
|
|
241
241
|
* @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
|
|
242
242
|
*/
|
|
243
243
|
function canCacheResponse (cacheType, statusCode, resHeaders, cacheControlDirectives) {
|
|
244
|
-
|
|
244
|
+
// Allow caching for status codes 200 and 307 (original behavior)
|
|
245
|
+
// Also allow caching for other status codes that are heuristically cacheable
|
|
246
|
+
// when they have explicit cache directives
|
|
247
|
+
if (statusCode !== 200 && statusCode !== 307 && !HEURISTICALLY_CACHEABLE_STATUS_CODES.includes(statusCode)) {
|
|
245
248
|
return false
|
|
246
249
|
}
|
|
247
250
|
|
|
@@ -42,7 +42,8 @@ class RedirectHandler {
|
|
|
42
42
|
|
|
43
43
|
this.dispatch = dispatch
|
|
44
44
|
this.location = null
|
|
45
|
-
|
|
45
|
+
const { maxRedirections: _, ...cleanOpts } = opts
|
|
46
|
+
this.opts = cleanOpts // opts must be a copy, exclude maxRedirections
|
|
46
47
|
this.maxRedirections = maxRedirections
|
|
47
48
|
this.handler = handler
|
|
48
49
|
this.history = []
|
|
@@ -138,7 +139,6 @@ class RedirectHandler {
|
|
|
138
139
|
this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin)
|
|
139
140
|
this.opts.path = path
|
|
140
141
|
this.opts.origin = origin
|
|
141
|
-
this.opts.maxRedirections = 0
|
|
142
142
|
this.opts.query = null
|
|
143
143
|
}
|
|
144
144
|
|
|
@@ -29,13 +29,16 @@ class RetryHandler {
|
|
|
29
29
|
methods,
|
|
30
30
|
errorCodes,
|
|
31
31
|
retryAfter,
|
|
32
|
-
statusCodes
|
|
32
|
+
statusCodes,
|
|
33
|
+
throwOnError
|
|
33
34
|
} = retryOptions ?? {}
|
|
34
35
|
|
|
36
|
+
this.error = null
|
|
35
37
|
this.dispatch = dispatch
|
|
36
38
|
this.handler = WrapHandler.wrap(handler)
|
|
37
39
|
this.opts = { ...dispatchOpts, body: wrapRequestBody(opts.body) }
|
|
38
40
|
this.retryOpts = {
|
|
41
|
+
throwOnError: throwOnError ?? true,
|
|
39
42
|
retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry],
|
|
40
43
|
retryAfter: retryAfter ?? true,
|
|
41
44
|
maxTimeout: maxTimeout ?? 30 * 1000, // 30s,
|
|
@@ -68,6 +71,50 @@ class RetryHandler {
|
|
|
68
71
|
this.etag = null
|
|
69
72
|
}
|
|
70
73
|
|
|
74
|
+
onResponseStartWithRetry (controller, statusCode, headers, statusMessage, err) {
|
|
75
|
+
if (this.retryOpts.throwOnError) {
|
|
76
|
+
// Preserve old behavior for status codes that are not eligible for retry
|
|
77
|
+
if (this.retryOpts.statusCodes.includes(statusCode) === false) {
|
|
78
|
+
this.headersSent = true
|
|
79
|
+
this.handler.onResponseStart?.(controller, statusCode, headers, statusMessage)
|
|
80
|
+
} else {
|
|
81
|
+
this.error = err
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (isDisturbed(this.opts.body)) {
|
|
88
|
+
this.headersSent = true
|
|
89
|
+
this.handler.onResponseStart?.(controller, statusCode, headers, statusMessage)
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function shouldRetry (passedErr) {
|
|
94
|
+
if (passedErr) {
|
|
95
|
+
this.headersSent = true
|
|
96
|
+
|
|
97
|
+
this.headersSent = true
|
|
98
|
+
this.handler.onResponseStart?.(controller, statusCode, headers, statusMessage)
|
|
99
|
+
controller.resume()
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.error = err
|
|
104
|
+
controller.resume()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
controller.pause()
|
|
108
|
+
this.retryOpts.retry(
|
|
109
|
+
err,
|
|
110
|
+
{
|
|
111
|
+
state: { counter: this.retryCount },
|
|
112
|
+
opts: { retryOptions: this.retryOpts, ...this.opts }
|
|
113
|
+
},
|
|
114
|
+
shouldRetry.bind(this)
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
71
118
|
onRequestStart (controller, context) {
|
|
72
119
|
if (!this.headersSent) {
|
|
73
120
|
this.handler.onRequestStart?.(controller, context)
|
|
@@ -137,26 +184,19 @@ class RetryHandler {
|
|
|
137
184
|
}
|
|
138
185
|
|
|
139
186
|
onResponseStart (controller, statusCode, headers, statusMessage) {
|
|
187
|
+
this.error = null
|
|
140
188
|
this.retryCount += 1
|
|
141
189
|
|
|
142
190
|
if (statusCode >= 300) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
} else {
|
|
153
|
-
throw new RequestRetryError('Request failed', statusCode, {
|
|
154
|
-
headers,
|
|
155
|
-
data: {
|
|
156
|
-
count: this.retryCount
|
|
157
|
-
}
|
|
158
|
-
})
|
|
159
|
-
}
|
|
191
|
+
const err = new RequestRetryError('Request failed', statusCode, {
|
|
192
|
+
headers,
|
|
193
|
+
data: {
|
|
194
|
+
count: this.retryCount
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
this.onResponseStartWithRetry(controller, statusCode, headers, statusMessage, err)
|
|
199
|
+
return
|
|
160
200
|
}
|
|
161
201
|
|
|
162
202
|
// Checkpoint for resume from where we left it
|
|
@@ -175,6 +215,7 @@ class RetryHandler {
|
|
|
175
215
|
const contentRange = parseRangeHeader(headers['content-range'])
|
|
176
216
|
// If no content range
|
|
177
217
|
if (!contentRange) {
|
|
218
|
+
// We always throw here as we want to indicate that we entred unexpected path
|
|
178
219
|
throw new RequestRetryError('Content-Range mismatch', statusCode, {
|
|
179
220
|
headers,
|
|
180
221
|
data: { count: this.retryCount }
|
|
@@ -183,6 +224,7 @@ class RetryHandler {
|
|
|
183
224
|
|
|
184
225
|
// Let's start with a weak etag check
|
|
185
226
|
if (this.etag != null && this.etag !== headers.etag) {
|
|
227
|
+
// We always throw here as we want to indicate that we entred unexpected path
|
|
186
228
|
throw new RequestRetryError('ETag mismatch', statusCode, {
|
|
187
229
|
headers,
|
|
188
230
|
data: { count: this.retryCount }
|
|
@@ -266,14 +308,52 @@ class RetryHandler {
|
|
|
266
308
|
}
|
|
267
309
|
|
|
268
310
|
onResponseData (controller, chunk) {
|
|
311
|
+
if (this.error) {
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
|
|
269
315
|
this.start += chunk.length
|
|
270
316
|
|
|
271
317
|
this.handler.onResponseData?.(controller, chunk)
|
|
272
318
|
}
|
|
273
319
|
|
|
274
320
|
onResponseEnd (controller, trailers) {
|
|
275
|
-
this.
|
|
276
|
-
|
|
321
|
+
if (this.error && this.retryOpts.throwOnError) {
|
|
322
|
+
throw this.error
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (!this.error) {
|
|
326
|
+
this.retryCount = 0
|
|
327
|
+
return this.handler.onResponseEnd?.(controller, trailers)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
this.retry(controller)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
retry (controller) {
|
|
334
|
+
if (this.start !== 0) {
|
|
335
|
+
const headers = { range: `bytes=${this.start}-${this.end ?? ''}` }
|
|
336
|
+
|
|
337
|
+
// Weak etag check - weak etags will make comparison algorithms never match
|
|
338
|
+
if (this.etag != null) {
|
|
339
|
+
headers['if-match'] = this.etag
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
this.opts = {
|
|
343
|
+
...this.opts,
|
|
344
|
+
headers: {
|
|
345
|
+
...this.opts.headers,
|
|
346
|
+
...headers
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
this.retryCountCheckpoint = this.retryCount
|
|
353
|
+
this.dispatch(this.opts, this)
|
|
354
|
+
} catch (err) {
|
|
355
|
+
this.handler.onResponseError?.(controller, err)
|
|
356
|
+
}
|
|
277
357
|
}
|
|
278
358
|
|
|
279
359
|
onResponseError (controller, err) {
|
|
@@ -282,6 +362,15 @@ class RetryHandler {
|
|
|
282
362
|
return
|
|
283
363
|
}
|
|
284
364
|
|
|
365
|
+
function shouldRetry (returnedErr) {
|
|
366
|
+
if (!returnedErr) {
|
|
367
|
+
this.retry(controller)
|
|
368
|
+
return
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
this.handler?.onResponseError?.(controller, returnedErr)
|
|
372
|
+
}
|
|
373
|
+
|
|
285
374
|
// We reconcile in case of a mix between network errors
|
|
286
375
|
// and server error response
|
|
287
376
|
if (this.retryCount - this.retryCountCheckpoint > 0) {
|
|
@@ -299,43 +388,8 @@ class RetryHandler {
|
|
|
299
388
|
state: { counter: this.retryCount },
|
|
300
389
|
opts: { retryOptions: this.retryOpts, ...this.opts }
|
|
301
390
|
},
|
|
302
|
-
|
|
391
|
+
shouldRetry.bind(this)
|
|
303
392
|
)
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* @this {RetryHandler}
|
|
307
|
-
* @param {Error} [err]
|
|
308
|
-
* @returns
|
|
309
|
-
*/
|
|
310
|
-
function onRetry (err) {
|
|
311
|
-
if (err != null || controller?.aborted || isDisturbed(this.opts.body)) {
|
|
312
|
-
return this.handler.onResponseError?.(controller, err)
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (this.start !== 0) {
|
|
316
|
-
const headers = { range: `bytes=${this.start}-${this.end ?? ''}` }
|
|
317
|
-
|
|
318
|
-
// Weak etag check - weak etags will make comparison algorithms never match
|
|
319
|
-
if (this.etag != null) {
|
|
320
|
-
headers['if-match'] = this.etag
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
this.opts = {
|
|
324
|
-
...this.opts,
|
|
325
|
-
headers: {
|
|
326
|
-
...this.opts.headers,
|
|
327
|
-
...headers
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
try {
|
|
333
|
-
this.retryCountCheckpoint = this.retryCount
|
|
334
|
-
this.dispatch(this.opts, this)
|
|
335
|
-
} catch (err) {
|
|
336
|
-
this.handler.onResponseError?.(controller, err)
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
393
|
}
|
|
340
394
|
}
|
|
341
395
|
|
package/lib/interceptor/cache.js
CHANGED
|
@@ -301,11 +301,11 @@ module.exports = (opts = {}) => {
|
|
|
301
301
|
assertCacheMethods(methods, 'opts.methods')
|
|
302
302
|
|
|
303
303
|
if (typeof cacheByDefault !== 'undefined' && typeof cacheByDefault !== 'number') {
|
|
304
|
-
throw new TypeError(`
|
|
304
|
+
throw new TypeError(`expected opts.cacheByDefault to be number or undefined, got ${typeof cacheByDefault}`)
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
if (typeof type !== 'undefined' && type !== 'shared' && type !== 'private') {
|
|
308
|
-
throw new TypeError(`
|
|
308
|
+
throw new TypeError(`expected opts.type to be shared, private, or undefined, got ${typeof type}`)
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
const globalOpts = {
|
|
@@ -11,7 +11,7 @@ function createRedirectInterceptor ({ maxRedirections: defaultMaxRedirections }
|
|
|
11
11
|
return dispatch(opts, handler)
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const dispatchOpts = { ...rest
|
|
14
|
+
const dispatchOpts = { ...rest } // Stop sub dispatcher from also redirecting.
|
|
15
15
|
const redirectHandler = new RedirectHandler(dispatch, maxRedirections, dispatchOpts, handler)
|
|
16
16
|
return dispatch(dispatchOpts, redirectHandler)
|
|
17
17
|
}
|
package/lib/mock/mock-client.js
CHANGED