aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/node_modules/undici/lib/handler
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/undici/lib/handler')
-rw-r--r--node_modules/undici/lib/handler/DecoratorHandler.js35
-rw-r--r--node_modules/undici/lib/handler/decorator-handler.js44
-rw-r--r--node_modules/undici/lib/handler/redirect-handler.js (renamed from node_modules/undici/lib/handler/RedirectHandler.js)21
-rw-r--r--node_modules/undici/lib/handler/retry-handler.js (renamed from node_modules/undici/lib/handler/RetryHandler.js)134
4 files changed, 146 insertions, 88 deletions
diff --git a/node_modules/undici/lib/handler/DecoratorHandler.js b/node_modules/undici/lib/handler/DecoratorHandler.js
deleted file mode 100644
index 9d70a76..0000000
--- a/node_modules/undici/lib/handler/DecoratorHandler.js
+++ /dev/null
@@ -1,35 +0,0 @@
-'use strict'
-
-module.exports = class DecoratorHandler {
- constructor (handler) {
- this.handler = handler
- }
-
- onConnect (...args) {
- return this.handler.onConnect(...args)
- }
-
- onError (...args) {
- return this.handler.onError(...args)
- }
-
- onUpgrade (...args) {
- return this.handler.onUpgrade(...args)
- }
-
- onHeaders (...args) {
- return this.handler.onHeaders(...args)
- }
-
- onData (...args) {
- return this.handler.onData(...args)
- }
-
- onComplete (...args) {
- return this.handler.onComplete(...args)
- }
-
- onBodySent (...args) {
- return this.handler.onBodySent(...args)
- }
-}
diff --git a/node_modules/undici/lib/handler/decorator-handler.js b/node_modules/undici/lib/handler/decorator-handler.js
new file mode 100644
index 0000000..26f220c
--- /dev/null
+++ b/node_modules/undici/lib/handler/decorator-handler.js
@@ -0,0 +1,44 @@
+'use strict'
+
+module.exports = class DecoratorHandler {
+ #handler
+
+ constructor (handler) {
+ if (typeof handler !== 'object' || handler === null) {
+ throw new TypeError('handler must be an object')
+ }
+ this.#handler = handler
+ }
+
+ onConnect (...args) {
+ return this.#handler.onConnect?.(...args)
+ }
+
+ onError (...args) {
+ return this.#handler.onError?.(...args)
+ }
+
+ onUpgrade (...args) {
+ return this.#handler.onUpgrade?.(...args)
+ }
+
+ onResponseStarted (...args) {
+ return this.#handler.onResponseStarted?.(...args)
+ }
+
+ onHeaders (...args) {
+ return this.#handler.onHeaders?.(...args)
+ }
+
+ onData (...args) {
+ return this.#handler.onData?.(...args)
+ }
+
+ onComplete (...args) {
+ return this.#handler.onComplete?.(...args)
+ }
+
+ onBodySent (...args) {
+ return this.#handler.onBodySent?.(...args)
+ }
+}
diff --git a/node_modules/undici/lib/handler/RedirectHandler.js b/node_modules/undici/lib/handler/redirect-handler.js
index 933725f..16a7b21 100644
--- a/node_modules/undici/lib/handler/RedirectHandler.js
+++ b/node_modules/undici/lib/handler/redirect-handler.js
@@ -2,9 +2,9 @@
const util = require('../core/util')
const { kBodyUsed } = require('../core/symbols')
-const assert = require('assert')
+const assert = require('node:assert')
const { InvalidArgumentError } = require('../core/errors')
-const EE = require('events')
+const EE = require('node:events')
const redirectableStatusCodes = [300, 301, 302, 303, 307, 308]
@@ -38,6 +38,7 @@ class RedirectHandler {
this.maxRedirections = maxRedirections
this.handler = handler
this.history = []
+ this.redirectionLimitReached = false
if (util.isStream(this.opts.body)) {
// TODO (fix): Provide some way for the user to cache the file to e.g. /tmp
@@ -91,6 +92,16 @@ class RedirectHandler {
? null
: parseLocation(statusCode, headers)
+ if (this.opts.throwOnMaxRedirect && this.history.length >= this.maxRedirections) {
+ if (this.request) {
+ this.request.abort(new Error('max redirects'))
+ }
+
+ this.redirectionLimitReached = true
+ this.abort(new Error('max redirects'))
+ return
+ }
+
if (this.opts.origin) {
this.history.push(new URL(this.opts.path, this.opts.origin))
}
@@ -135,7 +146,7 @@ class RedirectHandler {
For status 300, which is "Multiple Choices", the spec mentions both generating a Location
response header AND a response body with the other possible location to follow.
- Since the spec explicitily chooses not to specify a format for such body and leave it to
+ Since the spec explicitly chooses not to specify a format for such body and leave it to
servers and browsers implementors, we ignore the body as there is no specified way to eventually parse it.
*/
} else {
@@ -151,7 +162,7 @@ class RedirectHandler {
TLDR: undici always ignores 3xx response trailers as they are not expected in case of redirections
and neither are useful if present.
- See comment on onData method above for more detailed informations.
+ See comment on onData method above for more detailed information.
*/
this.location = null
@@ -176,7 +187,7 @@ function parseLocation (statusCode, headers) {
}
for (let i = 0; i < headers.length; i += 2) {
- if (headers[i].toString().toLowerCase() === 'location') {
+ if (headers[i].length === 8 && util.headerNameToString(headers[i]) === 'location') {
return headers[i + 1]
}
}
diff --git a/node_modules/undici/lib/handler/RetryHandler.js b/node_modules/undici/lib/handler/retry-handler.js
index 3710447..5d1ccf0 100644
--- a/node_modules/undici/lib/handler/RetryHandler.js
+++ b/node_modules/undici/lib/handler/retry-handler.js
@@ -1,14 +1,18 @@
-const assert = require('assert')
+'use strict'
+const assert = require('node:assert')
const { kRetryHandlerDefaultRetry } = require('../core/symbols')
const { RequestRetryError } = require('../core/errors')
-const { isDisturbed, parseHeaders, parseRangeHeader } = require('../core/util')
+const {
+ isDisturbed,
+ parseHeaders,
+ parseRangeHeader,
+ wrapRequestBody
+} = require('../core/util')
function calculateRetryAfterHeader (retryAfter) {
const current = Date.now()
- const diff = new Date(retryAfter).getTime() - current
-
- return diff
+ return new Date(retryAfter).getTime() - current
}
class RetryHandler {
@@ -30,14 +34,14 @@ class RetryHandler {
this.dispatch = handlers.dispatch
this.handler = handlers.handler
- this.opts = dispatchOpts
+ this.opts = { ...dispatchOpts, body: wrapRequestBody(opts.body) }
this.abort = null
this.aborted = false
this.retryOpts = {
retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry],
retryAfter: retryAfter ?? true,
maxTimeout: maxTimeout ?? 30 * 1000, // 30s,
- timeout: minTimeout ?? 500, // .5s
+ minTimeout: minTimeout ?? 500, // .5s
timeoutFactor: timeoutFactor ?? 2,
maxRetries: maxRetries ?? 5,
// What errors we should retry
@@ -53,11 +57,13 @@ class RetryHandler {
'ENETUNREACH',
'EHOSTDOWN',
'EHOSTUNREACH',
- 'EPIPE'
+ 'EPIPE',
+ 'UND_ERR_SOCKET'
]
}
this.retryCount = 0
+ this.retryCountCheckpoint = 0
this.start = 0
this.end = null
this.etag = null
@@ -103,25 +109,17 @@ class RetryHandler {
const { method, retryOptions } = opts
const {
maxRetries,
- timeout,
+ minTimeout,
maxTimeout,
timeoutFactor,
statusCodes,
errorCodes,
methods
} = retryOptions
- let { counter, currentTimeout } = state
-
- currentTimeout =
- currentTimeout != null && currentTimeout > 0 ? currentTimeout : timeout
+ const { counter } = state
// Any code that is not a Undici's originated and allowed to retry
- if (
- code &&
- code !== 'UND_ERR_REQ_RETRY' &&
- code !== 'UND_ERR_SOCKET' &&
- !errorCodes.includes(code)
- ) {
+ if (code && code !== 'UND_ERR_REQ_RETRY' && !errorCodes.includes(code)) {
cb(err)
return
}
@@ -148,10 +146,10 @@ class RetryHandler {
return
}
- let retryAfterHeader = headers != null && headers['retry-after']
+ let retryAfterHeader = headers?.['retry-after']
if (retryAfterHeader) {
retryAfterHeader = Number(retryAfterHeader)
- retryAfterHeader = isNaN(retryAfterHeader)
+ retryAfterHeader = Number.isNaN(retryAfterHeader)
? calculateRetryAfterHeader(retryAfterHeader)
: retryAfterHeader * 1e3 // Retry-After is in seconds
}
@@ -159,9 +157,7 @@ class RetryHandler {
const retryTimeout =
retryAfterHeader > 0
? Math.min(retryAfterHeader, maxTimeout)
- : Math.min(currentTimeout * timeoutFactor ** counter, maxTimeout)
-
- state.currentTimeout = retryTimeout
+ : Math.min(minTimeout * timeoutFactor ** (counter - 1), maxTimeout)
setTimeout(() => cb(null), retryTimeout)
}
@@ -172,21 +168,42 @@ class RetryHandler {
this.retryCount += 1
if (statusCode >= 300) {
- this.abort(
- new RequestRetryError('Request failed', statusCode, {
- headers,
- count: this.retryCount
- })
- )
- return false
+ if (this.retryOpts.statusCodes.includes(statusCode) === false) {
+ return this.handler.onHeaders(
+ statusCode,
+ rawHeaders,
+ resume,
+ statusMessage
+ )
+ } else {
+ this.abort(
+ new RequestRetryError('Request failed', statusCode, {
+ headers,
+ data: {
+ count: this.retryCount
+ }
+ })
+ )
+ return false
+ }
}
// Checkpoint for resume from where we left it
if (this.resume != null) {
this.resume = null
- if (statusCode !== 206) {
- return true
+ // Only Partial Content 206 supposed to provide Content-Range,
+ // any other status code that partially consumed the payload
+ // should not be retry because it would result in downstream
+ // wrongly concatanete multiple responses.
+ if (statusCode !== 206 && (this.start > 0 || statusCode !== 200)) {
+ this.abort(
+ new RequestRetryError('server does not support the range header and the payload was partially consumed', statusCode, {
+ headers,
+ data: { count: this.retryCount }
+ })
+ )
+ return false
}
const contentRange = parseRangeHeader(headers['content-range'])
@@ -195,7 +212,7 @@ class RetryHandler {
this.abort(
new RequestRetryError('Content-Range mismatch', statusCode, {
headers,
- count: this.retryCount
+ data: { count: this.retryCount }
})
)
return false
@@ -206,13 +223,13 @@ class RetryHandler {
this.abort(
new RequestRetryError('ETag mismatch', statusCode, {
headers,
- count: this.retryCount
+ data: { count: this.retryCount }
})
)
return false
}
- const { start, size, end = size } = contentRange
+ const { start, size, end = size - 1 } = contentRange
assert(this.start === start, 'content-range mismatch')
assert(this.end == null || this.end === end, 'content-range mismatch')
@@ -235,17 +252,12 @@ class RetryHandler {
)
}
- const { start, size, end = size } = range
-
+ const { start, size, end = size - 1 } = range
assert(
- start != null && Number.isFinite(start) && this.start !== start,
+ start != null && Number.isFinite(start),
'content-range mismatch'
)
- assert(Number.isFinite(start))
- assert(
- end != null && Number.isFinite(end) && this.end !== end,
- 'invalid content-length'
- )
+ assert(end != null && Number.isFinite(end), 'invalid content-length')
this.start = start
this.end = end
@@ -254,7 +266,7 @@ class RetryHandler {
// We make our best to checkpoint the body for further range headers
if (this.end == null) {
const contentLength = headers['content-length']
- this.end = contentLength != null ? Number(contentLength) : null
+ this.end = contentLength != null ? Number(contentLength) - 1 : null
}
assert(Number.isFinite(this.start))
@@ -266,6 +278,13 @@ class RetryHandler {
this.resume = resume
this.etag = headers.etag != null ? headers.etag : null
+ // Weak etags are not useful for comparison nor cache
+ // for instance not safe to assume if the response is byte-per-byte
+ // equal
+ if (this.etag != null && this.etag.startsWith('W/')) {
+ this.etag = null
+ }
+
return this.handler.onHeaders(
statusCode,
rawHeaders,
@@ -276,7 +295,7 @@ class RetryHandler {
const err = new RequestRetryError('Request failed', statusCode, {
headers,
- count: this.retryCount
+ data: { count: this.retryCount }
})
this.abort(err)
@@ -300,10 +319,21 @@ class RetryHandler {
return this.handler.onError(err)
}
+ // We reconcile in case of a mix between network errors
+ // and server error response
+ if (this.retryCount - this.retryCountCheckpoint > 0) {
+ // We count the difference between the last checkpoint and the current retry count
+ this.retryCount =
+ this.retryCountCheckpoint +
+ (this.retryCount - this.retryCountCheckpoint)
+ } else {
+ this.retryCount += 1
+ }
+
this.retryOpts.retry(
err,
{
- state: { counter: this.retryCount++, currentTimeout: this.retryAfter },
+ state: { counter: this.retryCount },
opts: { retryOptions: this.retryOpts, ...this.opts }
},
onRetry.bind(this)
@@ -315,16 +345,24 @@ class RetryHandler {
}
if (this.start !== 0) {
+ const headers = { range: `bytes=${this.start}-${this.end ?? ''}` }
+
+ // Weak etag check - weak etags will make comparison algorithms never match
+ if (this.etag != null) {
+ headers['if-match'] = this.etag
+ }
+
this.opts = {
...this.opts,
headers: {
...this.opts.headers,
- range: `bytes=${this.start}-${this.end ?? ''}`
+ ...headers
}
}
}
try {
+ this.retryCountCheckpoint = this.retryCount
this.dispatch(this.opts, this)
} catch (err) {
this.handler.onError(err)