Skip to content

Commit 01ee0f2

Browse files
authored
feat(client-s3): make expect continue header configurable (#7450)
* feat(client-s3): make expect continue header configurable * test: update integ tests for expect 100-continue
1 parent cc4845b commit 01ee0f2

File tree

8 files changed

+157
-20
lines changed

8 files changed

+157
-20
lines changed

packages/middleware-expect-continue/src/index.spec.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ describe("addExpectContinueMiddleware", () => {
1111
});
1212

1313
it("sets the Expect header to 100-continue if there is a request body in node runtime", async () => {
14-
const handler = addExpectContinueMiddleware({ runtime: "node" })(mockNextHandler, {} as any);
14+
const handler = addExpectContinueMiddleware({ runtime: "node", expectContinueHeader: true })(
15+
mockNextHandler,
16+
{} as any
17+
);
1518
await handler({
1619
input: {},
1720
request: new HttpRequest({
@@ -27,7 +30,10 @@ describe("addExpectContinueMiddleware", () => {
2730
});
2831

2932
it("does not set the Expect header to 100-continue if there is no request body in node runtime", async () => {
30-
const handler = addExpectContinueMiddleware({ runtime: "node" })(mockNextHandler, {} as any);
33+
const handler = addExpectContinueMiddleware({ runtime: "node", expectContinueHeader: true })(
34+
mockNextHandler,
35+
{} as any
36+
);
3137
await handler({
3238
input: {},
3339
request: new HttpRequest({
@@ -42,7 +48,10 @@ describe("addExpectContinueMiddleware", () => {
4248
});
4349

4450
it("does not set the Expect header to 100-continue for browser runtime", async () => {
45-
const handler = addExpectContinueMiddleware({ runtime: "browser" })(mockNextHandler, {} as any);
51+
const handler = addExpectContinueMiddleware({ runtime: "browser", expectContinueHeader: true })(
52+
mockNextHandler,
53+
{} as any
54+
);
4655
await handler({
4756
input: {},
4857
request: new HttpRequest({

packages/middleware-expect-continue/src/index.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { HttpHandler, HttpRequest } from "@smithy/protocol-http";
2-
import {
2+
import type {
3+
BodyLengthCalculator,
34
BuildHandler,
45
BuildHandlerArguments,
56
BuildHandlerOptions,
@@ -13,20 +14,43 @@ import {
1314
interface PreviouslyResolved {
1415
runtime: string;
1516
requestHandler?: RequestHandler<any, any, any> | HttpHandler<any>;
17+
bodyLengthChecker?: BodyLengthCalculator;
18+
expectContinueHeader?: boolean | number;
1619
}
1720

1821
export function addExpectContinueMiddleware(options: PreviouslyResolved): BuildMiddleware<any, any> {
1922
return <Output extends MetadataBearer>(next: BuildHandler<any, Output>): BuildHandler<any, Output> =>
2023
async (args: BuildHandlerArguments<any>): Promise<BuildHandlerOutput<Output>> => {
2124
const { request } = args;
22-
if (HttpRequest.isInstance(request) && request.body && options.runtime === "node") {
23-
if (options.requestHandler?.constructor?.name !== "FetchHttpHandler") {
24-
request.headers = {
25-
...request.headers,
26-
Expect: "100-continue",
27-
};
25+
26+
if (
27+
options.expectContinueHeader !== false &&
28+
HttpRequest.isInstance(request) &&
29+
request.body &&
30+
options.runtime === "node" &&
31+
options.requestHandler?.constructor?.name !== "FetchHttpHandler"
32+
) {
33+
let sendHeader = true;
34+
if (typeof options.expectContinueHeader === "number") {
35+
try {
36+
const bodyLength =
37+
Number(request.headers?.["content-length"]) ?? options.bodyLengthChecker?.(request.body) ?? Infinity;
38+
sendHeader = bodyLength >= options.expectContinueHeader;
39+
console.log({
40+
sendHeader,
41+
bodyLength,
42+
threshold: options.expectContinueHeader,
43+
});
44+
} catch (e) {}
45+
} else {
46+
sendHeader = !!options.expectContinueHeader;
47+
}
48+
49+
if (sendHeader) {
50+
request.headers.Expect = "100-continue";
2851
}
2952
}
53+
3054
return next({
3155
...args,
3256
request,

packages/middleware-expect-continue/src/middleware-expect-continue.integ.spec.ts

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,88 @@ import { describe, expect, test as it } from "vitest";
55
describe("middleware-expect-continue", () => {
66
describe(S3.name, () => {
77
it("should not set expect header if there is no body", async () => {
8-
const client = new S3({ region: "us-west-2" });
9-
8+
const client = new S3({ region: "us-west-2", expectContinueHeader: true });
109
requireRequestsFrom(client).toMatch({
1110
headers: {
1211
Expect: /undefined/,
1312
},
1413
});
15-
1614
await client.listBuckets({});
17-
1815
expect.assertions(1);
1916
});
2017

2118
it("should set expect header if there is a body", async () => {
22-
const client = new S3({ region: "us-west-2" });
23-
19+
const client = new S3({ region: "us-west-2", expectContinueHeader: 4 });
2420
requireRequestsFrom(client).toMatch({
2521
headers: {
2622
Expect: /100-continue/,
2723
},
2824
});
29-
3025
await client.putObject({
3126
Bucket: "b",
3227
Key: "k",
3328
Body: Buffer.from("abcd"),
3429
});
35-
3630
expect.assertions(1);
3731
});
32+
33+
describe("should set or omit expect header based on configurations", () => {
34+
it("false", async () => {
35+
const client = new S3({ region: "us-west-2", expectContinueHeader: false });
36+
requireRequestsFrom(client).toMatch({
37+
headers: {
38+
Expect: /undefined/,
39+
},
40+
});
41+
await client.putObject({
42+
Bucket: "b",
43+
Key: "k",
44+
Body: Buffer.from("abcd"),
45+
});
46+
expect.assertions(1);
47+
});
48+
it("5", async () => {
49+
const client = new S3({ region: "us-west-2", expectContinueHeader: 5 });
50+
requireRequestsFrom(client).toMatch({
51+
headers: {
52+
Expect: /undefined/,
53+
},
54+
});
55+
await client.putObject({
56+
Bucket: "b",
57+
Key: "k",
58+
Body: Buffer.from("abcd"),
59+
});
60+
expect.assertions(1);
61+
});
62+
it("true", async () => {
63+
const client = new S3({ region: "us-west-2", expectContinueHeader: true });
64+
requireRequestsFrom(client).toMatch({
65+
headers: {
66+
Expect: /100-continue/,
67+
},
68+
});
69+
await client.putObject({
70+
Bucket: "b",
71+
Key: "k",
72+
Body: Buffer.from("abcd"),
73+
});
74+
expect.assertions(1);
75+
});
76+
it("4", async () => {
77+
const client = new S3({ region: "us-west-2", expectContinueHeader: 4 });
78+
requireRequestsFrom(client).toMatch({
79+
headers: {
80+
Expect: /100-continue/,
81+
},
82+
});
83+
await client.putObject({
84+
Bucket: "b",
85+
Key: "k",
86+
Body: Buffer.from("abcd"),
87+
});
88+
expect.assertions(1);
89+
});
90+
});
3891
});
3992
});

packages/middleware-flexible-checksums/src/middleware-flexible-checksums.integ.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ describe("middleware-flexible-checksums", () => {
6565
...(body.length
6666
? {
6767
"content-length": body.length.toString(),
68-
Expect: "100-continue",
6968
}
7069
: {}),
7170
...(requestChecksumCalculation === RequestChecksumCalculation.WHEN_REQUIRED &&

packages/middleware-sdk-s3/src/s3Configuration.spec.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,37 @@ describe(resolveS3Config.name, () => {
1111
})
1212
).toBe(input);
1313
});
14+
15+
it("accepts bool/num for expectContinueHeader and defaults to 2mb", () => {
16+
expect(
17+
resolveS3Config(
18+
{
19+
expectContinueHeader: 1,
20+
},
21+
{
22+
session: [() => null, vi.fn()],
23+
}
24+
).expectContinueHeader
25+
).toEqual(1);
26+
27+
expect(
28+
resolveS3Config(
29+
{
30+
expectContinueHeader: false,
31+
},
32+
{
33+
session: [() => null, vi.fn()],
34+
}
35+
).expectContinueHeader
36+
).toEqual(false);
37+
38+
expect(
39+
resolveS3Config(
40+
{},
41+
{
42+
session: [() => null, vi.fn()],
43+
}
44+
).expectContinueHeader
45+
).toEqual(2 * 1024 * 1024);
46+
});
1447
});

packages/middleware-sdk-s3/src/s3Configuration.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,22 @@ export interface S3InputConfig {
3535
* Whether to use the bucket name as the endpoint for this client.
3636
*/
3737
bucketEndpoint?: boolean;
38+
/**
39+
* This field configures the SDK's behavior around setting the `expect: 100-continue` header.
40+
*
41+
* Default: 2_097_152 (2 MB)
42+
*
43+
* When given as a boolean - always send or omit the header.
44+
* When given as a number - minimum byte threshold of the payload before setting the header.
45+
* Unmeasurable payload sizes (streams) will set the header too.
46+
*
47+
* The `expect: 100-continue` header is used to allow the server a chance to validate the PUT request
48+
* headers before the client begins to send the object payload. This avoids wasteful data transmission for a
49+
* request that is rejected.
50+
*
51+
* However, there is a trade-off where the request will take longer to complete.
52+
*/
53+
expectContinueHeader?: boolean | number;
3854
}
3955

4056
/**
@@ -58,6 +74,7 @@ export interface S3ResolvedConfig {
5874
followRegionRedirects: boolean;
5975
s3ExpressIdentityProvider: S3ExpressIdentityProvider;
6076
bucketEndpoint: boolean;
77+
expectContinueHeader: boolean | number;
6178
}
6279

6380
export const resolveS3Config = <T>(
@@ -76,6 +93,7 @@ export const resolveS3Config = <T>(
7693
followRegionRedirects,
7794
s3ExpressIdentityProvider,
7895
bucketEndpoint,
96+
expectContinueHeader,
7997
} = input;
8098

8199
return Object.assign(input, {
@@ -93,5 +111,6 @@ export const resolveS3Config = <T>(
93111
)
94112
),
95113
bucketEndpoint: bucketEndpoint ?? false,
114+
expectContinueHeader: expectContinueHeader ?? 2_097_152,
96115
});
97116
};

private/aws-client-api-test/src/client-interface-tests/client-s3/impl/initializeWithMaximalConfiguration.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ export const initializeWithMaximalConfiguration = () => {
132132
responseChecksumValidation: DEFAULT_RESPONSE_CHECKSUM_VALIDATION,
133133
userAgentAppId: "testApp",
134134
requestStreamBufferSize: 8 * 1024,
135+
expectContinueHeader: 8 * 1024 * 1024,
135136
};
136137

137138
const s3 = new S3Client(config);

private/aws-middleware-test/src/middleware-serde.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ describe("middleware-serde", () => {
2525
"content-type": "application/xml",
2626
"x-amz-acl": "private",
2727
"content-length": "509",
28-
Expect: "100-continue",
2928
"x-amz-checksum-crc32": "XnKFaw==",
3029
host: "s3.us-west-2.amazonaws.com",
3130
"x-amz-content-sha256": "c0a89780e1aac5dfa17604e9e25616e7babba0b655db189be49b4c352543bb22",

0 commit comments

Comments
 (0)