diff --git a/packages/node-http-handler/src/node-http-handler.ts b/packages/node-http-handler/src/node-http-handler.ts index 0386b5011023..4f7dbc4cf0c8 100644 --- a/packages/node-http-handler/src/node-http-handler.ts +++ b/packages/node-http-handler/src/node-http-handler.ts @@ -9,6 +9,7 @@ import { getTransformedHeaders } from "./get-transformed-headers"; import { setConnectionTimeout } from "./set-connection-timeout"; import { setSocketTimeout } from "./set-socket-timeout"; import { writeRequestBody } from "./write-request-body"; +import { setSocketKeepAlive } from "./set-socket-keep-alive"; /** * Represents the http options that can be passed to a node http client. @@ -151,6 +152,17 @@ export class NodeHttpHandler implements HttpHandler { }; } + // Workaround for bug report in Node.js https://github.com/nodejs/node/issues/47137 + const httpAgent = nodeHttpsOptions.agent; + if (typeof httpAgent === "object" && "keepAlive" in httpAgent) { + setSocketKeepAlive(req, { + // @ts-expect-error keepAlive is not public on httpAgent. + keepAlive: (httpAgent as hAgent).keepAlive, + // @ts-expect-error keepAliveMsecs is not public on httpAgent. + keepAliveMsecs: (httpAgent as hAgent).keepAliveMsecs, + }); + } + writeRequestBody(req, request); }); } diff --git a/packages/node-http-handler/src/set-socket-keep-alive.spec.ts b/packages/node-http-handler/src/set-socket-keep-alive.spec.ts new file mode 100644 index 000000000000..27502d51ed0e --- /dev/null +++ b/packages/node-http-handler/src/set-socket-keep-alive.spec.ts @@ -0,0 +1,46 @@ +import { EventEmitter } from "events"; +import { ClientRequest } from "http"; +import { Socket } from "net"; + +import { setSocketKeepAlive } from "./set-socket-keep-alive"; + +describe("setSocketKeepAlive", () => { + let request: ClientRequest; + let socket: Socket; + + beforeEach(() => { + request = new EventEmitter() as ClientRequest; + socket = new Socket(); + }); + + it("should set keepAlive to true", () => { + setSocketKeepAlive(request, { keepAlive: true }); + + const setKeepAliveSpy = jest.spyOn(socket, "setKeepAlive"); + request.emit("socket", socket); + + expect(setKeepAliveSpy).toHaveBeenCalled(); + expect(setKeepAliveSpy).toHaveBeenCalledWith(true, 0); + }); + + it("should set keepAlive to true with custom initialDelay", () => { + const initialDelay = 5 * 1000; + setSocketKeepAlive(request, { keepAlive: true, keepAliveMsecs: initialDelay }); + + const setKeepAliveSpy = jest.spyOn(socket, "setKeepAlive"); + request.emit("socket", socket); + + expect(setKeepAliveSpy).toHaveBeenCalled(); + expect(setKeepAliveSpy).toHaveBeenCalledWith(true, initialDelay); + }); + + it("should not set keepAlive at all when keepAlive is false", () => { + setSocketKeepAlive(request, { keepAlive: false }); + + const setKeepAliveSpy = jest.spyOn(socket, "setKeepAlive"); + request.emit("socket", socket); + + expect(setKeepAliveSpy).not.toHaveBeenCalled(); + }); +}); + diff --git a/packages/node-http-handler/src/set-socket-keep-alive.ts b/packages/node-http-handler/src/set-socket-keep-alive.ts new file mode 100644 index 000000000000..4ae9c079efe1 --- /dev/null +++ b/packages/node-http-handler/src/set-socket-keep-alive.ts @@ -0,0 +1,16 @@ +import { ClientRequest } from "http"; + +export interface SocketKeepAliveOptions { + keepAlive: boolean; + keepAliveMsecs?: number; +} + +export const setSocketKeepAlive = (request: ClientRequest, { keepAlive, keepAliveMsecs }: SocketKeepAliveOptions) => { + if (keepAlive !== true) { + return; + } + + request.on("socket", (socket) => { + socket.setKeepAlive(keepAlive, keepAliveMsecs || 0); + }); +};