Skip to content

Commit

Permalink
feat(middleware-sdk-s3): add middleware for following region redirects
Browse files Browse the repository at this point in the history
  • Loading branch information
siddsriv committed Sep 7, 2023
1 parent fa8ecad commit 30b56e0
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/middleware-sdk-s3/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./check-content-length-header";
export * from "./region-redirect-middleware";
export * from "./s3Configuration";
export * from "./throw-200-exceptions";
export * from "./validate-bucket-name";
117 changes: 117 additions & 0 deletions packages/middleware-sdk-s3/src/region-redirect-middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import {
HandlerExecutionContext,
InitializeHandler,
InitializeHandlerArguments,
InitializeHandlerOptions,
InitializeHandlerOutput,
InitializeMiddleware,
MetadataBearer,
Pluggable,
Provider,
RelativeMiddlewareOptions,
SerializeHandler,
SerializeHandlerArguments,
SerializeHandlerOutput,
SerializeMiddleware,
} from "@smithy/types";

/**
* @internal
*/
interface PreviouslyResolved {
region: Provider<string>;
followRegionRedirects: boolean;
}

/**
* @internal
*/
export function regionRedirectMiddleware(clientConfig: PreviouslyResolved): InitializeMiddleware<any, any> {
return <Output extends MetadataBearer>(
next: InitializeHandler<any, Output>,
context: HandlerExecutionContext
): InitializeHandler<any, Output> =>
async (args: InitializeHandlerArguments<any>): Promise<InitializeHandlerOutput<Output>> => {
try {
return next(args);
} catch (err) {
if (
clientConfig.followRegionRedirects &&
err.Code === "PermanentRedirect" &&
err.$metadata.httpStatusCode === 301
) {
try {
const actualRegion = err.$response.headers["x-amz-bucket-region"];
context.logger?.debug(`Redirecting from ${await clientConfig.region()} to ${actualRegion}`);
context.__s3RegionRedirect = actualRegion;
} catch (e) {
throw new Error("Region redirect failed: " + e);
}
return next(args);
} else {
throw err;
}
}
};
}

/**
* @internal
*/
export const regionRedirectEndpointMiddleware = (config: PreviouslyResolved): SerializeMiddleware<any, any> => {
return <Output extends MetadataBearer>(
next: SerializeHandler<any, Output>,
context: HandlerExecutionContext
): SerializeHandler<any, Output> =>
async (args: SerializeHandlerArguments<any>): Promise<SerializeHandlerOutput<Output>> => {
const originalRegion = await config.region();
if (context.__s3RegionRedirect) {
const regionProviderRef = config.region;
config.region = async () => {
config.region = regionProviderRef;
return context.__s3RegionRedirect;
};
}
const result = await next({
...args,
});
if (context.__s3RegionRedirect) {
const region = await config.region();
if (originalRegion !== region) {
throw new Error("Region was not restored following S3 region redirect.");
}
}
return result;
};
};

/**
* @internal
*/
export const regionRedirectMiddlewareOptions: InitializeHandlerOptions = {
step: "initialize",
tags: ["REGION_REDIRECT", "S3"],
name: "regionRedirectMiddleware",
override: true,
};

/**
* @internal
*/
export const regionRedirectEndpointMiddlewareOptions: RelativeMiddlewareOptions = {
tags: ["REGION_REDIRECT", "S3"],
name: "regionRedirectEndpointMiddleware",
override: true,
relation: "before",
toMiddleware: "endpointV2Middleware",
};

/**
* @internal
*/
export const getRegionRedirectMiddlewarePlugin = (clientConfig: PreviouslyResolved): Pluggable<any, any> => ({
applyToStack: (clientStack) => {
clientStack.add(regionRedirectMiddleware(clientConfig), regionRedirectMiddlewareOptions);
clientStack.addRelativeTo(regionRedirectEndpointMiddleware(clientConfig), regionRedirectEndpointMiddlewareOptions);
},
});
9 changes: 8 additions & 1 deletion packages/middleware-sdk-s3/src/s3Configuration.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* @public
*
*
* All endpoint parameters with built-in bindings of AWS::S3::*
*/
export interface S3InputConfig {
Expand All @@ -17,17 +17,24 @@ export interface S3InputConfig {
* Whether multi-region access points (MRAP) should be disabled.
*/
disableMultiregionAccessPoints?: boolean;
/**
* If you receive a permanent redirect with status 301,
* the client will retry your request with the corrected region.
*/
followRegionRedirects?: boolean;
}

export interface S3ResolvedConfig {
forcePathStyle: boolean;
useAccelerateEndpoint: boolean;
disableMultiregionAccessPoints: boolean;
followRegionRedirects: boolean;
}

export const resolveS3Config = <T>(input: T & S3InputConfig): T & S3ResolvedConfig => ({
...input,
forcePathStyle: input.forcePathStyle ?? false,
useAccelerateEndpoint: input.useAccelerateEndpoint ?? false,
disableMultiregionAccessPoints: input.disableMultiregionAccessPoints ?? false,
followRegionRedirects: input.followRegionRedirects ?? false,
});

0 comments on commit 30b56e0

Please sign in to comment.