Skip to content

Commit

Permalink
fix(node-http-handler): Write request body if 100 Continue response t…
Browse files Browse the repository at this point in the history
…akes 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 <kuhe@users.noreply.github.com>
  • Loading branch information
Nevon and kuhe authored Jan 15, 2025
1 parent 6e70955 commit fbd06eb
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/good-buttons-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@smithy/node-http-handler": patch
---

fix sending request when 100 Continue response takes more than 1 second
34 changes: 33 additions & 1 deletion packages/node-http-handler/src/write-request-body.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import EventEmitter from "events";
import { describe, expect, test as it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, test as it, vi } from "vitest";

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

Expand Down Expand Up @@ -59,4 +59,36 @@ describe(writeRequestBody.name, () => {
await promise;
}
);

describe("with fake timers", () => {
beforeEach(() => {
vi.useFakeTimers();
});

afterEach(() => {
vi.useRealTimers();
});
it("should send the body if the 100 Continue response is not received before the timeout", async () => {
const httpRequest = Object.assign(new EventEmitter(), {
end: vi.fn(),
}) as any;
const request = {
headers: {
expect: "100-continue",
},
body: Buffer.from("abcd"),
method: "GET",
hostname: "",
protocol: "https:",
path: "/",
};

const promise = writeRequestBody(httpRequest, request, 12_000);
expect(httpRequest.end).not.toHaveBeenCalled();
vi.advanceTimersByTime(13000);
await promise;

expect(httpRequest.end).toHaveBeenCalled();
});
});
});
14 changes: 11 additions & 3 deletions packages/node-http-handler/src/write-request-body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { Readable } from "stream";

import { timing } from "./timing";

const MIN_WAIT_TIME = 1000;
const MIN_WAIT_TIME = 6_000;

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

0 comments on commit fbd06eb

Please sign in to comment.