-
Notifications
You must be signed in to change notification settings - Fork 587
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(middleware-sdk-s3): add string fallback for S3#Expires field (#…
- Loading branch information
Showing
10 changed files
with
293 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
126 changes: 126 additions & 0 deletions
126
packages/middleware-sdk-s3/src/s3-expires-middleware.e2e.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import { S3 } from "@aws-sdk/client-s3"; | ||
import { GetCallerIdentityCommandOutput, STS } from "@aws-sdk/client-sts"; | ||
|
||
jest.setTimeout(25000); | ||
|
||
describe("S3 Expires e2e test", () => { | ||
const s3 = new S3({ | ||
region: "us-west-2", | ||
logger: { | ||
trace() {}, | ||
debug() {}, | ||
info() {}, | ||
warn: jest.fn(), | ||
error() {}, | ||
}, | ||
}); | ||
const stsClient = new STS({ region: "us-west-2" }); | ||
|
||
let callerID = null as unknown as GetCallerIdentityCommandOutput; | ||
let Bucket: string; | ||
|
||
// random element limited to 2 letters to avoid concurrent IO, and | ||
// to limit bucket count to 676 if there is failure to delete them. | ||
const alphabet = "abcdefghijklmnopqrstuvwxyz"; | ||
const randId = alphabet[(Math.random() * alphabet.length) | 0] + alphabet[(Math.random() * alphabet.length) | 0]; | ||
|
||
beforeAll(async () => { | ||
callerID = await stsClient.getCallerIdentity({}); | ||
Bucket = `${callerID.Account}-${randId}-s3-expires`; | ||
await s3.createBucket({ | ||
Bucket, | ||
}); | ||
}); | ||
|
||
afterAll(async () => { | ||
await deleteBucket(s3, Bucket); | ||
}); | ||
|
||
const staticDate = new Date(0); | ||
const dateString = "Thu, 01 Jan 1970 00:00:00 GMT"; | ||
|
||
it("should parse Expires from response if it is valid date-time, and include ExpiresString", async () => { | ||
await s3.putObject({ | ||
Bucket, | ||
Key: "good-expires", | ||
Expires: staticDate, | ||
Body: "good-expires", | ||
}); | ||
|
||
const get = await s3.getObject({ | ||
Bucket, | ||
Key: "good-expires", | ||
}); | ||
await get.Body?.transformToByteArray(); // drain stream. | ||
|
||
expect(get.Expires?.getTime()).toEqual(staticDate.getTime()); | ||
expect(get.ExpiresString).toEqual(dateString); | ||
}); | ||
|
||
it("should fail with a non-blocking warning if Expires is not a valid date-time, and include the raw string in ExpiresString", async () => { | ||
await s3.putObject({ | ||
Bucket, | ||
Key: "bad-expires", | ||
Expires: new Date("invalid date"), | ||
Body: "bad-expires", | ||
}); | ||
|
||
const get = await s3.getObject({ | ||
Bucket, | ||
Key: "bad-expires", | ||
}); | ||
await get.Body?.transformToByteArray(); // drain stream. | ||
|
||
expect(get.Expires).toBeUndefined(); | ||
expect(s3.config.logger.warn).toHaveBeenCalledWith( | ||
`AWS SDK Warning for S3Client::GetObjectCommand response parsing (undefined, NaN undefined NaN NaN:NaN:NaN GMT): TypeError: Invalid RFC-7231 date-time value` | ||
); | ||
expect(get.ExpiresString).toEqual("undefined, NaN undefined NaN NaN:NaN:NaN GMT"); | ||
}); | ||
}); | ||
|
||
async function deleteBucket(s3: S3, bucketName: string) { | ||
const Bucket = bucketName; | ||
|
||
try { | ||
await s3.headBucket({ | ||
Bucket, | ||
}); | ||
} catch (e) { | ||
return; | ||
} | ||
|
||
const list = await s3 | ||
.listObjects({ | ||
Bucket, | ||
}) | ||
.catch((e) => { | ||
if (!String(e).includes("NoSuchBucket")) { | ||
throw e; | ||
} | ||
return { | ||
Contents: [], | ||
}; | ||
}); | ||
|
||
const promises = [] as any[]; | ||
for (const key of list.Contents ?? []) { | ||
promises.push( | ||
s3.deleteObject({ | ||
Bucket, | ||
Key: key.Key, | ||
}) | ||
); | ||
} | ||
await Promise.all(promises); | ||
|
||
try { | ||
return await s3.deleteBucket({ | ||
Bucket, | ||
}); | ||
} catch (e) { | ||
if (!String(e).includes("NoSuchBucket")) { | ||
throw e; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { HttpResponse } from "@smithy/protocol-http"; | ||
import { parseRfc7231DateTime } from "@smithy/smithy-client"; | ||
import { | ||
DeserializeHandler, | ||
DeserializeHandlerArguments, | ||
DeserializeHandlerOutput, | ||
DeserializeMiddleware, | ||
HandlerExecutionContext, | ||
MetadataBearer, | ||
Pluggable, | ||
RelativeMiddlewareOptions, | ||
} from "@smithy/types"; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
interface PreviouslyResolved {} | ||
|
||
/** | ||
* @internal | ||
* | ||
* From the S3 Expires compatibility spec. | ||
* A model transform will ensure S3#Expires remains a timestamp shape, though | ||
* it is deprecated. | ||
* If a particular object has a non-date string set as the Expires value, | ||
* the SDK will have the raw string as "ExpiresString" on the response. | ||
* | ||
*/ | ||
export const s3ExpiresMiddleware = (config: PreviouslyResolved): DeserializeMiddleware<any, any> => { | ||
return <Output extends MetadataBearer>( | ||
next: DeserializeHandler<any, Output>, | ||
context: HandlerExecutionContext | ||
): DeserializeHandler<any, Output> => | ||
async (args: DeserializeHandlerArguments<any>): Promise<DeserializeHandlerOutput<Output>> => { | ||
const result = await next(args); | ||
const { response } = result; | ||
if (HttpResponse.isInstance(response)) { | ||
if (response.headers.expires) { | ||
response.headers.expiresstring = response.headers.expires; | ||
try { | ||
parseRfc7231DateTime(response.headers.expires); | ||
} catch (e) { | ||
context.logger?.warn( | ||
`AWS SDK Warning for ${context.clientName}::${context.commandName} response parsing (${response.headers.expires}): ${e}` | ||
); | ||
delete response.headers.expires; | ||
} | ||
} | ||
} | ||
return result; | ||
}; | ||
}; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export const s3ExpiresMiddlewareOptions: RelativeMiddlewareOptions = { | ||
tags: ["S3"], | ||
name: "s3ExpiresMiddleware", | ||
override: true, | ||
relation: "after", | ||
toMiddleware: "deserializerMiddleware", | ||
}; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export const getS3ExpiresMiddlewarePlugin = (clientConfig: PreviouslyResolved): Pluggable<any, any> => ({ | ||
applyToStack: (clientStack) => { | ||
clientStack.addRelativeTo(s3ExpiresMiddleware(clientConfig), s3ExpiresMiddlewareOptions); | ||
}, | ||
}); |
Oops, something went wrong.