diff --git a/src/listener.ts b/src/listener.ts index a8d9d24..92f2716 100644 --- a/src/listener.ts +++ b/src/listener.ts @@ -1,7 +1,7 @@ import type { IncomingMessage, ServerResponse, OutgoingHttpHeaders } from 'node:http' import type { Http2ServerRequest, Http2ServerResponse } from 'node:http2' import { - getAbortController, + abortControllerKey, newRequest, Request as LightweightRequest, toRequestError, @@ -187,10 +187,15 @@ export const getRequestListener = ( // Detect if request was aborted. outgoing.on('close', () => { + const abortController = req[abortControllerKey] as AbortController | undefined + if (!abortController) { + return + } + if (incoming.errored) { - req[getAbortController]().abort(incoming.errored.toString()) + req[abortControllerKey].abort(incoming.errored.toString()) } else if (!outgoing.writableFinished) { - req[getAbortController]().abort('Client connection prematurely closed.') + req[abortControllerKey].abort('Client connection prematurely closed.') } }) diff --git a/src/request.ts b/src/request.ts index 28408ba..a77d7eb 100644 --- a/src/request.ts +++ b/src/request.ts @@ -85,7 +85,7 @@ const getRequestCache = Symbol('getRequestCache') const requestCache = Symbol('requestCache') const incomingKey = Symbol('incomingKey') const urlKey = Symbol('urlKey') -const abortControllerKey = Symbol('abortControllerKey') +export const abortControllerKey = Symbol('abortControllerKey') export const getAbortController = Symbol('getAbortController') const requestPrototype: Record = { diff --git a/test/listener.test.ts b/test/listener.test.ts index e2d8f8c..1f20892 100644 --- a/test/listener.test.ts +++ b/test/listener.test.ts @@ -274,6 +274,17 @@ describe('Abort request', () => { } } ) + + it('should handle request abort without requestCache', async () => { + const fetchCallback = async () => { + // NOTE: we don't req.signal + await new Promise(() => {}) // never resolve + } + const requestListener = getRequestListener(fetchCallback) + const server = createServer(requestListener) + const req = request(server).post('/abort').timeout({ deadline: 1 }) + await expect(req).rejects.toHaveProperty('timeout') + }) }) describe('overrideGlobalObjects', () => { diff --git a/test/request.test.ts b/test/request.test.ts index 999a371..34fdd26 100644 --- a/test/request.test.ts +++ b/test/request.test.ts @@ -4,6 +4,7 @@ import { Request as LightweightRequest, GlobalRequest, getAbortController, + abortControllerKey, RequestError, } from '../src/request' @@ -80,6 +81,21 @@ describe('Request', () => { expect(z).not.toBe(y) }) + it('should be able to safely check if an AbortController has been initialized by referencing the abortControllerKey', async () => { + const req = newRequest({ + headers: { + host: 'localhost', + }, + rawHeaders: ['host', 'localhost'], + url: '/foo.txt', + } as IncomingMessage) + + expect(req[abortControllerKey]).toBeUndefined() // not initialized, do not initialize internal request object automatically + + expect(req[getAbortController]()).toBeDefined() + expect(req[abortControllerKey]).toBeDefined() // initialized + }) + it('Should throw error if host header contains path', async () => { expect(() => { newRequest({