diff options
Diffstat (limited to 'node_modules/undici/lib/handler')
| -rw-r--r-- | node_modules/undici/lib/handler/DecoratorHandler.js | 35 | ||||
| -rw-r--r-- | node_modules/undici/lib/handler/decorator-handler.js | 44 | ||||
| -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) |
