UNPKG

awscdk-construct-scte-scheduler

Version:

AWS CDK Construct for scheduling SCTE-35 events using the MediaLive schedule API

110 lines (109 loc) 4.84 kB
import { NoOpLogger } from "@smithy/core/client"; import { HttpRequest } from "@smithy/core/protocols"; import { v4 } from "@smithy/core/serde"; import { isServerError, isThrottlingError, isTransientError, } from "../service-error-classification/service-error-classification"; import { INVOCATION_ID_HEADER, REQUEST_HEADER } from "../util-retry/constants"; import { parseRetryAfterHeader } from "./parseRetryAfterHeader"; import { asSdkError } from "./util"; export function bindRetryMiddleware(isStreamingPayload) { return (options) => (next, context) => async (args) => { let retryStrategy = await options.retryStrategy(); const maxAttempts = await options.maxAttempts(); if (isRetryStrategyV2(retryStrategy)) { retryStrategy = retryStrategy; let retryToken = await retryStrategy.acquireInitialRetryToken((context["partition_id"] ?? "") + (context.__retryLongPoll ? ":longpoll" : "")); let lastError = new Error(); let attempts = 0; let totalRetryDelay = 0; const { request } = args; const isRequest = HttpRequest.isInstance(request); if (isRequest) { request.headers[INVOCATION_ID_HEADER] = v4(); } while (true) { try { if (isRequest) { request.headers[REQUEST_HEADER] = `attempt=${attempts + 1}; max=${maxAttempts}`; } const { response, output } = await next(args); retryStrategy.recordSuccess(retryToken); output.$metadata.attempts = attempts + 1; output.$metadata.totalRetryDelay = totalRetryDelay; return { response, output }; } catch (e) { const retryErrorInfo = getRetryErrorInfo(e, options.logger); lastError = asSdkError(e); if (isRequest && isStreamingPayload(request)) { (context.logger instanceof NoOpLogger ? console : context.logger)?.warn("An error was encountered in a non-retryable streaming request."); throw lastError; } try { retryToken = await retryStrategy.refreshRetryTokenForRetry(retryToken, retryErrorInfo); } catch (refreshError) { if (typeof refreshError.$backoff === "number") { await cooldown(refreshError.$backoff); } if (!lastError.$metadata) { lastError.$metadata = {}; } lastError.$metadata.attempts = attempts + 1; lastError.$metadata.totalRetryDelay = totalRetryDelay; throw lastError; } attempts = retryToken.getRetryCount(); const delay = retryToken.getRetryDelay(); totalRetryDelay += delay; await cooldown(delay); } } } else { retryStrategy = retryStrategy; if (retryStrategy?.mode) { context.userAgent = [...(context.userAgent || []), ["cfg/retry-mode", retryStrategy.mode]]; } return retryStrategy.retry(next, args); } }; } const cooldown = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const isRetryStrategyV2 = (retryStrategy) => typeof retryStrategy.acquireInitialRetryToken !== "undefined" && typeof retryStrategy.refreshRetryTokenForRetry !== "undefined" && typeof retryStrategy.recordSuccess !== "undefined"; const getRetryErrorInfo = (error, logger) => { const errorInfo = { error, errorType: getRetryErrorType(error), }; const retryAfterHint = parseRetryAfterHeader(error.$response, logger); if (retryAfterHint) { errorInfo.retryAfterHint = retryAfterHint; } return errorInfo; }; const getRetryErrorType = (error) => { if (isThrottlingError(error)) return "THROTTLING"; if (isTransientError(error)) return "TRANSIENT"; if (isServerError(error)) return "SERVER_ERROR"; return "CLIENT_ERROR"; }; export const retryMiddlewareOptions = { name: "retryMiddleware", tags: ["RETRY"], step: "finalizeRequest", priority: "high", override: true, }; export function bindGetRetryPlugin(isStreamingPayload) { const retryMiddleware = bindRetryMiddleware(isStreamingPayload); return (options) => ({ applyToStack: (clientStack) => { clientStack.add(retryMiddleware(options), retryMiddlewareOptions); }, }); }