Skip to content

Commit c5da7b5

Browse files
committed
review
1 parent 9d194c4 commit c5da7b5

File tree

5 files changed

+72
-95
lines changed

5 files changed

+72
-95
lines changed

.changeset/bright-bulldogs-laugh.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,4 @@
44

55
Add aws-lambda-compressed wrapper
66

7-
Introduces a new wrapper called `aws-lambda-compressed`. Will compress the response body by default. Compression will be applied in the following priority order: br (Brotli) → gzip → deflate. If none of these is found, we just return the body as is.
8-
9-
The compression quality for brotli can be configured using the `BROTLI_QUALITY` environment variable. If not set, it defaults to 6.
7+
New wrapper called `aws-lambda-compressed`. The compression quality for brotli can be configured using the `BROTLI_QUALITY` environment variable. If not set, it defaults to 6.

packages/open-next/src/overrides/wrappers/aws-lambda-compressed.ts

Lines changed: 36 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,15 @@
1-
import { Readable, Writable } from "node:stream";
1+
import { Readable, type Transform, Writable } from "node:stream";
22
import type { ReadableStream } from "node:stream/web";
33
import zlib from "node:zlib";
44

5-
import type {
6-
APIGatewayProxyEvent,
7-
APIGatewayProxyEventV2,
8-
APIGatewayProxyResult,
9-
APIGatewayProxyResultV2,
10-
CloudFrontRequestEvent,
11-
CloudFrontRequestResult,
12-
} from "aws-lambda";
13-
import type { WrapperHandler } from "types/overrides";
14-
155
import type { InternalResult, StreamCreator } from "types/open-next";
16-
import { error } from "../../adapters/logger";
176
import type {
18-
WarmerEvent,
19-
WarmerResponse,
20-
} from "../../adapters/warmer-function";
21-
22-
type AwsLambdaEvent =
23-
| APIGatewayProxyEventV2
24-
| CloudFrontRequestEvent
25-
| APIGatewayProxyEvent
26-
| WarmerEvent;
27-
28-
type AwsLambdaReturn =
29-
| APIGatewayProxyResultV2
30-
| APIGatewayProxyResult
31-
| CloudFrontRequestResult
32-
| WarmerResponse;
33-
34-
function formatWarmerResponse(event: WarmerEvent) {
35-
return new Promise<WarmerResponse>((resolve) => {
36-
setTimeout(() => {
37-
resolve({ serverId, type: "warmer" } satisfies WarmerResponse);
38-
}, event.delay);
39-
});
40-
}
7+
AwsLambdaEvent,
8+
AwsLambdaReturn,
9+
WrapperHandler,
10+
} from "types/overrides";
11+
import { formatWarmerResponse } from "utils/overrides";
12+
import { error } from "../../adapters/logger";
4113

4214
const handler: WrapperHandler =
4315
async (handler, converter) =>
@@ -48,11 +20,7 @@ const handler: WrapperHandler =
4820
}
4921

5022
const internalEvent = await converter.convertFrom(event);
51-
//TODO: create a simple reproduction and open an issue in the node repo
52-
//This is a workaround, there is an issue in node that causes node to crash silently if the OpenNextNodeResponse stream is not consumed
53-
//This does not happen everytime, it's probably caused by suspended component in ssr (either via <Suspense> or loading.tsx)
54-
//Everyone that wish to create their own wrapper without a StreamCreator should implement this workaround
55-
//This is not necessary if the underlying handler does not use OpenNextNodeResponse (At the moment, OpenNextNodeResponse is used by the node runtime servers and the image server)
23+
// This is a workaround, you can read more about it in the aws-lambda wrapper
5624
const fakeStream: StreamCreator = {
5725
writeHeaders: () => {
5826
return new Writable({
@@ -63,6 +31,18 @@ const handler: WrapperHandler =
6331
},
6432
};
6533

34+
const handlerResponse = await handler(internalEvent, {
35+
streamCreator: fakeStream,
36+
});
37+
38+
// Check if response is already compressed
39+
const prevEncoding =
40+
handlerResponse.headers?.["content-encoding"] ??
41+
handlerResponse.headers?.["Content-Encoding"] ??
42+
"";
43+
44+
// Return early here if the response is already compressed
45+
6646
const acceptEncoding =
6747
internalEvent.headers["accept-encoding"] ??
6848
internalEvent.headers["Accept-Encoding"] ??
@@ -77,10 +57,6 @@ const handler: WrapperHandler =
7757
contentEncoding = "deflate";
7858
}
7959

80-
const handlerResponse = await handler(internalEvent, {
81-
streamCreator: fakeStream,
82-
});
83-
8460
const response: InternalResult = {
8561
...handlerResponse,
8662
body: compressBody(handlerResponse.body, contentEncoding),
@@ -105,29 +81,30 @@ function compressBody(body: ReadableStream, encoding: string | null) {
10581
if (!encoding) return body;
10682
try {
10783
const readable = Readable.fromWeb(body);
84+
let transform: Transform;
10885

10986
switch (encoding) {
11087
case "br":
111-
return Readable.toWeb(
112-
readable.pipe(
113-
zlib.createBrotliCompress({
114-
params: {
115-
// This is a compromise between speed and compression ratio.
116-
// The default one will most likely timeout an AWS Lambda with default configuration on large bodies (>6mb).
117-
// Therefore we set it to 6, which is a good compromise.
118-
[zlib.constants.BROTLI_PARAM_QUALITY]:
119-
Number(process.env.BROTLI_QUALITY) ?? 6,
120-
},
121-
}),
122-
),
123-
);
88+
transform = zlib.createBrotliCompress({
89+
params: {
90+
// This is a compromise between speed and compression ratio.
91+
// The default one will most likely timeout an AWS Lambda with default configuration on large bodies (>6mb).
92+
// Therefore we set it to 6, which is a good compromise.
93+
[zlib.constants.BROTLI_PARAM_QUALITY]:
94+
Number(process.env.BROTLI_QUALITY) ?? 6,
95+
},
96+
});
97+
break;
12498
case "gzip":
125-
return Readable.toWeb(readable.pipe(zlib.createGzip()));
99+
transform = zlib.createGzip();
100+
break;
126101
case "deflate":
127-
return Readable.toWeb(readable.pipe(zlib.createDeflate()));
102+
transform = zlib.createDeflate();
103+
break;
128104
default:
129105
return body;
130106
}
107+
return Readable.toWeb(readable.pipe(transform));
131108
} catch (e) {
132109
error("Error compressing body:", e);
133110
// Fall back to no compression on error

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

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,12 @@
11
import { Writable } from "node:stream";
22

3-
import type {
4-
APIGatewayProxyEvent,
5-
APIGatewayProxyEventV2,
6-
APIGatewayProxyResult,
7-
APIGatewayProxyResultV2,
8-
CloudFrontRequestEvent,
9-
CloudFrontRequestResult,
10-
} from "aws-lambda";
11-
import type { WrapperHandler } from "types/overrides";
12-
133
import type { StreamCreator } from "types/open-next";
144
import type {
15-
WarmerEvent,
16-
WarmerResponse,
17-
} from "../../adapters/warmer-function";
18-
19-
type AwsLambdaEvent =
20-
| APIGatewayProxyEventV2
21-
| CloudFrontRequestEvent
22-
| APIGatewayProxyEvent
23-
| WarmerEvent;
24-
25-
type AwsLambdaReturn =
26-
| APIGatewayProxyResultV2
27-
| APIGatewayProxyResult
28-
| CloudFrontRequestResult
29-
| WarmerResponse;
30-
31-
function formatWarmerResponse(event: WarmerEvent) {
32-
return new Promise<WarmerResponse>((resolve) => {
33-
setTimeout(() => {
34-
resolve({ serverId, type: "warmer" } satisfies WarmerResponse);
35-
}, event.delay);
36-
});
37-
}
5+
AwsLambdaEvent,
6+
AwsLambdaReturn,
7+
WrapperHandler,
8+
} from "types/overrides";
9+
import { formatWarmerResponse } from "utils/overrides";
3810

3911
const handler: WrapperHandler =
4012
async (handler, converter) =>

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@ import type { Readable } from "node:stream";
22

33
import type { Meta } from "types/cache";
44

5+
import type {
6+
APIGatewayProxyEvent,
7+
APIGatewayProxyEventV2,
8+
APIGatewayProxyResult,
9+
APIGatewayProxyResultV2,
10+
CloudFrontRequestEvent,
11+
CloudFrontRequestResult,
12+
} from "aws-lambda";
13+
import type { WarmerEvent, WarmerResponse } from "../adapters/warmer-function";
514
import type {
615
BaseEventOrResult,
716
BaseOverride,
@@ -229,3 +238,15 @@ type CDNPath = {
229238
export type CDNInvalidationHandler = BaseOverride & {
230239
invalidatePaths: (paths: CDNPath[]) => Promise<void>;
231240
};
241+
242+
export type AwsLambdaEvent =
243+
| APIGatewayProxyEventV2
244+
| CloudFrontRequestEvent
245+
| APIGatewayProxyEvent
246+
| WarmerEvent;
247+
248+
export type AwsLambdaReturn =
249+
| APIGatewayProxyResultV2
250+
| APIGatewayProxyResult
251+
| CloudFrontRequestResult
252+
| WarmerResponse;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { WarmerEvent, WarmerResponse } from "../adapters/warmer-function";
2+
3+
export function formatWarmerResponse(event: WarmerEvent) {
4+
return new Promise<WarmerResponse>((resolve) => {
5+
setTimeout(() => {
6+
resolve({ serverId, type: "warmer" } satisfies WarmerResponse);
7+
}, event.delay);
8+
});
9+
}

0 commit comments

Comments
 (0)