Skip to content

Commit fbd06eb

Browse files
Nevonkuhe
andauthored
fix(node-http-handler): Write request body if 100 Continue response takes more than 1 second (#1505)
* fix(node-http-handler): Write request body if 100 Continue response times out * set 100-continue timeout to 6s minimum --------- Co-authored-by: George Fu <[email protected]>
1 parent 6e70955 commit fbd06eb

File tree

3 files changed

+49
-4
lines changed

3 files changed

+49
-4
lines changed

.changeset/good-buttons-relax.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@smithy/node-http-handler": patch
3+
---
4+
5+
fix sending request when 100 Continue response takes more than 1 second

packages/node-http-handler/src/write-request-body.spec.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import EventEmitter from "events";
2-
import { describe, expect, test as it, vi } from "vitest";
2+
import { afterEach, beforeEach, describe, expect, test as it, vi } from "vitest";
33

44
import { writeRequestBody } from "./write-request-body";
55

@@ -59,4 +59,36 @@ describe(writeRequestBody.name, () => {
5959
await promise;
6060
}
6161
);
62+
63+
describe("with fake timers", () => {
64+
beforeEach(() => {
65+
vi.useFakeTimers();
66+
});
67+
68+
afterEach(() => {
69+
vi.useRealTimers();
70+
});
71+
it("should send the body if the 100 Continue response is not received before the timeout", async () => {
72+
const httpRequest = Object.assign(new EventEmitter(), {
73+
end: vi.fn(),
74+
}) as any;
75+
const request = {
76+
headers: {
77+
expect: "100-continue",
78+
},
79+
body: Buffer.from("abcd"),
80+
method: "GET",
81+
hostname: "",
82+
protocol: "https:",
83+
path: "/",
84+
};
85+
86+
const promise = writeRequestBody(httpRequest, request, 12_000);
87+
expect(httpRequest.end).not.toHaveBeenCalled();
88+
vi.advanceTimersByTime(13000);
89+
await promise;
90+
91+
expect(httpRequest.end).toHaveBeenCalled();
92+
});
93+
});
6294
});

packages/node-http-handler/src/write-request-body.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import { Readable } from "stream";
55

66
import { timing } from "./timing";
77

8-
const MIN_WAIT_TIME = 1000;
8+
const MIN_WAIT_TIME = 6_000;
99

1010
/**
1111
* This resolves when writeBody has been called.
1212
*
1313
* @param httpRequest - opened Node.js request.
1414
* @param request - container with the request body.
15-
* @param maxContinueTimeoutMs - maximum time to wait for the continue event. Minimum of 1000ms.
15+
* @param maxContinueTimeoutMs - time to wait for the continue event.
1616
*/
1717
export async function writeRequestBody(
1818
httpRequest: ClientRequest | ClientHttp2Stream,
@@ -28,7 +28,15 @@ export async function writeRequestBody(
2828
if (expect === "100-continue") {
2929
sendBody = await Promise.race<boolean>([
3030
new Promise((resolve) => {
31-
timeoutId = Number(timing.setTimeout(resolve, Math.max(MIN_WAIT_TIME, maxContinueTimeoutMs)));
31+
// If this resolves first (wins the race), it means that at least MIN_WAIT_TIME ms
32+
// elapsed and no continue, response, or error has happened.
33+
// The high default timeout is to give the server ample time to respond.
34+
// This is an unusual situation, and indicates the server may not be S3 actual
35+
// and did not correctly implement 100-continue event handling.
36+
// Strictly speaking, we should perhaps keep waiting up to the request timeout
37+
// and then throw an error, but we resolve true to allow the server to deal
38+
// with the request body.
39+
timeoutId = Number(timing.setTimeout(() => resolve(true), Math.max(MIN_WAIT_TIME, maxContinueTimeoutMs)));
3240
}),
3341
new Promise((resolve) => {
3442
httpRequest.on("continue", () => {

0 commit comments

Comments
 (0)