format: prettify entire project
This commit is contained in:
247
node_modules/undici/lib/handler/retry-handler.js
generated
vendored
247
node_modules/undici/lib/handler/retry-handler.js
generated
vendored
@ -1,23 +1,23 @@
|
||||
'use strict'
|
||||
const assert = require('node:assert')
|
||||
'use strict';
|
||||
const assert = require('node:assert');
|
||||
|
||||
const { kRetryHandlerDefaultRetry } = require('../core/symbols')
|
||||
const { RequestRetryError } = require('../core/errors')
|
||||
const WrapHandler = require('./wrap-handler')
|
||||
const { kRetryHandlerDefaultRetry } = require('../core/symbols');
|
||||
const { RequestRetryError } = require('../core/errors');
|
||||
const WrapHandler = require('./wrap-handler');
|
||||
const {
|
||||
isDisturbed,
|
||||
parseRangeHeader,
|
||||
wrapRequestBody
|
||||
} = require('../core/util')
|
||||
wrapRequestBody,
|
||||
} = require('../core/util');
|
||||
|
||||
function calculateRetryAfterHeader (retryAfter) {
|
||||
const retryTime = new Date(retryAfter).getTime()
|
||||
return isNaN(retryTime) ? 0 : retryTime - Date.now()
|
||||
function calculateRetryAfterHeader(retryAfter) {
|
||||
const retryTime = new Date(retryAfter).getTime();
|
||||
return isNaN(retryTime) ? 0 : retryTime - Date.now();
|
||||
}
|
||||
|
||||
class RetryHandler {
|
||||
constructor (opts, { dispatch, handler }) {
|
||||
const { retryOptions, ...dispatchOpts } = opts
|
||||
constructor(opts, { dispatch, handler }) {
|
||||
const { retryOptions, ...dispatchOpts } = opts;
|
||||
const {
|
||||
// Retry scoped
|
||||
retry: retryFn,
|
||||
@ -29,12 +29,12 @@ class RetryHandler {
|
||||
methods,
|
||||
errorCodes,
|
||||
retryAfter,
|
||||
statusCodes
|
||||
} = retryOptions ?? {}
|
||||
statusCodes,
|
||||
} = retryOptions ?? {};
|
||||
|
||||
this.dispatch = dispatch
|
||||
this.handler = WrapHandler.wrap(handler)
|
||||
this.opts = { ...dispatchOpts, body: wrapRequestBody(opts.body) }
|
||||
this.dispatch = dispatch;
|
||||
this.handler = WrapHandler.wrap(handler);
|
||||
this.opts = { ...dispatchOpts, body: wrapRequestBody(opts.body) };
|
||||
this.retryOpts = {
|
||||
retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry],
|
||||
retryAfter: retryAfter ?? true,
|
||||
@ -56,31 +56,31 @@ class RetryHandler {
|
||||
'EHOSTDOWN',
|
||||
'EHOSTUNREACH',
|
||||
'EPIPE',
|
||||
'UND_ERR_SOCKET'
|
||||
]
|
||||
}
|
||||
'UND_ERR_SOCKET',
|
||||
],
|
||||
};
|
||||
|
||||
this.retryCount = 0
|
||||
this.retryCountCheckpoint = 0
|
||||
this.headersSent = false
|
||||
this.start = 0
|
||||
this.end = null
|
||||
this.etag = null
|
||||
this.retryCount = 0;
|
||||
this.retryCountCheckpoint = 0;
|
||||
this.headersSent = false;
|
||||
this.start = 0;
|
||||
this.end = null;
|
||||
this.etag = null;
|
||||
}
|
||||
|
||||
onRequestStart (controller, context) {
|
||||
onRequestStart(controller, context) {
|
||||
if (!this.headersSent) {
|
||||
this.handler.onRequestStart?.(controller, context)
|
||||
this.handler.onRequestStart?.(controller, context);
|
||||
}
|
||||
}
|
||||
|
||||
onRequestUpgrade (controller, statusCode, headers, socket) {
|
||||
this.handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
|
||||
onRequestUpgrade(controller, statusCode, headers, socket) {
|
||||
this.handler.onRequestUpgrade?.(controller, statusCode, headers, socket);
|
||||
}
|
||||
|
||||
static [kRetryHandlerDefaultRetry] (err, { state, opts }, cb) {
|
||||
const { statusCode, code, headers } = err
|
||||
const { method, retryOptions } = opts
|
||||
static [kRetryHandlerDefaultRetry](err, { state, opts }, cb) {
|
||||
const { statusCode, code, headers } = err;
|
||||
const { method, retryOptions } = opts;
|
||||
const {
|
||||
maxRetries,
|
||||
minTimeout,
|
||||
@ -88,20 +88,20 @@ class RetryHandler {
|
||||
timeoutFactor,
|
||||
statusCodes,
|
||||
errorCodes,
|
||||
methods
|
||||
} = retryOptions
|
||||
const { counter } = state
|
||||
methods,
|
||||
} = retryOptions;
|
||||
const { counter } = state;
|
||||
|
||||
// Any code that is not a Undici's originated and allowed to retry
|
||||
if (code && code !== 'UND_ERR_REQ_RETRY' && !errorCodes.includes(code)) {
|
||||
cb(err)
|
||||
return
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// If a set of method are provided and the current method is not in the list
|
||||
if (Array.isArray(methods) && !methods.includes(method)) {
|
||||
cb(err)
|
||||
return
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// If a set of status code are provided and the current status code is not in the list
|
||||
@ -110,52 +110,53 @@ class RetryHandler {
|
||||
Array.isArray(statusCodes) &&
|
||||
!statusCodes.includes(statusCode)
|
||||
) {
|
||||
cb(err)
|
||||
return
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// If we reached the max number of retries
|
||||
if (counter > maxRetries) {
|
||||
cb(err)
|
||||
return
|
||||
cb(err);
|
||||
return;
|
||||
}
|
||||
|
||||
let retryAfterHeader = headers?.['retry-after']
|
||||
let retryAfterHeader = headers?.['retry-after'];
|
||||
if (retryAfterHeader) {
|
||||
retryAfterHeader = Number(retryAfterHeader)
|
||||
retryAfterHeader = Number.isNaN(retryAfterHeader)
|
||||
? calculateRetryAfterHeader(headers['retry-after'])
|
||||
: retryAfterHeader * 1e3 // Retry-After is in seconds
|
||||
retryAfterHeader = Number(retryAfterHeader);
|
||||
retryAfterHeader =
|
||||
Number.isNaN(retryAfterHeader) ?
|
||||
calculateRetryAfterHeader(headers['retry-after'])
|
||||
: retryAfterHeader * 1e3; // Retry-After is in seconds
|
||||
}
|
||||
|
||||
const retryTimeout =
|
||||
retryAfterHeader > 0
|
||||
? Math.min(retryAfterHeader, maxTimeout)
|
||||
: Math.min(minTimeout * timeoutFactor ** (counter - 1), maxTimeout)
|
||||
retryAfterHeader > 0 ?
|
||||
Math.min(retryAfterHeader, maxTimeout)
|
||||
: Math.min(minTimeout * timeoutFactor ** (counter - 1), maxTimeout);
|
||||
|
||||
setTimeout(() => cb(null), retryTimeout)
|
||||
setTimeout(() => cb(null), retryTimeout);
|
||||
}
|
||||
|
||||
onResponseStart (controller, statusCode, headers, statusMessage) {
|
||||
this.retryCount += 1
|
||||
onResponseStart(controller, statusCode, headers, statusMessage) {
|
||||
this.retryCount += 1;
|
||||
|
||||
if (statusCode >= 300) {
|
||||
if (this.retryOpts.statusCodes.includes(statusCode) === false) {
|
||||
this.headersSent = true
|
||||
this.headersSent = true;
|
||||
this.handler.onResponseStart?.(
|
||||
controller,
|
||||
statusCode,
|
||||
headers,
|
||||
statusMessage
|
||||
)
|
||||
return
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
throw new RequestRetryError('Request failed', statusCode, {
|
||||
headers,
|
||||
data: {
|
||||
count: this.retryCount
|
||||
}
|
||||
})
|
||||
count: this.retryCount,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,120 +167,120 @@ class RetryHandler {
|
||||
// should not be retried because it would result in downstream
|
||||
// wrongly concatenate multiple responses.
|
||||
if (statusCode !== 206 && (this.start > 0 || statusCode !== 200)) {
|
||||
throw new RequestRetryError('server does not support the range header and the payload was partially consumed', statusCode, {
|
||||
headers,
|
||||
data: { count: this.retryCount }
|
||||
})
|
||||
throw new RequestRetryError(
|
||||
'server does not support the range header and the payload was partially consumed',
|
||||
statusCode,
|
||||
{
|
||||
headers,
|
||||
data: { count: this.retryCount },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const contentRange = parseRangeHeader(headers['content-range'])
|
||||
const contentRange = parseRangeHeader(headers['content-range']);
|
||||
// If no content range
|
||||
if (!contentRange) {
|
||||
throw new RequestRetryError('Content-Range mismatch', statusCode, {
|
||||
headers,
|
||||
data: { count: this.retryCount }
|
||||
})
|
||||
data: { count: this.retryCount },
|
||||
});
|
||||
}
|
||||
|
||||
// Let's start with a weak etag check
|
||||
if (this.etag != null && this.etag !== headers.etag) {
|
||||
throw new RequestRetryError('ETag mismatch', statusCode, {
|
||||
headers,
|
||||
data: { count: this.retryCount }
|
||||
})
|
||||
data: { count: this.retryCount },
|
||||
});
|
||||
}
|
||||
|
||||
const { start, size, end = size ? size - 1 : null } = contentRange
|
||||
const { start, size, end = size ? size - 1 : null } = contentRange;
|
||||
|
||||
assert(this.start === start, 'content-range mismatch')
|
||||
assert(this.end == null || this.end === end, 'content-range mismatch')
|
||||
assert(this.start === start, 'content-range mismatch');
|
||||
assert(this.end == null || this.end === end, 'content-range mismatch');
|
||||
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.end == null) {
|
||||
if (statusCode === 206) {
|
||||
// First time we receive 206
|
||||
const range = parseRangeHeader(headers['content-range'])
|
||||
const range = parseRangeHeader(headers['content-range']);
|
||||
|
||||
if (range == null) {
|
||||
this.headersSent = true
|
||||
this.headersSent = true;
|
||||
this.handler.onResponseStart?.(
|
||||
controller,
|
||||
statusCode,
|
||||
headers,
|
||||
statusMessage
|
||||
)
|
||||
return
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { start, size, end = size ? size - 1 : null } = range
|
||||
const { start, size, end = size ? size - 1 : null } = range;
|
||||
assert(
|
||||
start != null && Number.isFinite(start),
|
||||
'content-range mismatch'
|
||||
)
|
||||
assert(end != null && Number.isFinite(end), 'invalid content-length')
|
||||
);
|
||||
assert(end != null && Number.isFinite(end), 'invalid content-length');
|
||||
|
||||
this.start = start
|
||||
this.end = end
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
// 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) - 1 : null
|
||||
const contentLength = headers['content-length'];
|
||||
this.end = contentLength != null ? Number(contentLength) - 1 : null;
|
||||
}
|
||||
|
||||
assert(Number.isFinite(this.start))
|
||||
assert(Number.isFinite(this.start));
|
||||
assert(
|
||||
this.end == null || Number.isFinite(this.end),
|
||||
'invalid content-length'
|
||||
)
|
||||
);
|
||||
|
||||
this.resume = true
|
||||
this.etag = headers.etag != null ? headers.etag : null
|
||||
this.resume = true;
|
||||
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[0] === 'W' &&
|
||||
this.etag[1] === '/'
|
||||
) {
|
||||
this.etag = null
|
||||
if (this.etag != null && this.etag[0] === 'W' && this.etag[1] === '/') {
|
||||
this.etag = null;
|
||||
}
|
||||
|
||||
this.headersSent = true
|
||||
this.headersSent = true;
|
||||
this.handler.onResponseStart?.(
|
||||
controller,
|
||||
statusCode,
|
||||
headers,
|
||||
statusMessage
|
||||
)
|
||||
);
|
||||
} else {
|
||||
throw new RequestRetryError('Request failed', statusCode, {
|
||||
headers,
|
||||
data: { count: this.retryCount }
|
||||
})
|
||||
data: { count: this.retryCount },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onResponseData (controller, chunk) {
|
||||
this.start += chunk.length
|
||||
onResponseData(controller, chunk) {
|
||||
this.start += chunk.length;
|
||||
|
||||
this.handler.onResponseData?.(controller, chunk)
|
||||
this.handler.onResponseData?.(controller, chunk);
|
||||
}
|
||||
|
||||
onResponseEnd (controller, trailers) {
|
||||
this.retryCount = 0
|
||||
return this.handler.onResponseEnd?.(controller, trailers)
|
||||
onResponseEnd(controller, trailers) {
|
||||
this.retryCount = 0;
|
||||
return this.handler.onResponseEnd?.(controller, trailers);
|
||||
}
|
||||
|
||||
onResponseError (controller, err) {
|
||||
onResponseError(controller, err) {
|
||||
if (controller?.aborted || isDisturbed(this.opts.body)) {
|
||||
this.handler.onResponseError?.(controller, err)
|
||||
return
|
||||
this.handler.onResponseError?.(controller, err);
|
||||
return;
|
||||
}
|
||||
|
||||
// We reconcile in case of a mix between network errors
|
||||
@ -288,55 +289,55 @@ class RetryHandler {
|
||||
// We count the difference between the last checkpoint and the current retry count
|
||||
this.retryCount =
|
||||
this.retryCountCheckpoint +
|
||||
(this.retryCount - this.retryCountCheckpoint)
|
||||
(this.retryCount - this.retryCountCheckpoint);
|
||||
} else {
|
||||
this.retryCount += 1
|
||||
this.retryCount += 1;
|
||||
}
|
||||
|
||||
this.retryOpts.retry(
|
||||
err,
|
||||
{
|
||||
state: { counter: this.retryCount },
|
||||
opts: { retryOptions: this.retryOpts, ...this.opts }
|
||||
opts: { retryOptions: this.retryOpts, ...this.opts },
|
||||
},
|
||||
onRetry.bind(this)
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* @this {RetryHandler}
|
||||
* @param {Error} [err]
|
||||
* @returns
|
||||
*/
|
||||
function onRetry (err) {
|
||||
function onRetry(err) {
|
||||
if (err != null || controller?.aborted || isDisturbed(this.opts.body)) {
|
||||
return this.handler.onResponseError?.(controller, err)
|
||||
return this.handler.onResponseError?.(controller, err);
|
||||
}
|
||||
|
||||
if (this.start !== 0) {
|
||||
const headers = { range: `bytes=${this.start}-${this.end ?? ''}` }
|
||||
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
|
||||
headers['if-match'] = this.etag;
|
||||
}
|
||||
|
||||
this.opts = {
|
||||
...this.opts,
|
||||
headers: {
|
||||
...this.opts.headers,
|
||||
...headers
|
||||
}
|
||||
}
|
||||
...headers,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
this.retryCountCheckpoint = this.retryCount
|
||||
this.dispatch(this.opts, this)
|
||||
this.retryCountCheckpoint = this.retryCount;
|
||||
this.dispatch(this.opts, this);
|
||||
} catch (err) {
|
||||
this.handler.onResponseError?.(controller, err)
|
||||
this.handler.onResponseError?.(controller, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RetryHandler
|
||||
module.exports = RetryHandler;
|
||||
|
Reference in New Issue
Block a user