Skip to content

request.signal is not aborted for aborted http.ClientRequest  #768

@piranmontford-ww

Description

@piranmontford-ww

How do I see that a request was timed out or aborted using @mswjs/interceptors ?

Why? I'm using @mswjs/interceptors to log calls made by a number of SDKs we're using, and we can't intercept the HTTP requests they make through their SDK's API. Therefore we're using @mswjs/interceptors to log calls, timings, response headers, errors etc. However, I'd also like to log timeouts. Some of the SDKs retry on timeouts, and we'd like to know when they do that and see both requests.

Details:

  • Node v22.14.0 with yarn 3.8.7
  • @mswjs/interceptors 0.41.3

In this JavaScript Jest test below, neither on('response') or on('unhandledException') are called when the request times out:

const { describe, expect, it, beforeEach, afterEach } = require('@jest/globals')
const { BatchInterceptor } = require('@mswjs/interceptors')
const nodeInterceptors = require('@mswjs/interceptors/presets/node')
const https = require('node:https')

describe('@mswjs/interceptors timeout tests', () => {
    let interceptor
    beforeEach(() => {
        interceptor = new BatchInterceptor({
            name: 'a test',
            interceptors: nodeInterceptors,
        })

        interceptor.on('request', ({ requestId }) => {
            console.log('request', requestId) // <-- This does get called
        })

        interceptor.on('response', ({ requestId }) => {
            console.log('response', requestId) // <-- This doesn't get called
        })

        interceptor.on('unhandledException', ({ error }) => {
            console.log('unhandledException', error) // <-- This doesn't get called
        })

        interceptor.apply()
    })
    afterEach(() => {
        interceptor.dispose()
    })

    it('fetch', async () => {
        try {
            const response = await fetch('https://httpbin.org/delay/10', {
                signal: AbortSignal.timeout(1_000),
            })
            expect(response).toEqual('this should not happen')
        } catch (e) {
            expect(e.name).toEqual('TimeoutError')
        }

        // leave some time for background processes
        await new Promise((resolve) => {
            setTimeout(resolve, 2_000)
        })
    })
    it('node:https', async () => {
        try {
            const promiseWithResolver = Promise.withResolvers()
            const request = https.get(
                'https://httpbin.org/delay/10',
                {
                    timeout: 1_000,
                    signal: AbortSignal.timeout(1_000),
                },
                (result) => {
                    promiseWithResolver.resolve(result)
                },
            )
            request.on('error', promiseWithResolver.reject)

            const response = await promiseWithResolver.promise
            expect(response).toEqual('this should not happen')
        } catch (e) {
            expect(e.name).toEqual('AbortError')
        }

        // leave some time for background processes
        await new Promise((resolve) => {
            setTimeout(resolve, 2_000)
        })
    })
})

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