interceptors icon indicating copy to clipboard operation
interceptors copied to clipboard

Fetch interceptor does not abort signal for long response

Open mikicho opened this issue 1 year ago • 1 comments

const http = require('http');
const stream = require('stream');
const { FetchInterceptor } = require('@mswjs/interceptors/fetch');

const interceptor = new FetchInterceptor();
interceptor.apply();

interceptor.on('request', ({ controller}) => {
  const readable = new stream.Readable({
    read() {
      setTimeout(() => this.push('hello'), 100)
    }
  })
  controller.respondWith(new Response(readable))
})

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' })
  setInterval(() => res.write('hello\n'), 100)
})
server.listen(3000);

(async () => {
  const start = process.hrtime.bigint()
  const response = await fetch('http://localhost:3000', {
    signal: AbortSignal.timeout(300),
  })
  console.log((process.hrtime.bigint() - start) / BigInt(1e6));
  for await (const data of response.body) {
    console.log((process.hrtime.bigint() - start) / BigInt(1e6));
    console.log(Buffer.from(data).toString())
  }
  console.log((process.hrtime.bigint() - start) / BigInt(1e6));
})()

This process continues to run indefinitely. It should abort the request for a long response as well.

P.S. this is weird and somewhat unexpected behavior, I must say.

mikicho avatar Dec 21 '24 21:12 mikicho

I think we should extract the signal abort from the handleRequest method and send it to the outer interceptor because each client behaves differently. From a quick check:

  1. Fetch abort for the entire request & response. (as shown above)
  2. ClientRequest does not use this abort at all and relies on Node.js internal code for the abort.
  3. IIUC, XMLHttpRequest does not have a signal property.

WDYT?

mikicho avatar Dec 22 '24 19:12 mikicho