Skip to content
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
92 changes: 91 additions & 1 deletion packages/xhr-http-handler/src/xhr-http-handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class XhrMock {
public static captures: any[] = [];
public static DONE = 4;

private captureArgs =
protected captureArgs =
(caller: string) =>
(...args: any[]) => {
XhrMock.captures.push([caller, ...args]);
Expand Down Expand Up @@ -244,4 +244,94 @@ describe(XhrHttpHandler.name, () => {
["getAllResponseHeaders"],
]);
});

describe("per-request requestTimeout", () => {
it("should use per-request timeout over handler config timeout", async () => {
const handler = new XhrHttpHandler({ requestTimeout: 5000 });

const requestTimeoutSpy = vi.spyOn(await import("./request-timeout"), "requestTimeout");

const mockRequest = new HttpRequest({
method: "GET",
hostname: "example.com",
protocol: "https:",
path: "/",
headers: {},
});

class TimeoutXhrMock extends XhrMock {
send(...args: any[]) {
this.captureArgs("send")(...args);
// let it timeout
}
}

(global as any).XMLHttpRequest = TimeoutXhrMock;

try {
await handler.handle(mockRequest, { requestTimeout: 100 });
} catch (error) {
// expected to timeout
}

// verify requestTimeout function was called with per-request timeout (100), not handler timeout (5000)
expect(requestTimeoutSpy).toHaveBeenCalledWith(100);

requestTimeoutSpy.mockRestore();
(global as any).XMLHttpRequest = XhrMock; // restore original mock
});

it("should fall back to handler config timeout when per-request timeout not provided", async () => {
const handler = new XhrHttpHandler({ requestTimeout: 200 });

const requestTimeoutSpy = vi.spyOn(await import("./request-timeout"), "requestTimeout");

const mockRequest = new HttpRequest({
method: "GET",
hostname: "example.com",
protocol: "https:",
path: "/",
headers: {},
});

class TimeoutXhrMock extends XhrMock {
send(...args: any[]) {
this.captureArgs("send")(...args);
}
}

(global as any).XMLHttpRequest = TimeoutXhrMock;

try {
await handler.handle(mockRequest, {});
} catch (error) {}

expect(requestTimeoutSpy).toHaveBeenCalledWith(200);

requestTimeoutSpy.mockRestore();
(global as any).XMLHttpRequest = XhrMock;
});

it("should handle zero timeout correctly", async () => {
const handler = new XhrHttpHandler({ requestTimeout: 1000 });

const requestTimeoutSpy = vi.spyOn(await import("./request-timeout"), "requestTimeout");

const mockRequest = new HttpRequest({
method: "GET",
hostname: "example.com",
protocol: "https:",
path: "/",
headers: {},
});

try {
await handler.handle(mockRequest, { requestTimeout: 0 });
} catch (error) {}

expect(requestTimeoutSpy).toHaveBeenCalledWith(0);

requestTimeoutSpy.mockRestore();
});
});
});
8 changes: 4 additions & 4 deletions packages/xhr-http-handler/src/xhr-http-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { buildQueryString } from "@smithy/querystring-builder";
import { HttpHandlerOptions, Provider } from "@smithy/types";
import { EventEmitter } from "events";

import { requestTimeout } from "./request-timeout";
import { requestTimeout as requestTimeoutFn } from "./request-timeout";

/**
* Represents the http options that can be passed to the xhr http client.
Expand Down Expand Up @@ -114,12 +114,12 @@ export class XhrHttpHandler extends EventEmitter implements HttpHandler<XhrHttpH

public async handle(
request: HttpRequest,
{ abortSignal }: HttpHandlerOptions = {}
{ abortSignal, requestTimeout }: HttpHandlerOptions = {}
): Promise<{ response: HttpResponse }> {
if (!this.config) {
this.config = await this.configProvider;
}
const requestTimeoutInMs = Number(this.config!.requestTimeout) | 0;
const requestTimeoutInMs = Number(requestTimeout ?? this.config!.requestTimeout) | 0;

// if the request was already aborted, prevent doing extra work
if (abortSignal?.aborted) {
Expand Down Expand Up @@ -214,7 +214,7 @@ export class XhrHttpHandler extends EventEmitter implements HttpHandler<XhrHttpH
this.emit(EVENTS.BEFORE_XHR_SEND, xhr);
xhr.send(body);
}),
requestTimeout(requestTimeoutInMs),
requestTimeoutFn(requestTimeoutInMs),
];
let removeSignalEventListener = () => {};
if (abortSignal) {
Expand Down
Loading