UNPKG

minio

Version:

S3 Compatible Cloud Storage client

101 lines (86 loc) 3.01 kB
import type * as http from 'node:http' import type * as https from 'node:https' import type * as stream from 'node:stream' import { pipeline } from 'node:stream' import { promisify } from 'node:util' import type { Transport } from './type.ts' const pipelineAsync = promisify(pipeline) export async function request( transport: Transport, opt: https.RequestOptions, body: Buffer | string | stream.Readable | null = null, ): Promise<http.IncomingMessage> { return new Promise<http.IncomingMessage>((resolve, reject) => { const requestObj = transport.request(opt, (response) => { resolve(response) }) requestObj.on('error', reject) if (!body || Buffer.isBuffer(body) || typeof body === 'string') { requestObj.end(body) } else { pipelineAsync(body, requestObj).catch(reject) } }) } const MAX_RETRIES = 10 const EXP_BACK_OFF_BASE_DELAY = 1000 // Base delay for exponential backoff const ADDITIONAL_DELAY_FACTOR = 1.0 // to avoid synchronized retries // Retryable error codes for HTTP ( ref: minio-go) export const retryHttpCodes: Record<string, boolean> = { 408: true, 429: true, 499: true, 500: true, 502: true, 503: true, 504: true, 520: true, } const isHttpRetryable = (httpResCode: number) => { return retryHttpCodes[httpResCode] !== undefined } const sleep = (ms: number) => { return new Promise((resolve) => setTimeout(resolve, ms)) } const getExpBackOffDelay = (retryCount: number) => { const backOffBy = EXP_BACK_OFF_BASE_DELAY * 2 ** retryCount const additionalDelay = Math.random() * backOffBy * ADDITIONAL_DELAY_FACTOR return backOffBy + additionalDelay } export async function requestWithRetry( transport: Transport, opt: https.RequestOptions, body: Buffer | string | stream.Readable | null = null, maxRetries: number = MAX_RETRIES, ): Promise<http.IncomingMessage> { let attempt = 0 let isRetryable = false while (attempt <= maxRetries) { try { const response = await request(transport, opt, body) // Check if the HTTP status code is retryable if (isHttpRetryable(response.statusCode as number)) { isRetryable = true throw new Error(`Retryable HTTP status: ${response.statusCode}`) // trigger retry attempt with calculated delay } return response // Success, return the raw response } catch (err) { if (isRetryable) { attempt++ isRetryable = false if (attempt > maxRetries) { throw new Error(`Request failed after ${maxRetries} retries: ${err}`) } const delay = getExpBackOffDelay(attempt) // eslint-disable-next-line no-console console.warn( `${new Date().toLocaleString()} Retrying request (attempt ${attempt}/${maxRetries}) after ${delay}ms due to: ${err}`, ) await sleep(delay) } else { throw err // re-throw if any request, syntax errors } } } throw new Error(`${MAX_RETRIES} Retries exhausted, request failed.`) }