Repository navigation
Http IncomingMessage.signal is always aborted for POST requests #63532
Description
Activity
I don't think
req.signalexists onhttp.IncomingMessagein current Node.js versions.From the current typings/runtime,
IncomingMessagedoes not expose asignalproperty.
Because of that:const { signal } = req; signal.addEventListener(...)
will throw since
signalisundefined.A safer approach is to create your own
AbortControllerand bridge request lifecycle events manually:const http = require('node:http'); const server = http.createServer(async (req, res) => { const controller = new AbortController(); // req.signal is an AbortSignal that aborts when the client disconnects console.log('Request received. Starting long task...'); try { // You can pass the signal directly to other APIs like fetch() or fs.promises await performLongTask(controller.signal); req.on('close', () => { console.log('Client disconnected. Aborting task...'); controller.abort(); }); res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Task completed successfully!'); } catch (err) { if (err.name === 'AbortError') { console.log('Request was aborted by the client. Cleaning up...'); } else { console.error('An error occurred:', err); res.statusCode = 500; res.end('Internal Server Error'); } } }); // Mock function representing an asynchronous task function performLongTask(signal) { return new Promise((resolve, reject) => { const timeout = setTimeout(() => { resolve(); }, 5000); // Manually listen for the abort event if the API doesn't support signals signal.addEventListener( 'abort', () => { clearTimeout(timeout); reject(new DOMException('Aborted', 'AbortError')); }, { once: true }, ); }); } server.listen(3000, () => { console.log('Server running at http://localhost:3000/'); });
This works consistently across current Node versions without relying on a non-existent
req.signal.https://nodejs.org/docs/latest-v24.x/api/http.html#messagesignal
Release notes of 24.16
[aa1d8a9afc] - (SEMVER-MINOR) http: add req.signal to IncomingMessage (Akshat) #62541@ackava
It is due to OLD docs, they must have forgot to update the information.@arijitchhatui Have you checked this PR ? #62541 , this pr was released in version 24.16, as mentioned in release notes,
Adds a lazy signal getter to IncomingMessage that returns an
AbortSignal which aborts when the request is closed or aborted.
This mirrors the Web Fetch API's Request.signal and Deno's
request.signal.Why are you telling me it does not exist?
You're right — I initially tested against
v26...andv25..., whereIncomingMessage.prototype.signalwas not present at runtime.After checking the PR history and current main branch implementation, I realized the feature was actually released in:
v26.1.0v24.16.0
not
v26.0.0.My runtime verification on
v26.0.0showed:console.log('signal' in req); // false console.log( Object.getOwnPropertyDescriptor( Object.getPrototypeOf(req), 'signal' ) ); // undefined
which is why I concluded it was absent.
I also traced the implementation to PR #62541 and confirmed the getter exists on
main:
So the issue was actually version mismatch/confusion on my side, not that the API itself was missing from Node.
Version
24.16
Platform
Subsystem
No response
What steps will reproduce the bug?
Sending get request works, but sending POST/PATCH with a request body always results in signal being aborted automatically at start of the requst.
How often does it reproduce? Is there a required condition?
Always
What is the expected behavior? Why is that the expected behavior?
Signal should not aborted in any requests with body such as POST
What do you see instead?
For any request with body such as POST, signal is set to Aborted right from beginning.
Additional information
This was never an issue before 24.14