Skip to content

Commit bb9f18e

Browse files
authored
fix(s3): update endpoints for writeGetObjectResponse for object lambda (#3662)
* fix(s3): update endpoints for writeGetObjectResponse for object lambda * fix(middleware-sdk-s3): set chunked encoding for writeGetObjectResponse * test(client-s3): set chunked encoding for writeGetObjectResponse
1 parent efdc6a0 commit bb9f18e

File tree

7 files changed

+302
-40
lines changed

7 files changed

+302
-40
lines changed

clients/client-s3/src/commands/WriteGetObjectResponseCommand.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// smithy-typescript generated code
2+
import { getWriteGetObjectResponseEndpointPlugin } from "@aws-sdk/middleware-sdk-s3";
23
import { getSerdePlugin } from "@aws-sdk/middleware-serde";
34
import { HttpRequest as __HttpRequest, HttpResponse as __HttpResponse } from "@aws-sdk/protocol-http";
45
import { Command as $Command } from "@aws-sdk/smithy-client";
@@ -93,6 +94,7 @@ export class WriteGetObjectResponseCommand extends $Command<
9394
options?: __HttpHandlerOptions
9495
): Handler<WriteGetObjectResponseCommandInput, WriteGetObjectResponseCommandOutput> {
9596
this.middlewareStack.use(getSerdePlugin(configuration, this.serialize, this.deserialize));
97+
this.middlewareStack.use(getWriteGetObjectResponseEndpointPlugin(configuration));
9698

9799
const stack = clientStack.concat(this.middlewareStack);
98100

clients/client-s3/test/S3.spec.ts

Lines changed: 82 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { HttpRequest } from "@aws-sdk/protocol-http";
33
import { BuildMiddleware, FinalizeRequestMiddleware, SerializeMiddleware } from "@aws-sdk/types";
44
import chai from "chai";
55
import chaiAsPromised from "chai-as-promised";
6-
import { PassThrough } from "stream";
6+
import { PassThrough, Readable } from "stream";
77

88
import { S3 } from "../src/S3";
99

@@ -38,7 +38,7 @@ describe("endpoint", () => {
3838
});
3939
});
4040

41-
describe("Accesspoint ARN", async () => {
41+
describe("Endpoints from ARN", () => {
4242
const endpointValidator: BuildMiddleware<any, any> = (next, context) => (args) => {
4343
// middleware intercept the request and return it early
4444
const request = args.request as HttpRequest;
@@ -52,51 +52,93 @@ describe("Accesspoint ARN", async () => {
5252
});
5353
};
5454

55-
it("should succeed with access point ARN", async () => {
56-
const client = new S3({ region: "us-west-2" });
57-
client.middlewareStack.add(endpointValidator, { step: "build", priority: "low" });
58-
const result: any = await client.putObject({
59-
Bucket: "arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint",
60-
Key: "key",
61-
Body: "body",
55+
describe("Accesspoint ARN", async () => {
56+
it("should succeed with access point ARN", async () => {
57+
const client = new S3({ region: "us-west-2" });
58+
client.middlewareStack.add(endpointValidator, { step: "build", priority: "low" });
59+
const result: any = await client.putObject({
60+
Bucket: "arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint",
61+
Key: "key",
62+
Body: "body",
63+
});
64+
expect(result.request.hostname).to.eql("myendpoint-123456789012.s3-accesspoint.us-west-2.amazonaws.com");
6265
});
63-
expect(result.request.hostname).to.eql("myendpoint-123456789012.s3-accesspoint.us-west-2.amazonaws.com");
64-
});
6566

66-
it("should sign request with region from ARN is useArnRegion is set", async () => {
67-
const client = new S3({
68-
region: "us-east-1",
69-
useArnRegion: true,
70-
credentials: { accessKeyId: "key", secretAccessKey: "secret" },
67+
it("should sign request with region from ARN is useArnRegion is set", async () => {
68+
const client = new S3({
69+
region: "us-east-1",
70+
useArnRegion: true,
71+
credentials: { accessKeyId: "key", secretAccessKey: "secret" },
72+
});
73+
client.middlewareStack.add(endpointValidator, { step: "finalizeRequest", priority: "low" });
74+
const result: any = await client.putObject({
75+
Bucket: "arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint",
76+
Key: "key",
77+
Body: "body",
78+
});
79+
expect(result.request.hostname).to.eql("myendpoint-123456789012.s3-accesspoint.us-west-2.amazonaws.com");
80+
// Sign request with us-west-2 region from bucket access point ARN
81+
expect(result.request.headers.authorization).to.contain("/us-west-2/s3/aws4_request, SignedHeaders=");
7182
});
72-
client.middlewareStack.add(endpointValidator, { step: "finalizeRequest", priority: "low" });
73-
const result: any = await client.putObject({
74-
Bucket: "arn:aws:s3:us-west-2:123456789012:accesspoint:myendpoint",
75-
Key: "key",
76-
Body: "body",
83+
});
84+
85+
describe("Outposts ARN", () => {
86+
it("should succeed with outposts ARN", async () => {
87+
const OutpostId = "op-01234567890123456";
88+
const AccountId = "123456789012";
89+
const region = "us-west-2";
90+
const credentials = { accessKeyId: "key", secretAccessKey: "secret" };
91+
const client = new S3({ region: "us-east-1", credentials, useArnRegion: true });
92+
client.middlewareStack.add(endpointValidator, { step: "finalizeRequest", priority: "low" });
93+
const result: any = await client.putObject({
94+
Bucket: `arn:aws:s3-outposts:${region}:${AccountId}:outpost/${OutpostId}/accesspoint/abc-111`,
95+
Key: "key",
96+
Body: "body",
97+
});
98+
expect(result.request.hostname).to.eql(`abc-111-${AccountId}.${OutpostId}.s3-outposts.us-west-2.amazonaws.com`);
99+
const date = new Date().toISOString().slice(0, 10).replace(/-/g, ""); //20201029
100+
expect(result.request.headers["authorization"]).contains(
101+
`Credential=${credentials.accessKeyId}/${date}/${region}/s3-outposts/aws4_request`
102+
);
77103
});
78-
expect(result.request.hostname).to.eql("myendpoint-123456789012.s3-accesspoint.us-west-2.amazonaws.com");
79-
// Sign request with us-west-2 region from bucket access point ARN
80-
expect(result.request.headers.authorization).to.contain("/us-west-2/s3/aws4_request, SignedHeaders=");
81104
});
82105

83-
it("should succeed with outposts ARN", async () => {
84-
const OutpostId = "op-01234567890123456";
85-
const AccountId = "123456789012";
86-
const region = "us-west-2";
87-
const credentials = { accessKeyId: "key", secretAccessKey: "secret" };
88-
const client = new S3({ region: "us-east-1", credentials, useArnRegion: true });
89-
client.middlewareStack.add(endpointValidator, { step: "finalizeRequest", priority: "low" });
90-
const result: any = await client.putObject({
91-
Bucket: `arn:aws:s3-outposts:${region}:${AccountId}:outpost/${OutpostId}/accesspoint/abc-111`,
92-
Key: "key",
93-
Body: "body",
106+
describe("Object Lambda ARN", () => {
107+
it("should succeed with Object Lambda ARN", async () => {
108+
const region = "us-east-1";
109+
const credentials = { accessKeyId: "key", secretAccessKey: "secret" };
110+
const client = new S3({ region, credentials });
111+
client.middlewareStack.add(endpointValidator, { step: "finalizeRequest", priority: "low" });
112+
const result: any = await client.putObject({
113+
Bucket: "arn:aws:s3-object-lambda:us-east-1:123456789012:accesspoint/mybanner",
114+
Key: "key",
115+
Body: "body",
116+
});
117+
const date = new Date().toISOString().slice(0, 10).replace(/-/g, ""); //20201029
118+
expect(result.request.hostname).to.eql("mybanner-123456789012.s3-object-lambda.us-east-1.amazonaws.com");
119+
expect(result.request.headers["authorization"]).contains(
120+
`Credential=${credentials.accessKeyId}/${date}/${region}/s3-object-lambda/aws4_request`
121+
);
122+
});
123+
124+
it("should update endpoint of WriteGetObjectResponse command", async () => {
125+
const region = "us-east-1";
126+
const credentials = { accessKeyId: "key", secretAccessKey: "secret" };
127+
const client = new S3({ region, credentials });
128+
client.middlewareStack.add(endpointValidator, { step: "finalizeRequest", priority: "low" });
129+
const requestRoute = "route123";
130+
const result: any = await client.writeGetObjectResponse({
131+
RequestRoute: requestRoute,
132+
RequestToken: "token",
133+
Body: new Readable(),
134+
});
135+
const date = new Date().toISOString().slice(0, 10).replace(/-/g, ""); //20201029
136+
expect(result.request.hostname).to.eql(`${requestRoute}.s3-object-lambda.us-east-1.amazonaws.com`);
137+
expect(result.request.headers["authorization"]).contains(
138+
`Credential=${credentials.accessKeyId}/${date}/${region}/s3-object-lambda/aws4_request`
139+
);
140+
expect(result.request.headers["transfer-encoding"]).to.equal("chunked");
94141
});
95-
expect(result.request.hostname).to.eql(`abc-111-${AccountId}.${OutpostId}.s3-outposts.us-west-2.amazonaws.com`);
96-
const date = new Date().toISOString().slice(0, 10).replace(/-/g, ""); //20201029
97-
expect(result.request.headers["authorization"]).contains(
98-
`Credential=${credentials.accessKeyId}/${date}/${region}/s3-outposts/aws4_request`
99-
);
100142
});
101143
});
102144

codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddS3Config.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,12 @@ public List<RuntimeClientPlugin> getClientPlugins() {
172172
(m, s, o) -> EXCEPTIONS_OF_200_OPERATIONS.contains(o.getId().getName(s))
173173
&& testServiceId(s))
174174
.build(),
175+
RuntimeClientPlugin.builder()
176+
.withConventions(AwsDependency.S3_MIDDLEWARE.dependency,
177+
"WriteGetObjectResponseEndpoint", HAS_MIDDLEWARE)
178+
.operationPredicate((m, s, o) -> testServiceId(s)
179+
&& o.getId().getName(s).equals("WriteGetObjectResponse"))
180+
.build(),
175181
RuntimeClientPlugin.builder()
176182
.withConventions(AwsDependency.ADD_EXPECT_CONTINUE.dependency, "AddExpectContinue",
177183
HAS_MIDDLEWARE)

packages/middleware-sdk-s3/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
},
2020
"license": "Apache-2.0",
2121
"dependencies": {
22+
"@aws-sdk/middleware-bucket-endpoint": "*",
2223
"@aws-sdk/protocol-http": "*",
2324
"@aws-sdk/types": "*",
2425
"@aws-sdk/util-arn-parser": "*",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from "./check-content-length-header";
22
export * from "./throw-200-exceptions";
33
export * from "./validate-bucket-name";
4+
export * from "./write-get-object-response-endpoint";
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { HttpRequest } from "@aws-sdk/protocol-http";
2+
3+
import { writeGetObjectResponseEndpointMiddleware } from "./write-get-object-response-endpoint";
4+
5+
describe("writeGetObjectResponseEndpointMiddlewareOptions", () => {
6+
const mockNextHandler = jest.fn();
7+
const mockRegionProvider = jest.fn();
8+
const mockConfig = {
9+
region: mockRegionProvider,
10+
isCustomEndpoint: false,
11+
disableHostPrefix: false,
12+
runtime: "node",
13+
};
14+
15+
beforeEach(() => {
16+
jest.clearAllMocks();
17+
});
18+
19+
it.each(["s3.amazonaws.com", "s3-external-1.amazonaws.com"])(
20+
"should not update endpoint for global endpoints %s",
21+
async (hostname) => {
22+
const context = {} as any;
23+
const handler = writeGetObjectResponseEndpointMiddleware(mockConfig)(mockNextHandler, context);
24+
await handler({
25+
request: new HttpRequest({ hostname }),
26+
input: {
27+
RequestRoute: "route",
28+
},
29+
});
30+
expect(mockNextHandler.mock.calls.length).toBe(1);
31+
expect(mockNextHandler.mock.calls[0][0].request.hostname).toEqual(hostname);
32+
expect(context).toEqual({});
33+
mockNextHandler.mockClear();
34+
}
35+
);
36+
37+
it.each([
38+
["us-west-2", "s3.us-west-2.amazonaws.com", "route1", "route1.s3-object-lambda.us-west-2.amazonaws.com"],
39+
["us-east-1", "s3.us-east-1.amazonaws.com", "route2", "route2.s3-object-lambda.us-east-1.amazonaws.com"],
40+
[
41+
"cn-northeast-1",
42+
"s3.cn-northeast-1.amazonaws.com.cn",
43+
"route",
44+
"route.s3-object-lambda.cn-northeast-1.amazonaws.com.cn",
45+
],
46+
])("should update endpoint for regional endpoints", async (region, originalEndpoint, requestRoute, expected) => {
47+
const context = {} as any;
48+
mockRegionProvider.mockResolvedValueOnce(region);
49+
const handler = writeGetObjectResponseEndpointMiddleware(mockConfig)(mockNextHandler, context);
50+
await handler({
51+
request: new HttpRequest({ hostname: originalEndpoint }),
52+
input: {
53+
RequestRoute: requestRoute,
54+
},
55+
});
56+
expect(mockNextHandler.mock.calls.length).toBe(1);
57+
expect(mockNextHandler.mock.calls[0][0].request.hostname).toBe(expected);
58+
expect(context).toMatchObject({ signing_service: "s3-object-lambda" });
59+
expect(mockNextHandler.mock.calls[0][0].request.headers["transfer-encoding"]).toBe("chunked");
60+
mockNextHandler.mockClear();
61+
});
62+
63+
it("should update endpoint for custom endpoints", async () => {
64+
const context = {} as any;
65+
const handler = writeGetObjectResponseEndpointMiddleware({ ...mockConfig, isCustomEndpoint: true })(
66+
mockNextHandler,
67+
context
68+
);
69+
await handler({
70+
request: new HttpRequest({ hostname: "my-endpoint.com" }),
71+
input: {
72+
RequestRoute: "route",
73+
},
74+
});
75+
expect(mockNextHandler.mock.calls.length).toBe(1);
76+
expect(mockNextHandler.mock.calls[0][0].request.hostname).toBe("route.my-endpoint.com");
77+
expect(mockNextHandler.mock.calls[0][0].request.headers["transfer-encoding"]).toBe("chunked");
78+
expect(context).toMatchObject({ signing_service: "s3-object-lambda" });
79+
mockNextHandler.mockClear();
80+
});
81+
82+
it("should not prepend requestRoute parameter if disableHostPrefix is set", async () => {
83+
mockRegionProvider.mockResolvedValueOnce("us-west-2");
84+
const handler = writeGetObjectResponseEndpointMiddleware({ ...mockConfig, disableHostPrefix: true })(
85+
mockNextHandler,
86+
{} as any
87+
);
88+
await handler({
89+
request: new HttpRequest({ hostname: "s3.us-west-2.amazonaws.com" }),
90+
input: {
91+
RequestRoute: "route",
92+
},
93+
});
94+
expect(mockNextHandler.mock.calls.length).toBe(1);
95+
expect(mockNextHandler.mock.calls[0][0].request.hostname).toBe("s3-object-lambda.us-west-2.amazonaws.com");
96+
});
97+
98+
it("should not set chunked encoding if content-length is already set", async () => {
99+
const context = {} as any;
100+
const handler = writeGetObjectResponseEndpointMiddleware({ ...mockConfig })(mockNextHandler, context);
101+
const headers = { "content-length": "123" };
102+
await handler({
103+
request: new HttpRequest({ hostname: "s3.us-west-2.amazonaws.com", headers }),
104+
input: {
105+
RequestRoute: "route",
106+
},
107+
});
108+
expect(mockNextHandler.mock.calls.length).toBe(1);
109+
expect(mockNextHandler.mock.calls[0][0].request.headers).toEqual(headers);
110+
});
111+
112+
it("should not set the chunked encoding for non Node.js runtime", async () => {
113+
const context = {} as any;
114+
const handler = writeGetObjectResponseEndpointMiddleware({ ...mockConfig, runtime: "browser" })(
115+
mockNextHandler,
116+
context
117+
);
118+
await handler({
119+
request: new HttpRequest({ hostname: "s3.us-west-2.amazonaws.com" }),
120+
input: {
121+
RequestRoute: "route",
122+
},
123+
});
124+
expect(mockNextHandler.mock.calls.length).toBe(1);
125+
expect(mockNextHandler.mock.calls[0][0].request.headers["transfer-encoding"]).not.toBeDefined();
126+
});
127+
});

0 commit comments

Comments
 (0)