From d137501a639cb315772866f6775fcd9f43e28f5b Mon Sep 17 00:00:00 2001 From: Levente Kurusa Date: Sat, 22 Apr 2023 13:20:00 +0200 Subject: [PATCH] feat(node/http): implement ClientRequest.setTimeout() (#18783) - implement setTimeout with matching semantics of Node - add the test from Node but leave it turned off because ClientRequest has no underlying socket --- cli/tests/node_compat/config.jsonc | 3 +++ ext/node/polyfills/http.ts | 25 +++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/cli/tests/node_compat/config.jsonc b/cli/tests/node_compat/config.jsonc index ddbdf458ff..ce1cf3a08c 100644 --- a/cli/tests/node_compat/config.jsonc +++ b/cli/tests/node_compat/config.jsonc @@ -351,6 +351,9 @@ "test-http-agent-getname.js", "test-http-client-get-url.js", "test-http-client-read-in-error.js", + // TODO(lev): ClientRequest.socket is not polyfilled so this test keeps + // failing + //"test-http-client-set-timeout.js", "test-http-localaddress.js", "test-http-outgoing-buffer.js", "test-http-outgoing-internal-headernames-getter.js", diff --git a/ext/node/polyfills/http.ts b/ext/node/polyfills/http.ts index 9104183cac..d8ec7650bc 100644 --- a/ext/node/polyfills/http.ts +++ b/ext/node/polyfills/http.ts @@ -18,6 +18,7 @@ import { urlToHttpOptions } from "ext:deno_node/internal/url.ts"; import { constants, TCP } from "ext:deno_node/internal_binding/tcp_wrap.ts"; import * as denoHttp from "ext:deno_http/01_http.js"; import * as httpRuntime from "ext:runtime/40_http.js"; +import { connResetException } from "ext:deno_node/internal/errors.ts"; enum STATUS_CODES { /** RFC 7231, 6.2.1 */ @@ -259,16 +260,21 @@ class ClientRequest extends NodeWritable { method: this.opts.method, client, headers: this.opts.headers, + signal: this.opts.signal ?? undefined, }; const mayResponse = fetch(this._createUrlStrFromOptions(this.opts), opts) .catch((e) => { if (e.message.includes("connection closed before message completed")) { // Node.js seems ignoring this error + } else if (e.message.includes("The signal has been aborted")) { + // Remap this error + this.emit("error", connResetException("socket hang up")); } else { this.emit("error", e); } return undefined; }); + const res = new IncomingMessageForClient( await mayResponse, this._createSocket(), @@ -279,6 +285,10 @@ class ClientRequest extends NodeWritable { client.close(); }); } + if (this.opts.timeout != undefined) { + clearTimeout(this.opts.timeout); + this.opts.timeout = undefined; + } this.cb?.(res); } @@ -340,8 +350,19 @@ class ClientRequest extends NodeWritable { }${path}`; } - setTimeout() { - console.log("not implemented: ClientRequest.setTimeout"); + setTimeout(timeout: number, callback?: () => void) { + const controller = new AbortController(); + this.opts.signal = controller.signal; + + this.opts.timeout = setTimeout(() => { + controller.abort(); + + this.emit("timeout"); + + if (callback !== undefined) { + callback(); + } + }, timeout); } }