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
JavaScript
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);
},
});
}