Skip to content

Commit 6032493

Browse files
authored
Fix for lambda streaming on empty body (#443)
* fix for lambda streaming on empty body * Create little-pots-raise.md
1 parent 8e0cdce commit 6032493

File tree

5 files changed

+40
-9
lines changed

5 files changed

+40
-9
lines changed

.changeset/little-pots-raise.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"open-next": patch
3+
---
4+
5+
Fix for lambda streaming on empty body

examples/sst/stacks/AppRouter.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ export function AppRouter({ stack }) {
44
// We should probably switch to ion once it's ready
55
const site = new OpenNextCdkReferenceImplementation(stack, "approuter", {
66
path: "../app-router",
7+
environment: {
8+
OPEN_NEXT_FORCE_NON_EMPTY_RESPONSE: "true",
9+
},
710
});
811
// const site = new NextjsSite(stack, "approuter", {
912
// path: "../app-router",

examples/sst/stacks/OpenNextReferenceImplementation.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ interface OpenNextOutput {
110110

111111
interface OpenNextCdkReferenceImplementationProps {
112112
path: string;
113+
environment?: Record<string, string>;
113114
}
114115

115116
export class OpenNextCdkReferenceImplementation extends Construct {
@@ -122,6 +123,8 @@ export class OpenNextCdkReferenceImplementation extends Construct {
122123
private staticCachePolicy: ICachePolicy;
123124
private serverCachePolicy: CachePolicy;
124125

126+
private customEnvironment: Record<string, string>;
127+
125128
public distribution: Distribution;
126129

127130
constructor(
@@ -130,6 +133,7 @@ export class OpenNextCdkReferenceImplementation extends Construct {
130133
props: OpenNextCdkReferenceImplementationProps,
131134
) {
132135
super(scope, id);
136+
this.customEnvironment = props.environment ?? {};
133137
this.openNextBasePath = path.join(process.cwd(), props.path);
134138
execSync("npm run openbuild", {
135139
cwd: path.join(process.cwd(), props.path),
@@ -312,6 +316,7 @@ export class OpenNextCdkReferenceImplementation extends Construct {
312316
// Those 2 are used only for image optimizer
313317
BUCKET_NAME: this.bucket.bucketName,
314318
BUCKET_KEY_PREFIX: "_assets",
319+
...this.customEnvironment,
315320
};
316321
}
317322

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

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
import { Socket } from "net";
88
import { Transform, TransformCallback, Writable } from "stream";
99

10+
import { debug } from "../adapters/logger";
1011
import { parseCookies, parseHeaders } from "./util";
1112

1213
const SET_COOKIE_HEADER = "set-cookie";
@@ -20,7 +21,7 @@ export interface StreamCreator {
2021
}): Writable;
2122
// Just to fix an issue with aws lambda streaming with empty body
2223
onWrite?: () => void;
23-
onFinish: () => void;
24+
onFinish: (length: number) => void;
2425
}
2526

2627
// We only need to implement the methods that are used by next.js
@@ -91,7 +92,8 @@ export class OpenNextNodeResponse extends Transform implements ServerResponse {
9192
this.flushHeaders();
9293
}
9394
onEnd(this.headers);
94-
this.streamCreator?.onFinish();
95+
const bodyLength = this.body.length;
96+
this.streamCreator?.onFinish(bodyLength);
9597
});
9698
}
9799

@@ -281,4 +283,25 @@ export class OpenNextNodeResponse extends Transform implements ServerResponse {
281283
this._internalWrite(chunk, encoding);
282284
callback();
283285
}
286+
287+
//This is only here because of aws broken streaming implementation.
288+
//Hopefully one day they will be able to give us a working streaming implementation in lambda for everyone
289+
//If you're lucky you have a working streaming implementation in your aws account and don't need this
290+
//If not you can set the OPEN_NEXT_FORCE_NON_EMPTY_RESPONSE env variable to true
291+
//BE CAREFUL: Aws keeps rolling out broken streaming implementations even on accounts that had working ones before
292+
//This is not dependent on the node runtime used
293+
//There is another known issue with aws lambda streaming where the request reach the lambda only way after the request has been sent by the client. For this there is absolutely nothing we can do, contact aws support if that's your case
294+
_flush(callback: TransformCallback): void {
295+
if (
296+
this.body.length < 1 &&
297+
// We use an env variable here because not all aws account have the same behavior
298+
// On some aws accounts the response will hang if the body is empty
299+
// We are modifying the response body here, this is not a good practice
300+
process.env.OPEN_NEXT_FORCE_NON_EMPTY_RESPONSE === "true"
301+
) {
302+
debug('Force writing "SOMETHING" to the response body');
303+
this.push("SOMETHING");
304+
}
305+
callback();
306+
}
284307
}

packages/open-next/src/wrappers/aws-lambda-streaming.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ const handler: WrapperHandler = async (handler, converter) =>
3131
}
3232

3333
const internalEvent = await converter.convertFrom(event);
34-
let _hasWriten = false;
3534

3635
//Handle compression
3736
const acceptEncoding =
@@ -89,13 +88,9 @@ const handler: WrapperHandler = async (handler, converter) =>
8988
return compressedStream ?? responseStream;
9089
},
9190
onWrite: () => {
92-
_hasWriten = true;
93-
},
94-
onFinish: () => {
95-
if (!_hasWriten) {
96-
compressedStream?.end(new Uint8Array(8));
97-
}
91+
// _hasWriten = true;
9892
},
93+
onFinish: () => {},
9994
};
10095

10196
const response = await handler(internalEvent, streamCreator);

0 commit comments

Comments
 (0)