Skip to content

fix(node-http-handler): Write request body if 100 Continue response takes more than 1 second #1505

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading