Skip to content

Commit 3ddd640

Browse files
sebstoSebastien Stormacq
andauthored
Add Streaming Lambda Examples with API Gateway and Function URL (#615)
## Overview This PR reorganizes and enhances the streaming Lambda examples by splitting them into two distinct examples that demonstrate different invocation methods: 1. **Streaming+FunctionUrl** - Streaming responses via Lambda Function URLs 2. **Streaming+APIGateway** - Streaming responses via API Gateway REST API ## Changes ### 🔄 Restructured Examples - **Renamed**: `Examples/Streaming/` → `Examples/Streaming+FunctionUrl/` - Maintains the original streaming example using Lambda Function URLs - Updated documentation to clarify Function URL-specific configuration - Improved AWS credentials handling in curl examples - **New**: `Examples/Streaming+APIGateway/` - Comprehensive example demonstrating API Gateway REST API with response streaming - Complete SAM template with proper IAM roles and streaming configuration - Detailed documentation covering API Gateway-specific setup ### 📚 Documentation Improvements #### Streaming+FunctionUrl - Clarified that this example uses Lambda Function URLs - Updated curl examples to use `eval $(aws configure export-credentials --format env)` for cleaner credential handling - Maintained all existing functionality and deployment instructions #### Streaming+APIGateway (New) - **316-line comprehensive README** covering: - Response streaming concepts and benefits - HTTP status code and header configuration - Streaming response body patterns - Local testing instructions - Complete SAM deployment guide with detailed template explanation - API Gateway-specific invocation with AWS Sigv4 authentication - Payload format documentation with example JSON - Security and reliability best practices - How API Gateway streaming works under the hood ### 🛠️ Technical Details #### API Gateway Streaming Configuration The new example demonstrates: - Special Lambda URI: `/response-streaming-invocations` endpoint - `responseTransferMode: STREAM` configuration - IAM role with both `lambda:InvokeFunction` and `lambda:InvokeWithResponseStream` permissions - Proper timeout configuration (60s) to accommodate streaming duration #### SAM Template Features ```yaml - Lambda function with streaming support (arm64, provided.al2) - API Gateway REST API with OpenAPI 3.0 definition - IAM execution role for API Gateway to invoke Lambda with streaming - Complete outputs for easy testing (API URL and Lambda ARN) ``` ### 🔐 Security Enhancements Both examples now include comprehensive security best practices: - API Gateway access logging - Throttling configuration - AWS WAF integration recommendations - Lambda concurrent execution limits - Environment variable encryption - Dead Letter Queue (DLQ) configuration - VPC configuration guidance ### 🧪 Testing Both examples support: - **Local testing**: `swift run` with curl invocation on port 7000 - **AWS deployment**: Complete SAM templates with deployment instructions - **Authenticated invocation**: AWS Sigv4 examples with proper credential handling ## Benefits 1. **Clearer separation**: Developers can now easily choose between Function URLs and API Gateway based on their use case 2. **Better documentation**: Each example has tailored documentation for its specific invocation method 3. **Production-ready**: Includes security best practices and proper IAM configuration 4. **Easier testing**: Improved credential handling in curl examples ## Breaking Changes None - this is purely additive. The original streaming example is preserved as `Streaming+FunctionUrl`. ## Testing Checklist - [x] Local testing works for both examples - [x] SAM deployment templates are valid - [x] Documentation is comprehensive and accurate - [x] Security best practices are documented - [x] Curl examples work with proper authentication ## Related Documentation - [AWS Lambda Response Streaming](https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html) - [API Gateway Lambda Proxy Integration with Streaming](https://docs.aws.amazon.com/apigateway/latest/developerguide/response-streaming-lambda-configure.html) - [Lambda Function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html) EOF --------- Co-authored-by: Sebastien Stormacq <stormacq@amazon.lu>
1 parent a590d08 commit 3ddd640

File tree

16 files changed

+561
-14
lines changed

16 files changed

+561
-14
lines changed

.github/workflows/pull_request.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
# We pass the list of examples here, but we can't pass an array as argument
4141
# Instead, we pass a String with a valid JSON array.
4242
# The workaround is mentioned here https://github.com/orgs/community/discussions/11692
43-
examples: "[ 'APIGatewayV1', 'APIGatewayV2', 'APIGatewayV2+LambdaAuthorizer', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'HelloWorldNoTraits', 'HummingbirdLambda', 'MultiSourceAPI', 'MultiTenant', 'ResourcesPackaging', 'S3EventNotifier', 'S3_AWSSDK', 'S3_Soto', 'Streaming', 'Streaming+Codable', 'ServiceLifecycle+Postgres', 'Testing', 'Tutorial' ]"
43+
examples: "[ 'APIGatewayV1', 'APIGatewayV2', 'APIGatewayV2+LambdaAuthorizer', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'HelloWorldNoTraits', 'HummingbirdLambda', 'MultiSourceAPI', 'MultiTenant', 'ResourcesPackaging', 'S3EventNotifier', 'S3_AWSSDK', 'S3_Soto', 'Streaming+APIGateway', 'Streaming+FunctionUrl', 'Streaming+Codable', 'ServiceLifecycle+Postgres', 'Testing', 'Tutorial' ]"
4444
archive_plugin_examples: "[ 'HelloWorld', 'ResourcesPackaging' ]"
4545
archive_plugin_enabled: true
4646

Examples/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ This directory contains example code for Lambda functions.
3838

3939
- **[S3_Soto](S3_Soto/README.md)**: a Lambda function that uses [Soto](https://github.com/soto-project/soto) to invoke an [Amazon S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html) API (requires [AWS SAM](https://aws.amazon.com/serverless/sam/)).
4040

41-
- **[Streaming](Streaming/README.md)**: create a Lambda function exposed as an URL. The Lambda function streams its response over time. (requires [AWS SAM](https://aws.amazon.com/serverless/sam/)).
41+
- **[Streaming with APIGateway](Streaming+APIGateway/README.md)**: create a Lambda function exposed by a REST API Gateway. The Lambda function streams its response over time. (requires [AWS SAM](https://aws.amazon.com/serverless/sam/)).
42+
43+
- **[Streaming with Function Url](Streaming+FunctionUrl/README.md)**: create a Lambda function exposed as an URL. The Lambda function streams its response over time. (requires [AWS SAM](https://aws.amazon.com/serverless/sam/)).
4244

4345
- **[Streaming+Codable](Streaming+Codable/README.md)**: a Lambda function that combines JSON input decoding with response streaming capabilities, demonstrating a streaming codable interface (requires [AWS SAM](https://aws.amazon.com/serverless/sam/) or the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)).
4446

Examples/Streaming/Package.swift renamed to Examples/Streaming+APIGateway/Package.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@ let package = Package(
1010
],
1111
dependencies: [
1212
// For local development (default)
13-
.package(name: "swift-aws-lambda-runtime", path: "../..")
13+
.package(name: "swift-aws-lambda-runtime", path: "../.."),
1414

1515
// For standalone usage, comment the line above and uncomment below:
1616
// .package(url: "https://github.com/awslabs/swift-aws-lambda-runtime.git", from: "1.0.0"),
17+
.package(url: "https://github.com/awslabs/swift-aws-lambda-events.git", from: "1.0.0"),
1718
],
1819
targets: [
1920
.executableTarget(
2021
name: "StreamingNumbers",
2122
dependencies: [
22-
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime")
23+
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
24+
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
2325
],
2426
path: "Sources"
2527
)
Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
# Streaming Lambda function with API Gateway
2+
3+
You can configure your Lambda function to stream response payloads back to clients through Amazon API Gateway. Response streaming can benefit latency sensitive applications by improving time to first byte (TTFB) performance. This is because you can send partial responses back to the client as they become available. Additionally, you can use response streaming to build functions that return larger payloads. Response stream payloads have a soft limit of 200 MB as compared to the 6 MB limit for buffered responses. Streaming a response also means that your function doesn't need to fit the entire response in memory. For very large responses, this can reduce the amount of memory you need to configure for your function.
4+
5+
Streaming responses incurs a cost. For more information, see [AWS Lambda Pricing](https://aws.amazon.com/lambda/pricing/).
6+
7+
You can stream responses through Lambda function URLs, **Amazon API Gateway**, the AWS SDK, or using the Lambda [InvokeWithResponseStream](https://docs.aws.amazon.com/lambda/latest/dg/API_InvokeWithResponseStream.html) API. In this example, we expose the streaming Lambda function through **API Gateway REST API** with response streaming enabled.
8+
9+
For more information about configuring Lambda response streaming with API Gateway, see [Configure a Lambda proxy integration with payload response streaming](https://docs.aws.amazon.com/apigateway/latest/developerguide/response-streaming-lambda-configure.html).
10+
11+
## Code
12+
13+
The sample code creates a `SendNumbersWithPause` struct that conforms to the `StreamingLambdaHandler` protocol provided by the Swift AWS Lambda Runtime.
14+
15+
The `handle(...)` method of this protocol receives incoming events as a Swift NIO `ByteBuffer` and returns the output as a `ByteBuffer`.
16+
17+
The response is streamed through the `LambdaResponseStreamWriter`, which is passed as an argument in the `handle` function.
18+
19+
### Setting HTTP Status Code and Headers
20+
21+
Before streaming the response body, you can set the HTTP status code and headers using the `writeStatusAndHeaders(_:)` method:
22+
23+
```swift
24+
try await responseWriter.writeStatusAndHeaders(
25+
StreamingLambdaStatusAndHeadersResponse(
26+
statusCode: 200,
27+
headers: [
28+
"Content-Type": "text/plain",
29+
"x-my-custom-header": "streaming-example"
30+
]
31+
)
32+
)
33+
```
34+
35+
The `StreamingLambdaStatusAndHeadersResponse` structure allows you to specify:
36+
- **statusCode**: HTTP status code (e.g., 200, 404, 500)
37+
- **headers**: Dictionary of single-value HTTP headers (optional)
38+
39+
### Streaming the Response Body
40+
41+
After setting headers, you can stream the response body by calling the `write(_:)` function of the `LambdaResponseStreamWriter` with partial data repeatedly before finally closing the response stream by calling `finish()`. Developers can also choose to return the entire output and not stream the response by calling `writeAndFinish(_:)`.
42+
43+
```swift
44+
// Stream data in chunks
45+
for i in 1...3 {
46+
try await responseWriter.write(ByteBuffer(string: "Number: \(i)\n"))
47+
try await Task.sleep(for: .milliseconds(1000))
48+
}
49+
50+
// Close the response stream
51+
try await responseWriter.finish()
52+
```
53+
54+
An error is thrown if `finish()` is called multiple times or if it is called after having called `writeAndFinish(_:)`.
55+
56+
### Example Usage Patterns
57+
58+
The example includes a **SendNumbersWithPause** handler that demonstrates basic streaming with headers, sending numbers with delays
59+
60+
The `handle(...)` method is marked as `mutating` to allow handlers to be implemented with a `struct`.
61+
62+
Once the struct is created and the `handle(...)` method is defined, the sample code creates a `LambdaRuntime` struct and initializes it with the handler just created. Then, the code calls `run()` to start the interaction with the AWS Lambda control plane.
63+
64+
## Build & Package
65+
66+
To build & archive the package, type the following commands.
67+
68+
```bash
69+
swift package archive --allow-network-connections docker
70+
```
71+
72+
If there is no error, there is a ZIP file ready to deploy.
73+
The ZIP file is located at `.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/StreamingNumbers/StreamingNumbers.zip`
74+
75+
## Test locally
76+
77+
You can test the function locally before deploying:
78+
79+
```bash
80+
swift run
81+
82+
# In another terminal, test with curl:
83+
curl -v --output response.txt \
84+
--header "Content-Type: application/json" \
85+
--data '"this is not used"' \
86+
http://127.0.0.1:7000/invoke
87+
```
88+
89+
## Deploy with AWS SAM
90+
91+
[AWS SAM](https://aws.amazon.com/serverless/sam/) provides a streamlined way to deploy Lambda functions with API Gateway streaming support.
92+
93+
**Prerequisites**: Install the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)
94+
95+
### SAM Template
96+
97+
The template file is provided as part of the example in the `template.yaml` file. It defines:
98+
99+
- A Lambda function with streaming support
100+
- An API Gateway REST API configured for response streaming
101+
- An IAM role that allows API Gateway to invoke the Lambda function with streaming
102+
- The `/stream` endpoint that accepts any HTTP method
103+
104+
Key configuration details:
105+
106+
```yaml
107+
Resources:
108+
StreamingNumbers:
109+
Type: AWS::Serverless::Function
110+
Properties:
111+
CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/StreamingNumbers/StreamingNumbers.zip
112+
Timeout: 60 # Must be bigger than the time it takes to stream the output
113+
Handler: swift.bootstrap
114+
Runtime: provided.al2
115+
MemorySize: 128
116+
Architectures:
117+
- arm64
118+
Events:
119+
StreamingApi:
120+
Type: Api
121+
Properties:
122+
RestApiId: !Ref StreamingApi
123+
Path: /stream
124+
Method: ANY
125+
126+
StreamingApi:
127+
Type: AWS::Serverless::Api
128+
Properties:
129+
StageName: prod
130+
DefinitionBody:
131+
openapi: "3.0.1"
132+
info:
133+
title: "StreamingAPI"
134+
version: "1.0"
135+
paths:
136+
/stream:
137+
x-amazon-apigateway-any-method:
138+
x-amazon-apigateway-integration:
139+
httpMethod: POST
140+
type: aws_proxy
141+
# Special URI for streaming invocations
142+
uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2021-11-15/functions/${StreamingNumbers.Arn}/response-streaming-invocations"
143+
timeoutInMillis: 60000
144+
responseTransferMode: STREAM # Enable streaming
145+
credentials: !GetAtt ApiGatewayLambdaInvokeRole.Arn
146+
```
147+
148+
> [!IMPORTANT]
149+
> The timeout value must be bigger than the time it takes for your function to stream its output. Otherwise, the Lambda control plane will terminate the execution environment before your code has a chance to finish writing the stream. The sample function streams responses over 3 seconds, and we set the timeout to 60 seconds for safety.
150+
151+
### Deploy with SAM
152+
153+
```bash
154+
sam deploy \
155+
--resolve-s3 \
156+
--template-file template.yaml \
157+
--stack-name StreamingNumbers \
158+
--capabilities CAPABILITY_IAM
159+
```
160+
161+
The API Gateway endpoint URL is provided as part of the output:
162+
163+
```
164+
CloudFormation outputs from deployed stack
165+
-----------------------------------------------------------------------------------------------------------------------------
166+
Outputs
167+
-----------------------------------------------------------------------------------------------------------------------------
168+
Key ApiUrl
169+
Description API Gateway endpoint URL for streaming
170+
Value https://abc123xyz.execute-api.us-east-1.amazonaws.com/prod/stream
171+
-----------------------------------------------------------------------------------------------------------------------------
172+
Key LambdaArn
173+
Description Lambda Function ARN
174+
Value arn:aws:lambda:us-east-1:123456789012:function:StreamingNumbers-StreamingNumbers-ABC123
175+
-----------------------------------------------------------------------------------------------------------------------------
176+
```
177+
178+
### Invoke the API Gateway endpoint
179+
180+
To invoke the streaming API through API Gateway, use `curl` with AWS Sigv4 authentication:
181+
182+
#### Get AWS Credentials
183+
184+
Read the [AWS Credentials and Signature](../README.md/#AWS-Credentials-and-Signature) section for more details about the AWS Sigv4 protocol and how to obtain AWS credentials.
185+
186+
When you have the `aws` command line installed and configured, you will find the credentials in the `~/.aws/credentials` file.
187+
188+
#### Invoke with authentication
189+
190+
```bash
191+
# Set your values
192+
API_URL=https://abc123xyz.execute-api.us-east-1.amazonaws.com/prod/stream
193+
REGION=us-east-1
194+
# Set the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN environment variables
195+
eval $(aws configure export-credentials --format env)
196+
197+
# Invoke the streaming API
198+
curl "$API_URL" \
199+
--user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
200+
--aws-sigv4 "aws:amz:$REGION:execute-api" \
201+
-H "x-amz-security-token: $AWS_SESSION_TOKEN" \
202+
--no-buffer
203+
```
204+
205+
> [!NOTE]
206+
> - The `--no-buffer` flag is important for streaming responses - it ensures curl displays data as it arrives
207+
> - The service name for API Gateway is `execute-api` (not `lambda`)
208+
> - If you're not using temporary credentials (session token), you can omit the `x-amz-security-token` header
209+
210+
This should output the following result, with a one-second delay between each number:
211+
212+
```
213+
1
214+
2
215+
3
216+
Streaming complete!
217+
```
218+
219+
### Alternative: Test without authentication (not recommended for production)
220+
221+
If you want to test without authentication, you can modify the API Gateway to use `NONE` auth type. However, this is **not recommended for production** as it exposes your API publicly.
222+
223+
To enable public access for testing, modify the `template.yaml`:
224+
225+
```yaml
226+
StreamingApi:
227+
Type: AWS::Serverless::Api
228+
Properties:
229+
StageName: prod
230+
Auth:
231+
DefaultAuthorizer: NONE
232+
# ... rest of configuration
233+
```
234+
235+
Then you can invoke without credentials:
236+
237+
```bash
238+
curl https://abc123xyz.execute-api.us-east-1.amazonaws.com/prod/stream --no-buffer
239+
```
240+
241+
### Undeploy with SAM
242+
243+
When done testing, you can delete the infrastructure with this command:
244+
245+
```bash
246+
sam delete --stack-name StreamingNumbers
247+
```
248+
249+
## Payload decoding
250+
251+
When you invoke the function through API Gateway, the incoming `ByteBuffer` contains a payload that gives developer access to the underlying HTTP call. The payload contains information about the HTTP verb used, the headers received, the authentication method, and more.
252+
253+
The [AWS documentation contains the details](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format) of the payload format. The [Swift Lambda Event library](https://github.com/awslabs/swift-aws-lambda-events) contains an [`APIGatewayV2Request` type](https://github.com/awslabs/swift-aws-lambda-events/blob/main/Sources/AWSLambdaEvents/APIGatewayV2.swift) ready to use in your projects.
254+
255+
Here is an example of API Gateway proxy integration payload:
256+
257+
```json
258+
{
259+
"version": "2.0",
260+
"routeKey": "ANY /stream",
261+
"rawPath": "/prod/stream",
262+
"rawQueryString": "",
263+
"headers": {
264+
"accept": "*/*",
265+
"content-length": "0",
266+
"host": "abc123xyz.execute-api.us-east-1.amazonaws.com",
267+
"user-agent": "curl/8.7.1",
268+
"x-amzn-trace-id": "Root=1-67890abc-1234567890abcdef",
269+
"x-forwarded-for": "203.0.113.1",
270+
"x-forwarded-port": "443",
271+
"x-forwarded-proto": "https"
272+
},
273+
"requestContext": {
274+
"accountId": "123456789012",
275+
"apiId": "abc123xyz",
276+
"domainName": "abc123xyz.execute-api.us-east-1.amazonaws.com",
277+
"domainPrefix": "abc123xyz",
278+
"http": {
279+
"method": "GET",
280+
"path": "/prod/stream",
281+
"protocol": "HTTP/1.1",
282+
"sourceIp": "203.0.113.1",
283+
"userAgent": "curl/8.7.1"
284+
},
285+
"requestId": "abc123-def456-ghi789",
286+
"routeKey": "ANY /stream",
287+
"stage": "prod",
288+
"time": "30/Nov/2025:10:30:00 +0000",
289+
"timeEpoch": 1733000000000
290+
},
291+
"isBase64Encoded": false
292+
}
293+
```
294+
295+
## How API Gateway Streaming Works
296+
297+
When you configure API Gateway with `responseTransferMode: STREAM`:
298+
299+
1. **Special Lambda URI**: API Gateway uses the `/response-streaming-invocations` endpoint instead of the standard `/invocations` endpoint
300+
2. **InvokeWithResponseStream API**: API Gateway calls the Lambda `InvokeWithResponseStream` API instead of the standard `Invoke` API
301+
3. **Chunked Transfer**: Responses are sent using HTTP chunked transfer encoding, allowing data to flow as it's generated
302+
4. **IAM Permissions**: The API Gateway execution role needs both `lambda:InvokeFunction` and `lambda:InvokeWithResponseStream` permissions
303+
304+
## ⚠️ Security and Reliability Notice
305+
306+
These are example applications for demonstration purposes. When deploying such infrastructure in production environments, we strongly encourage you to follow these best practices for improved security and resiliency:
307+
308+
- **Enable access logging on API Gateway** ([documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-logging.html))
309+
- **Configure API Gateway throttling** to protect against abuse ([documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html))
310+
- **Use AWS WAF** with API Gateway for additional security ([documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-control-access-aws-waf.html))
311+
- **Ensure Lambda function has concurrent execution limits** ([concurrency documentation](https://docs.aws.amazon.com/lambda/latest/dg/lambda-concurrency.html), [configuration guide](https://docs.aws.amazon.com/lambda/latest/dg/configuration-concurrency.html))
312+
- **Enable encryption for Lambda environment variables** ([documentation](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars-encryption.html))
313+
- **Configure a Dead Letter Queue (DLQ)** for Lambda ([documentation](https://docs.aws.amazon.com/lambda/latest/dg/invocation-async-retain-records.html#invocation-dlq))
314+
- **Use VPC configuration** when Lambda needs to access private resources ([documentation](https://docs.aws.amazon.com/lambda/latest/dg/configuration-vpc.html), [code example](https://github.com/awslabs/swift-aws-lambda-runtime/tree/main/Examples/ServiceLifecycle%2BPostgres))
315+
- **Implement proper IAM authentication** instead of public access for production APIs
316+
- **Enable CloudWatch Logs** for both API Gateway and Lambda for monitoring and debugging

0 commit comments

Comments
 (0)