diff --git a/.changeset/eleven-moose-heal.md b/.changeset/eleven-moose-heal.md new file mode 100644 index 000000000..89bd0af0f --- /dev/null +++ b/.changeset/eleven-moose-heal.md @@ -0,0 +1,7 @@ +--- +"@opennextjs/aws": patch +--- + +fix(dev-overrides): Add automatic response cleanup via onClose callback + +This changes will make `request.signal.onabort` work in route handlers for `node` and `express-dev` wrappers. \ No newline at end of file diff --git a/packages/open-next/src/http/openNextResponse.ts b/packages/open-next/src/http/openNextResponse.ts index dac069034..125358c29 100644 --- a/packages/open-next/src/http/openNextResponse.ts +++ b/packages/open-next/src/http/openNextResponse.ts @@ -84,6 +84,14 @@ export class OpenNextNodeResponse extends Transform implements ServerResponse { ) { this.statusCode = statusCode; } + + // We want to destroy this response when the original response is closed. (i.e when the client disconnects) + // This is to support `request.signal.onabort` in route handlers + if (streamCreator?.onClose) { + streamCreator.onClose(() => { + this.destroy(); + }); + } } // Necessary for next 12 diff --git a/packages/open-next/src/overrides/wrappers/express-dev.ts b/packages/open-next/src/overrides/wrappers/express-dev.ts index 14bc9fc91..a0998a47e 100644 --- a/packages/open-next/src/overrides/wrappers/express-dev.ts +++ b/packages/open-next/src/overrides/wrappers/express-dev.ts @@ -41,6 +41,8 @@ const wrapper: WrapperHandler = async (handler, converter) => { req.headers["x-forwarded-proto"] = req.protocol; } const internalEvent = await converter.convertFrom(req); + + let onCloseCallback: (() => void) | undefined; const streamCreator: StreamCreator = { writeHeaders: (prelude) => { res.setHeader("Set-Cookie", prelude.cookies); @@ -49,7 +51,17 @@ const wrapper: WrapperHandler = async (handler, converter) => { return res; }, onFinish: () => {}, + onClose: (callback) => { + onCloseCallback = callback; + }, }; + + res.on("close", () => { + if (onCloseCallback) { + onCloseCallback(); + } + }); + await handler(internalEvent, { streamCreator }); }); diff --git a/packages/open-next/src/overrides/wrappers/node.ts b/packages/open-next/src/overrides/wrappers/node.ts index 3e1a7be1d..34fe48e6b 100644 --- a/packages/open-next/src/overrides/wrappers/node.ts +++ b/packages/open-next/src/overrides/wrappers/node.ts @@ -8,6 +8,8 @@ import { debug, error } from "../../adapters/logger"; const wrapper: WrapperHandler = async (handler, converter) => { const server = createServer(async (req, res) => { const internalEvent = await converter.convertFrom(req); + + let onCloseCallback: (() => void) | undefined; const streamCreator: StreamCreator = { writeHeaders: (prelude) => { res.setHeader("Set-Cookie", prelude.cookies); @@ -15,14 +17,26 @@ const wrapper: WrapperHandler = async (handler, converter) => { res.flushHeaders(); return res; }, + onClose: (callback) => { + onCloseCallback = callback; + }, }; + + res.on("close", () => { + if (onCloseCallback) { + onCloseCallback(); + } + }); + if (internalEvent.rawPath === "/__health") { res.writeHead(200, { "Content-Type": "text/plain", }); res.end("OK"); } else { - await handler(internalEvent, { streamCreator }); + await handler(internalEvent, { + streamCreator, + }); } }); diff --git a/packages/open-next/src/types/open-next.ts b/packages/open-next/src/types/open-next.ts index 483516f04..0830a21b8 100644 --- a/packages/open-next/src/types/open-next.ts +++ b/packages/open-next/src/types/open-next.ts @@ -49,6 +49,8 @@ export interface StreamCreator { // Just to fix an issue with aws lambda streaming with empty body onWrite?: () => void; onFinish?: (length: number) => void; + // This is called when the wrappers response is closed, i.e. when the client disconnects + onClose?: (callback: () => void) => void; } export type WaitUntil = (promise: Promise) => void;