From 49ba7ae6036b0722a7bcd88e2111695d2173079b Mon Sep 17 00:00:00 2001 From: RaphaelFakhri <153192858+RaphaelFakhri@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:42:06 +0300 Subject: [PATCH] fix: abort the connection when a handler throws after the response started --- packages/micro/src/lib/index.ts | 9 ++++++++- test/suite/index.ts | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/micro/src/lib/index.ts b/packages/micro/src/lib/index.ts index 8977b35..707c7ee 100644 --- a/packages/micro/src/lib/index.ts +++ b/packages/micro/src/lib/index.ts @@ -114,7 +114,14 @@ export const sendError = ( res: ServerResponse, errorObj: Error | HttpError, ) => { - if ('statusCode' in errorObj && errorObj.statusCode) { + if (res.headersSent) { + // The status line and headers are already on the wire, so an error + // response can't be sent. Abort the connection so the client doesn't + // mistake the truncated body for a complete one. + if (!res.writableEnded) { + res.destroy(); + } + } else if ('statusCode' in errorObj && errorObj.statusCode) { send(res, errorObj.statusCode, errorObj.message); } else send(res, 500, 'Internal Server Error'); diff --git a/test/suite/index.ts b/test/suite/index.ts index 563f77c..01e9de5 100644 --- a/test/suite/index.ts +++ b/test/suite/index.ts @@ -696,3 +696,24 @@ void test('Content-Type header for JSON is set', async (t) => { t.equal(res.headers.get('content-type'), 'application/json; charset=utf-8'); shutdown(); }); + +void test('throw after the response has started does not reject run()', async (t) => { + let outcome: Promise = Promise.resolve(); + const fn: RequestHandler = (req, res) => { + res.write('partial'); + throw new Error('500 from test (expected)'); + }; + + const server = http.createServer((req, res) => { + outcome = run(req, res, fn); + }); + await new Promise((resolve) => { + server.listen(resolve); + }); + const { port } = server.address() as AddressInfo; + + const res = await fetch(`http://localhost:${port}`, { timeout: 2000 }); + await res.text().catch(() => undefined); + await t.resolves(outcome); + server.close(); +});