Skip to content

Commit 551dc85

Browse files
authored
fix: Add automatic response cleanup via AbortSignal (opennextjs#952)
* feat(dev-overrides): Add automatic response cleanup via onClose callback changeset changeset * lint * fix changeset * review * refactor cloudflare part * review * lint * add comment
1 parent 58789c9 commit 551dc85

File tree

6 files changed

+41
-2
lines changed

6 files changed

+41
-2
lines changed

.changeset/eleven-moose-heal.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@opennextjs/aws": patch
3+
---
4+
5+
fix: Add automatic response cleanup via AbortSignal
6+
7+
These changes will make `request.signal.onabort` work in route handlers for `node`, `cloudflare-node` and `express-dev` wrappers.

packages/open-next/src/http/openNextResponse.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ export class OpenNextNodeResponse extends Transform implements ServerResponse {
8484
) {
8585
this.statusCode = statusCode;
8686
}
87+
88+
// https://github.com/vercel/next.js/blob/ea08bf2/packages/next/src/server/web/spec-extension/adapters/next-request.ts#L46-L54
89+
// We want to destroy this response when the original response/request is closed. (i.e when the client disconnects)
90+
// This is to support `request.signal.onabort` in route handlers
91+
streamCreator?.abortSignal?.addEventListener("abort", () => {
92+
this.destroy();
93+
});
8794
}
8895

8996
// Necessary for next 12

packages/open-next/src/overrides/wrappers/cloudflare-node.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ const handler: WrapperHandler<InternalEvent, InternalResult> =
1616
request: Request,
1717
env: Record<string, string>,
1818
ctx: any,
19+
abortSignal: AbortSignal,
1920
): Promise<Response> => {
2021
globalThis.process = process;
21-
2222
// Set the environment variables
2323
// Cloudflare suggests to not override the process.env object but instead apply the values to it
2424
for (const [key, value] of Object.entries(env)) {
@@ -66,6 +66,10 @@ const handler: WrapperHandler<InternalEvent, InternalResult> =
6666

6767
return Writable.fromWeb(writable);
6868
},
69+
// This is for passing along the original abort signal from the initial Request you retrieve in your worker
70+
// Ensures that the response we pass to NextServer is aborted if the request is aborted
71+
// By doing this `request.signal.onabort` will work in route handlers
72+
abortSignal: abortSignal,
6973
};
7074

7175
ctx.waitUntil(

packages/open-next/src/overrides/wrappers/express-dev.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ const wrapper: WrapperHandler = async (handler, converter) => {
4141
req.headers["x-forwarded-proto"] = req.protocol;
4242
}
4343
const internalEvent = await converter.convertFrom(req);
44+
45+
const abortController = new AbortController();
46+
4447
const streamCreator: StreamCreator = {
4548
writeHeaders: (prelude) => {
4649
res.setHeader("Set-Cookie", prelude.cookies);
@@ -49,7 +52,13 @@ const wrapper: WrapperHandler = async (handler, converter) => {
4952
return res;
5053
},
5154
onFinish: () => {},
55+
abortSignal: abortController.signal,
5256
};
57+
58+
res.on("close", () => {
59+
abortController.abort();
60+
});
61+
5362
await handler(internalEvent, { streamCreator });
5463
});
5564

packages/open-next/src/overrides/wrappers/node.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,32 @@ import { debug, error } from "../../adapters/logger";
88
const wrapper: WrapperHandler = async (handler, converter) => {
99
const server = createServer(async (req, res) => {
1010
const internalEvent = await converter.convertFrom(req);
11+
12+
const abortController = new AbortController();
13+
1114
const streamCreator: StreamCreator = {
1215
writeHeaders: (prelude) => {
1316
res.setHeader("Set-Cookie", prelude.cookies);
1417
res.writeHead(prelude.statusCode, prelude.headers);
1518
res.flushHeaders();
1619
return res;
1720
},
21+
abortSignal: abortController.signal,
1822
};
23+
24+
res.on("close", () => {
25+
abortController.abort();
26+
});
27+
1928
if (internalEvent.rawPath === "/__health") {
2029
res.writeHead(200, {
2130
"Content-Type": "text/plain",
2231
});
2332
res.end("OK");
2433
} else {
25-
await handler(internalEvent, { streamCreator });
34+
await handler(internalEvent, {
35+
streamCreator,
36+
});
2637
}
2738
});
2839

packages/open-next/src/types/open-next.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export interface StreamCreator {
4949
// Just to fix an issue with aws lambda streaming with empty body
5050
onWrite?: () => void;
5151
onFinish?: (length: number) => void;
52+
abortSignal?: AbortSignal;
5253
}
5354

5455
export type WaitUntil = (promise: Promise<void>) => void;

0 commit comments

Comments
 (0)