Support for Lambda Response Streaming
Feature Request
Lambda has just released Lambda Response Streaming! It uses Transfer-Encoding: chunked to stream data back to the client. It's an extremely useful feature for my team, and I wish we could test it offline.
Note: It only works through Lambda Function URLs, but serverless-offline doesn't support them.
If a solution is not possible, then a workaround would be highly appreciated
Sample Code
- file: serverless.yml
service: my-service
plugins:
- serverless-offline
provider:
runtime: nodejs18.x
stage: dev
functions:
stream:
handler: handler
url: true # Ideal option - Lambda URL
events: # Backup option - API gateway
- http:
method: ANY
path: 'stream/{proxy+}'
- http:
method: ANY
path: /stream/
resources:
extensions:
StreamLambdaFunctionUrl:
Properties:
InvokeMode: RESPONSE_STREAM
- file: handler.js
// Example 1
exports.handler = awslambda.streamifyResponse(
async (event, responseStream, context) => {
responseStream.setContentType(“text/plain”);
responseStream.write(“Hello, world!”);
responseStream.end();
}
);
// Example 2
exports.handler = awslambda.streamifyResponse(async (event, responseStream, context) => {
const httpResponseMetadata = {
statusCode: 200,
headers: {
'Content-Type': 'text/html',
'X-Custom-Header': 'Example-Custom-Header',
},
};
responseStream = awslambda.HttpResponseStream.from(responseStream, httpResponseMetadata);
responseStream.write('<html>');
responseStream.write('<p>First write2!</p>');
responseStream.write('<h1>Streaming h1</h1>');
await new Promise((r) => setTimeout(r, 1000));
responseStream.write('<h2>Streaming h2</h2>');
await new Promise((r) => setTimeout(r, 1000));
responseStream.write('<h3>Streaming h3</h3>');
await new Promise((r) => setTimeout(r, 1000));
const loremIpsum1 =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque vitae mi tincidunt tellus ultricies dignissim id et diam. Morbi pharetra eu nisi et finibus. Vivamus diam nulla, vulputate et nisl cursus, pellentesque vehicula libero. Cras imperdiet lorem ante, non posuere dolor sollicitudin a. Vestibulum ipsum lacus, blandit nec augue id, lobortis dictum urna. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Morbi auctor orci eget tellus aliquam, non maximus massa porta. In diam ante, pulvinar aliquam nisl non, elementum hendrerit sapien. Vestibulum massa nunc, mattis non congue vitae, placerat in quam. Nam vulputate lectus metus, et dignissim erat varius a.';
responseStream.write(`<p>${loremIpsum1}</p>`);
await new Promise((r) => setTimeout(r, 1000));
responseStream.write('<p>DONE!</p>');
responseStream.end();
});
Expected behavior/code
Currently serverless-offline doesn't support Lambda URLs, so I have to do it through API gateway. This is what happens:
ANY /dev/stream (λ: stream)
× Unhandled exception in handler 'stream'.
× TypeError: underlyingStream.setContentType is not a function
at HttpResponseStream2.from (C:\monorepo\node_modules\serverless-offline\src\lambda\handler-runner\in-process-runner\aws-lambda-ric\UserFunction.js:157:28)
at C:\monorepo\apps\server\.esbuild\.build\src\functions\stream.js:1426:49
at InProcessRunner.run (file:///C:/monorepo/node_modules/serverless-offline/src/lambda/handler-runner/in-process-runner/InProcessRunner.js:87:20)
at async HandlerRunner.run (file:///C:/monorepo/node_modules/serverless-offline/src/lambda/handler-runner/HandlerRunner.js:114:14)
at async LambdaFunction.runHandler (file:///C:/monorepo/node_modules/serverless-offline/src/lambda/LambdaFunction.js:305:16)
at async file:///C:/monorepo/node_modules/serverless-offline/src/events/http/HttpServer.js:602:18
at async exports.Manager.execute (C:\monorepo\node_modules\@hapi\hapi\lib\toolkit.js:60:28)
at async internals.handler (C:\monorepo\node_modules\@hapi\hapi\lib\handler.js:46:20)
at async exports.execute (C:\monorepo\node_modules\@hapi\hapi\lib\handler.js:31:20)
at async Request._lifecycle (C:\monorepo\node_modules\@hapi\hapi\lib\request.js:370:32)
at async Request._execute (C:\monorepo\node_modules\@hapi\hapi\lib\request.js:280:9)
× underlyingStream.setContentType is not a function
Ideal solution: It provides me with a Lambda URL to use.
Minimal solution: I'm able to use it through API Gateway.
Additional context/Screenshots
Here are the contents of UserFunction.js: https://gist.github.com/serg06/0653a4c690a7f5854f8038e3ebe62a64
As serverless-offline added experimental supports for ALB and not just API Gateway, it could be argued that support for Lambda Function URLs could fit inside this project scope. Many users may be migrating from REST/HTTP API Gateway to Function URLs, and there seems to be no valid alternatives for local development today.
To simulate AWS environment, Lambda Function URLs do require hostname or port based routing, as when function is exposed via Function URL, any HTTP method and request path invokes the Lambda function. Adding path-prefix in serverless-offline to expose different functions will introduce different behavior than the simulated AWS environment.
Lambda Function URL can be set in buffered or streaming mode.
To support streaming mode in serverless-offline with Lambda In-Process runner we have to:
- expose additional hapi server with host-based routing to support multiple Lambda URLs. This is based on discussion in https://github.com/dherault/serverless-offline/issues/1382
- define exposed Lambda URLs as buffering or streaming based on https://github.com/serverless/serverless/issues/11906
- extend LambdaFunction with option to set hapi response stream
- after loading handler in Lambda In-Process runner check for symbol set by awslambda.streamifyResponse() and call it passing hapi response stream as underlaying stream
Based on some tests done in AWS environment, both decorated and un-decorated handlers can be invoked via function URLs in both invoke modes.
I am slowly working on this to allow local development for streaming responses.
Any update on this?
Any updates on this? I can see that RESPONSE_STREAM is supported however I get the same error when trying to call the function locally using serverless-offline: TypeError: underlyingStream.setContentType is not a function.
Thanks!
+1
+1 Same problem here. Trying with serverless-offline-lambda-function-urls plugin, and is failing too.
+1 Same problem:
"errorMessage": "underlyingStream.setContentType is not a function",
"errorType": "TypeError",
"stackTrace": [
"TypeError: underlyingStream.setContentType is not a function"
Any movement on this?