diff --git a/src/bun.js/bindings/NodeHTTP.cpp b/src/bun.js/bindings/NodeHTTP.cpp index 73a87667af..d0e59063b7 100644 --- a/src/bun.js/bindings/NodeHTTP.cpp +++ b/src/bun.js/bindings/NodeHTTP.cpp @@ -1304,13 +1304,15 @@ JSC_DEFINE_HOST_FUNCTION(jsHTTPSetTimeout, (JSGlobalObject * globalObject, CallF if (auto* jsRequest = jsDynamicCast(requestValue)) { Request__setTimeout(jsRequest->wrapped(), JSValue::encode(seconds), globalObject); + return JSValue::encode(jsBoolean(true)); } if (auto* nodeHttpResponse = jsDynamicCast(requestValue)) { NodeHTTPResponse__setTimeout(nodeHttpResponse->wrapped(), JSValue::encode(seconds), globalObject); + return JSValue::encode(jsBoolean(true)); } - return JSValue::encode(jsUndefined()); + return JSValue::encode(jsBoolean(false)); } JSC_DEFINE_HOST_FUNCTION(jsHTTPSetServerIdleTimeout, (JSGlobalObject * globalObject, CallFrame* callFrame)) { diff --git a/src/js/node/http.ts b/src/js/node/http.ts index 43ee855f27..05d845a543 100644 --- a/src/js/node/http.ts +++ b/src/js/node/http.ts @@ -1545,8 +1545,12 @@ const IncomingMessagePrototype = { const req = this[kHandle] || this[webRequestOrResponse]; if (req) { - setRequestTimeout(req, Math.ceil(msecs / 1000)); - typeof callback === "function" && this.once("timeout", callback); + if (setRequestTimeout(req, Math.ceil(msecs / 1000))) { + typeof callback === "function" && this.once("timeout", callback); + } else { + // Actually a Response object + req.setTimeout?.(msecs, callback); + } } return this; }, @@ -2659,6 +2663,10 @@ function ClientRequest(input, options, cb) { this.finished = true; + if (this.res && !this.res.complete) { + this.res.emit("end"); + } + // If request is destroyed we abort the current response this[kAbortController]?.abort?.(); this.socket.destroy(err); @@ -2843,6 +2851,19 @@ function ClientRequest(input, options, cb) { })); isNextIncomingMessageHTTPS = prevIsHTTPS; res.req = this; + let timer; + response.setTimeout = (msecs, callback) => { + if (timer) { + clearTimeout(timer); + } + timer = setTimeout(() => { + if (res.complete) { + return; + } + res.emit("timeout"); + callback?.(); + }, msecs); + }; process.nextTick( (self, res) => { // If the user did not listen for the 'response' event, then they @@ -2958,9 +2979,8 @@ function ClientRequest(input, options, cb) { const send = () => { this.finished = true; - const controller = new AbortController(); - this[kAbortController] = controller; - controller.signal.addEventListener("abort", onAbort, { once: true }); + this[kAbortController] = new AbortController(); + this[kAbortController].signal.addEventListener("abort", onAbort, { once: true }); var body = this[kBodyChunks] && this[kBodyChunks].length > 1 ? new Blob(this[kBodyChunks]) : this[kBodyChunks]?.[0]; @@ -3093,7 +3113,7 @@ function ClientRequest(input, options, cb) { signal.addEventListener( "abort", () => { - this[kAbortController]?.abort?.(); + this[kAbortController]?.abort(); }, { once: true }, ); @@ -3304,7 +3324,7 @@ function ClientRequest(input, options, cb) { const onTimeout = () => { this[kTimeoutTimer] = undefined; - this[kAbortController]?.abort?.(); + this[kAbortController]?.abort(); this.emit("timeout"); }; @@ -3370,7 +3390,7 @@ const ClientRequestPrototype = { }, get aborted() { - return this[abortedSymbol] || this[kSignal]?.aborted || !!this[kAbortController]?.signal?.aborted; + return this[abortedSymbol] || this[kSignal]?.aborted || !!this[kAbortController]?.signal.aborted; }, set aborted(value) { diff --git a/test/js/node/test/parallel/test-http-client-response-timeout.js b/test/js/node/test/parallel/test-http-client-response-timeout.js new file mode 100644 index 0000000000..7e44d83a83 --- /dev/null +++ b/test/js/node/test/parallel/test-http-client-response-timeout.js @@ -0,0 +1,14 @@ +'use strict'; +const common = require('../common'); +const http = require('http'); + +const server = http.createServer((req, res) => res.flushHeaders()); + +server.listen(common.mustCall(() => { + const req = + http.get({ port: server.address().port }, common.mustCall((res) => { + res.on('timeout', common.mustCall(() => req.destroy())); + res.setTimeout(1); + server.close(); + })); +}));