Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/eleven-moose-heal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@opennextjs/aws": patch
---

fix: Add automatic response cleanup via AbortSignal

These changes will make `request.signal.onabort` work in route handlers for `node`, `cloudflare-node` and `express-dev` wrappers.
7 changes: 7 additions & 0 deletions packages/open-next/src/http/openNextResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ export class OpenNextNodeResponse extends Transform implements ServerResponse {
) {
this.statusCode = statusCode;
}

// https://github.com/vercel/next.js/blob/ea08bf2/packages/next/src/server/web/spec-extension/adapters/next-request.ts#L46-L54
// We want to destroy this response when the original response/request is closed. (i.e when the client disconnects)
// This is to support `request.signal.onabort` in route handlers
streamCreator?.abortSignal?.addEventListener("abort", () => {
this.destroy();
});
}

// Necessary for next 12
Expand Down
6 changes: 5 additions & 1 deletion packages/open-next/src/overrides/wrappers/cloudflare-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ const handler: WrapperHandler<InternalEvent, InternalResult> =
request: Request,
env: Record<string, string>,
ctx: any,
abortSignal: AbortSignal,
): Promise<Response> => {
globalThis.process = process;

// Set the environment variables
// Cloudflare suggests to not override the process.env object but instead apply the values to it
for (const [key, value] of Object.entries(env)) {
Expand Down Expand Up @@ -66,6 +66,10 @@ const handler: WrapperHandler<InternalEvent, InternalResult> =

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

ctx.waitUntil(
Expand Down
9 changes: 9 additions & 0 deletions packages/open-next/src/overrides/wrappers/express-dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ const wrapper: WrapperHandler = async (handler, converter) => {
req.headers["x-forwarded-proto"] = req.protocol;
}
const internalEvent = await converter.convertFrom(req);

const abortController = new AbortController();

const streamCreator: StreamCreator = {
writeHeaders: (prelude) => {
res.setHeader("Set-Cookie", prelude.cookies);
Expand All @@ -49,7 +52,13 @@ const wrapper: WrapperHandler = async (handler, converter) => {
return res;
},
onFinish: () => {},
abortSignal: abortController.signal,
};

res.on("close", () => {
abortController.abort();
});

await handler(internalEvent, { streamCreator });
});

Expand Down
13 changes: 12 additions & 1 deletion packages/open-next/src/overrides/wrappers/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,32 @@ import { debug, error } from "../../adapters/logger";
const wrapper: WrapperHandler = async (handler, converter) => {
const server = createServer(async (req, res) => {
const internalEvent = await converter.convertFrom(req);

const abortController = new AbortController();

const streamCreator: StreamCreator = {
writeHeaders: (prelude) => {
res.setHeader("Set-Cookie", prelude.cookies);
res.writeHead(prelude.statusCode, prelude.headers);
res.flushHeaders();
return res;
},
abortSignal: abortController.signal,
};

res.on("close", () => {
abortController.abort();
});

if (internalEvent.rawPath === "/__health") {
res.writeHead(200, {
"Content-Type": "text/plain",
});
res.end("OK");
} else {
await handler(internalEvent, { streamCreator });
await handler(internalEvent, {
streamCreator,
});
}
});

Expand Down
1 change: 1 addition & 0 deletions packages/open-next/src/types/open-next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface StreamCreator {
// Just to fix an issue with aws lambda streaming with empty body
onWrite?: () => void;
onFinish?: (length: number) => void;
abortSignal?: AbortSignal;
}

export type WaitUntil = (promise: Promise<void>) => void;
Expand Down
Loading