sst / sst

Build modern full-stack applications on AWS

Home Page:https://sst.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Streaming not compatible with logging: 'per-route'

mateogianolio opened this issue · comments

Using NextjsSite with streaming: true and logging: 'per-route' breaks logging.

The problem is here: https://github.com/sst/sst/blob/master/packages/sst/src/constructs/SsrFunction.ts#L424

The function passed to streamifyResponse:

awslambda.streamifyResponse(async (event, context) => { /* ... */ }

... has the wrong arguments (see https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html).

Correct code:

awslambda.streamifyResponse(async (event, responseStream, context) => {
  /* ... */
  return rawHandler(event, responseStream);
}

They also recommend closing the response stream using responseStream.end() or by using pipe/pipeline but that's unrelated to this issue. open-next does this correctly but it seems sst does not use the open-next server handler? Never mind, I guess rawHandler is the openNext server handler?


If you've already posted your issue on Discord, make sure to leave a link to it here.
https://discord.com/channels/983865673656705025/1083422760111439973/threads/1196815754230771712

commented

Yup saw this on Discord.

Ah this problem also causes the warmer function to time out if using streaming: true. Streaming responses ignore return types and must be explicitly closed with responseStream.end():

if (event.type === "warmer") {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ serverId: "server-" + Math.random().toString(36).slice(2, 8) });
    }, event.delay);
  });
}

Here's the patch I applied for sst@2.39.6:

diff --git a/node_modules/sst/constructs/SsrFunction.js b/node_modules/sst/constructs/SsrFunction.js
index 5d8cbb1..ebbd05a 100644
--- a/node_modules/sst/constructs/SsrFunction.js
+++ b/node_modules/sst/constructs/SsrFunction.js
@@ -274,10 +274,10 @@ export class SsrFunction extends Construct {
         const newHandlerFunction = "handler";
         await fs.writeFile(path.join(bundle, handlerDir, `${newHandlerName}.mjs`), streaming
             ? [
-                `export const ${newHandlerFunction} = awslambda.streamifyResponse(async (event, context) => {`,
+                `export const ${newHandlerFunction} = awslambda.streamifyResponse(async (event, responseStream, context) => {`,
                 ...injections,
                 `  const { ${oldHandlerFunction}: rawHandler} = await import("./${oldHandlerName}.mjs");`,
-                `  return rawHandler(event, context);`,
+                `  return rawHandler(event, responseStream);`,
                 `});`,
             ].join("\n")
             : [
diff --git a/node_modules/sst/constructs/SsrSite.js b/node_modules/sst/constructs/SsrSite.js
index af27a7d..0446d1f 100644
--- a/node_modules/sst/constructs/SsrSite.js
+++ b/node_modules/sst/constructs/SsrSite.js
@@ -452,7 +452,7 @@ function handler(event) {
                 ...cdk?.server,
                 streaming: props.streaming,
                 injections: [
-                    ...(warm ? [useServerFunctionWarmingInjection()] : []),
+                    ...(warm ? [useServerFunctionWarmingInjection(props)] : []),
                     ...(props.injections || []),
                 ],
                 prefetchSecrets: regional?.prefetchSecrets,
@@ -645,14 +645,25 @@ function handler(event) {
                     OriginRequestPolicy.fromOriginRequestPolicyId(self, "ServerOriginRequestPolicy", "b689b0a8-53d0-40ab-baf2-68738e2966ac");
             return singletonOriginRequestPolicy;
         }
-        function useServerFunctionWarmingInjection() {
-            return `
+        function useServerFunctionWarmingInjection(props) {
+            return props.streaming ? `
 if (event.type === "warmer") {
-  return new Promise((resolve) => {
-    setTimeout(() => {
-      resolve({ serverId: "server-" + Math.random().toString(36).slice(2, 8) });
-    }, event.delay);
-  });
+    const response = await new Promise((resolve) => {
+        setTimeout(() => {
+            resolve({ serverId: "server-" + Math.random().toString(36).slice(2, 8) });
+        }, event.delay);
+    });
+
+    responseStream.write(JSON.stringify(response));
+    responseStream.end();
+    return;
+}` : `
+if (event.type === "warmer") {
+    return new Promise((resolve) => {
+        setTimeout(() => {
+            resolve({ serverId: "server-" + Math.random().toString(36).slice(2, 8) });
+        }, event.delay);
+    });
 }`;
         }
         function getS3FileOptions(copy) {
commented

Oh appreciate this!

Note: I cleaned up the patch a bit above and added a missed return statement for the streaming response :)

Thanks @mateogianolio! Released in v2.39.9.

Btw. does this also fix the logging problem with logging?

Yes it does!