@microsoft/kiota-http-fetchlibrary
Version:
Kiota request adapter implementation with fetch
195 lines • 8.09 kB
JavaScript
/**
* -------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
* See License in the project root for license information.
* -------------------------------------------------------------------------------------------
*/
/**
* @module RetryHandler
*/
import { HttpMethod } from "@microsoft/kiota-abstractions";
import { trace } from "@opentelemetry/api";
import { getObservabilityOptionsFromRequest } from "../observabilityOptions.js";
import { getRequestHeader, setRequestHeader } from "../utils/headersUtil.js";
import { RetryHandlerOptionKey, RetryHandlerOptions } from "./options/retryHandlerOptions.js";
/**
* Middleware
* Class for RetryHandler
*/
export class RetryHandler {
/**
*
* To create an instance of RetryHandler
* @param [options] - The retry handler options value
* @returns An instance of RetryHandler
*/
constructor(options = new RetryHandlerOptions()) {
this.options = options;
if (!options) {
throw new Error("The options parameter is required.");
}
}
/**
*
*
* To check whether the response has the retry status code
* @param response - The response object
* @returns Whether the response has retry status code or not
*/
isRetry(response) {
return RetryHandler.RETRY_STATUS_CODES.has(response.status);
}
/**
*
* To check whether the payload is buffered or not
* @param options - The options of a request
* @returns Whether the payload is buffered or not
*/
isBuffered(options) {
var _a;
const method = options.method;
const isPutPatchOrPost = method === HttpMethod.PUT || method === HttpMethod.PATCH || method === HttpMethod.POST;
if (isPutPatchOrPost) {
const isStream = ((_a = getRequestHeader(options, "content-type")) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === "application/octet-stream";
if (isStream) {
return false;
}
}
return true;
}
/**
*
* To get the delay for a retry
* @param response - The response object
* @param retryAttempts - The current attempt count
* @param delay - The delay value in seconds
* @returns A delay for a retry
*/
getDelay(response, retryAttempts, delay) {
const getRandomness = () => Number(Math.random().toFixed(3));
const retryAfter = response.headers !== undefined ? response.headers.get(RetryHandler.RETRY_AFTER_HEADER) : null;
let newDelay;
if (retryAfter !== null) {
// Retry-After: <http-date>
if (Number.isNaN(Number(retryAfter))) {
newDelay = Math.round((new Date(retryAfter).getTime() - Date.now()) / 1000);
}
else {
// Retry-After: <delay-seconds>
newDelay = Number(retryAfter);
}
}
else {
// Adding randomness to avoid retrying at a same
newDelay = retryAttempts >= 2 ? this.getExponentialBackOffTime(retryAttempts) + delay + getRandomness() : delay + getRandomness();
}
return Math.min(newDelay, this.options.getMaxDelay() + getRandomness());
}
/**
*
* To get an exponential back off value
* @param attempts - The current attempt count
* @returns An exponential back off value
*/
getExponentialBackOffTime(attempts) {
return Math.round((1 / 2) * (2 ** attempts - 1));
}
/**
* To add delay for the execution
* @param delaySeconds - The delay value in seconds
* @returns A Promise that resolves to nothing
*/
async sleep(delaySeconds) {
const delayMilliseconds = delaySeconds * 1000;
return new Promise((resolve) => setTimeout(resolve, delayMilliseconds)); // browser or node
}
/**
* To execute the middleware with retries
* @param url - The request url
* @param fetchRequestInit - The request options
* @param retryAttempts - The current attempt count
* @param currentOptions - The current request options for the retry handler.
* @param requestOptions - The retry middleware options instance
* @param tracerName - The name to use for the tracer
* @returns A Promise that resolves to nothing
*/
async executeWithRetry(url, fetchRequestInit, retryAttempts, currentOptions, requestOptions, tracerName) {
var _a;
const response = await ((_a = this.next) === null || _a === void 0 ? void 0 : _a.execute(url, fetchRequestInit, requestOptions));
if (!response) {
throw new Error("Response is undefined");
}
if (retryAttempts < currentOptions.maxRetries && this.isRetry(response) && this.isBuffered(fetchRequestInit) && currentOptions.shouldRetry(currentOptions.delay, retryAttempts, url, fetchRequestInit, response)) {
++retryAttempts;
setRequestHeader(fetchRequestInit, RetryHandler.RETRY_ATTEMPT_HEADER, retryAttempts.toString());
let delay = null;
if (response) {
delay = this.getDelay(response, retryAttempts, currentOptions.delay);
await this.sleep(delay);
}
if (tracerName) {
return await trace.getTracer(tracerName).startActiveSpan(`retryHandler - attempt ${retryAttempts}`, (span) => {
try {
span.setAttribute("http.request.resend_count", retryAttempts);
if (delay) {
span.setAttribute("http.request.resend_delay", delay);
}
span.setAttribute("http.response.status_code", response.status);
return this.executeWithRetry(url, fetchRequestInit, retryAttempts, currentOptions, requestOptions);
}
finally {
span.end();
}
});
}
return await this.executeWithRetry(url, fetchRequestInit, retryAttempts, currentOptions, requestOptions);
}
else {
return response;
}
}
/**
* To execute the current middleware
* @param url - The request url
* @param requestInit - The request options
* @param requestOptions - The request options
* @returns A Promise that resolves to nothing
*/
execute(url, requestInit, requestOptions) {
const retryAttempts = 0;
let currentOptions = this.options;
if (requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions[RetryHandlerOptionKey]) {
currentOptions = requestOptions[RetryHandlerOptionKey];
}
const obsOptions = getObservabilityOptionsFromRequest(requestOptions);
if (obsOptions) {
return trace.getTracer(obsOptions.getTracerInstrumentationName()).startActiveSpan("retryHandler - execute", (span) => {
try {
span.setAttribute("com.microsoft.kiota.handler.retry.enable", true);
return this.executeWithRetry(url, requestInit, retryAttempts, currentOptions, requestOptions, obsOptions.getTracerInstrumentationName());
}
finally {
span.end();
}
});
}
return this.executeWithRetry(url, requestInit, retryAttempts, currentOptions, requestOptions);
}
}
/**
* A list of status codes that needs to be retried
*/
RetryHandler.RETRY_STATUS_CODES = new Set([
429, // Too many requests
503, // Service unavailable
504, // Gateway timeout
]);
/**
* A member holding the name of retry attempt header
*/
RetryHandler.RETRY_ATTEMPT_HEADER = "Retry-Attempt";
/**
* A member holding the name of retry after header
*/
RetryHandler.RETRY_AFTER_HEADER = "Retry-After";
//# sourceMappingURL=retryHandler.js.map