diff --git a/packages/angular/ssr/node/src/request.ts b/packages/angular/ssr/node/src/request.ts index 4bd754b268cc..4f604b6a9d0e 100644 --- a/packages/angular/ssr/node/src/request.ts +++ b/packages/angular/ssr/node/src/request.ts @@ -55,9 +55,18 @@ export function createWebRequestFromNodeRequest( const { headers, method = 'GET' } = nodeRequest; const withBody = method !== 'GET' && method !== 'HEAD'; const referrer = headers.referer && URL.canParse(headers.referer) ? headers.referer : undefined; + const controller = new AbortController(); + if (nodeRequest.aborted) { + controller.abort(); + } else { + const onAbort = () => controller.abort(); + nodeRequest.once('aborted', onAbort); + nodeRequest.once('close', () => nodeRequest.off('aborted', onAbort)); + } return new Request(createRequestUrl(nodeRequest, trustProxyHeadersNormalized), { method, + signal: controller.signal, headers: createRequestHeaders(headers), body: withBody ? nodeRequest : undefined, duplex: withBody ? 'half' : undefined, diff --git a/packages/angular/ssr/node/test/request_http1_spec.ts b/packages/angular/ssr/node/test/request_http1_spec.ts index 87f25f918ef7..9f11fe390ed4 100644 --- a/packages/angular/ssr/node/test/request_http1_spec.ts +++ b/packages/angular/ssr/node/test/request_http1_spec.ts @@ -189,4 +189,42 @@ describe('createWebRequestFromNodeRequest (HTTP/1.1)', () => { expect(await webRequest.text()).toBe(''); }); }); + + describe('abort handling', () => { + it('should abort the web request signal when the node request is aborted', async () => { + const nodeRequest = await extractNodeRequest(() => { + request({ + hostname: 'localhost', + port, + path: '/abort', + method: 'GET', + }).end(); + }); + + const webRequest = createWebRequestFromNodeRequest(nodeRequest); + expect(webRequest.signal.aborted).toBeFalse(); + + nodeRequest.emit('aborted'); + + expect(webRequest.signal.aborted).toBeTrue(); + }); + + it('should create an aborted web request signal when the node request is already aborted', async () => { + const nodeRequest = await extractNodeRequest(() => { + request({ + hostname: 'localhost', + port, + path: '/already-aborted', + method: 'GET', + }).end(); + }); + + Object.defineProperty(nodeRequest, 'aborted', { get: () => true, configurable: true }); + + const webRequest = createWebRequestFromNodeRequest(nodeRequest); + expect(webRequest.signal.aborted).toBeTrue(); + + delete (nodeRequest as { aborted?: boolean }).aborted; + }); + }); }); diff --git a/packages/angular/ssr/node/test/request_http2_spec.ts b/packages/angular/ssr/node/test/request_http2_spec.ts index 7079a385daaf..e652c59f6461 100644 --- a/packages/angular/ssr/node/test/request_http2_spec.ts +++ b/packages/angular/ssr/node/test/request_http2_spec.ts @@ -188,4 +188,42 @@ describe('createWebRequestFromNodeRequest (HTTP/2)', () => { expect(await webRequest.text()).toBe(''); }); }); + + describe('abort handling', () => { + it('should abort the web request signal when the node request is aborted', async () => { + const nodeRequest = await extractNodeRequest(() => { + client + .request({ + ':path': '/abort', + ':method': 'GET', + }) + .end(); + }); + + const webRequest = createWebRequestFromNodeRequest(nodeRequest); + expect(webRequest.signal.aborted).toBeFalse(); + + nodeRequest.emit('aborted'); + + expect(webRequest.signal.aborted).toBeTrue(); + }); + + it('should create an aborted web request signal when the node request is already aborted', async () => { + const nodeRequest = await extractNodeRequest(() => { + client + .request({ + ':path': '/already-aborted', + ':method': 'GET', + }) + .end(); + }); + + Object.defineProperty(nodeRequest, 'aborted', { get: () => true, configurable: true }); + + const webRequest = createWebRequestFromNodeRequest(nodeRequest); + expect(webRequest.signal.aborted).toBeTrue(); + + delete (nodeRequest as { aborted?: boolean }).aborted; + }); + }); });