Skip to content

Http IncomingMessage.signal is always aborted for POST requests #63532

Description

@ackava

Version

24.16

Platform

Linux

Subsystem

No response

What steps will reproduce the bug?

const http = require('node:http');

const server = http.createServer(async (req, res) => {
  // req.signal is an AbortSignal that aborts when the client disconnects
  const { signal } = req;

  console.log('Request received. Starting long task...');

  try {
    // You can pass the signal directly to other APIs like fetch() or fs.promises
    await performLongTask(signal);
    
    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/');
});

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

Activity

  1. arijitchhatui commented on May 25, 2026

    @arijitchhatui

    I don't think req.signal exists on http.IncomingMessage in current Node.js versions.

    From the current typings/runtime, IncomingMessage does not expose a signal property.
    Because of that:

    const { signal } = req;
    signal.addEventListener(...)

    will throw since signal is undefined.

    A safer approach is to create your own AbortController and 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.

  2. ackava commented on May 25, 2026

    @ackava
    Author

    @arijitchhatui

    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

  3. arijitchhatui commented on May 25, 2026

    @arijitchhatui

    @ackava
    It is due to OLD docs, they must have forgot to update the information.

  4. ackava commented on May 25, 2026

    @ackava
    Author

    @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?

  5. arijitchhatui commented on May 25, 2026

    @arijitchhatui

    You're right — I initially tested against v26... and v25..., where IncomingMessage.prototype.signal was not present at runtime.

    After checking the PR history and current main branch implementation, I realized the feature was actually released in:

    • v26.1.0
    • v24.16.0

    not v26.0.0.

    My runtime verification on v26.0.0 showed:

    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.

  6. trivikr commented on Jul 13, 2026

    @trivikr
    Member

    This is now being tracked in #64390, with a proposed fix in #64392.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions