Files
Sébastien Stormacq 3ddd64087d 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>
2025-12-05 16:38:50 -08:00

15 KiB

Streaming Lambda function with API Gateway

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.

Streaming responses incurs a cost. For more information, see AWS Lambda Pricing.

You can stream responses through Lambda function URLs, Amazon API Gateway, the AWS SDK, or using the Lambda InvokeWithResponseStream API. In this example, we expose the streaming Lambda function through API Gateway REST API with response streaming enabled.

For more information about configuring Lambda response streaming with API Gateway, see Configure a Lambda proxy integration with payload response streaming.

Code

The sample code creates a SendNumbersWithPause struct that conforms to the StreamingLambdaHandler protocol provided by the Swift AWS Lambda Runtime.

The handle(...) method of this protocol receives incoming events as a Swift NIO ByteBuffer and returns the output as a ByteBuffer.

The response is streamed through the LambdaResponseStreamWriter, which is passed as an argument in the handle function.

Setting HTTP Status Code and Headers

Before streaming the response body, you can set the HTTP status code and headers using the writeStatusAndHeaders(_:) method:

try await responseWriter.writeStatusAndHeaders(
    StreamingLambdaStatusAndHeadersResponse(
        statusCode: 200,
        headers: [
            "Content-Type": "text/plain",
            "x-my-custom-header": "streaming-example"
        ]
    )
)

The StreamingLambdaStatusAndHeadersResponse structure allows you to specify:

  • statusCode: HTTP status code (e.g., 200, 404, 500)
  • headers: Dictionary of single-value HTTP headers (optional)

Streaming the Response Body

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(_:).

// Stream data in chunks
for i in 1...3 {
    try await responseWriter.write(ByteBuffer(string: "Number: \(i)\n"))
    try await Task.sleep(for: .milliseconds(1000))
}

// Close the response stream
try await responseWriter.finish()

An error is thrown if finish() is called multiple times or if it is called after having called writeAndFinish(_:).

Example Usage Patterns

The example includes a SendNumbersWithPause handler that demonstrates basic streaming with headers, sending numbers with delays

The handle(...) method is marked as mutating to allow handlers to be implemented with a struct.

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.

Build & Package

To build & archive the package, type the following commands.

swift package archive --allow-network-connections docker

If there is no error, there is a ZIP file ready to deploy. The ZIP file is located at .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/StreamingNumbers/StreamingNumbers.zip

Test locally

You can test the function locally before deploying:

swift run 

# In another terminal, test with curl:
curl -v --output response.txt \
  --header "Content-Type: application/json" \
  --data '"this is not used"' \
  http://127.0.0.1:7000/invoke

Deploy with AWS SAM

AWS SAM provides a streamlined way to deploy Lambda functions with API Gateway streaming support.

Prerequisites: Install the SAM CLI

SAM Template

The template file is provided as part of the example in the template.yaml file. It defines:

  • A Lambda function with streaming support
  • An API Gateway REST API configured for response streaming
  • An IAM role that allows API Gateway to invoke the Lambda function with streaming
  • The /stream endpoint that accepts any HTTP method

Key configuration details:

Resources:
  StreamingNumbers:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/StreamingNumbers/StreamingNumbers.zip
      Timeout: 60  # Must be bigger than the time it takes to stream the output
      Handler: swift.bootstrap
      Runtime: provided.al2
      MemorySize: 128
      Architectures:
        - arm64
      Events:
        StreamingApi:
          Type: Api
          Properties:
            RestApiId: !Ref StreamingApi
            Path: /stream
            Method: ANY

  StreamingApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: prod
      DefinitionBody:
        openapi: "3.0.1"
        info:
          title: "StreamingAPI"
          version: "1.0"
        paths:
          /stream:
            x-amazon-apigateway-any-method:
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                # Special URI for streaming invocations
                uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2021-11-15/functions/${StreamingNumbers.Arn}/response-streaming-invocations"
                timeoutInMillis: 60000
                responseTransferMode: STREAM  # Enable streaming
                credentials: !GetAtt ApiGatewayLambdaInvokeRole.Arn

Important

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.

Deploy with SAM

sam deploy \
  --resolve-s3 \
  --template-file template.yaml \
  --stack-name StreamingNumbers \
  --capabilities CAPABILITY_IAM

The API Gateway endpoint URL is provided as part of the output:

CloudFormation outputs from deployed stack
-----------------------------------------------------------------------------------------------------------------------------
Outputs                                                                                                                                   
-----------------------------------------------------------------------------------------------------------------------------
Key                 ApiUrl                                                                                                             
Description         API Gateway endpoint URL for streaming                                                                                                            
Value               https://abc123xyz.execute-api.us-east-1.amazonaws.com/prod/stream                                                 
-----------------------------------------------------------------------------------------------------------------------------
Key                 LambdaArn                                                                                                             
Description         Lambda Function ARN                                                                                                            
Value               arn:aws:lambda:us-east-1:123456789012:function:StreamingNumbers-StreamingNumbers-ABC123                                                 
-----------------------------------------------------------------------------------------------------------------------------

Invoke the API Gateway endpoint

To invoke the streaming API through API Gateway, use curl with AWS Sigv4 authentication:

Get AWS Credentials

Read the AWS Credentials and Signature section for more details about the AWS Sigv4 protocol and how to obtain AWS credentials.

When you have the aws command line installed and configured, you will find the credentials in the ~/.aws/credentials file.

Invoke with authentication

# Set your values
API_URL=https://abc123xyz.execute-api.us-east-1.amazonaws.com/prod/stream
REGION=us-east-1
# Set the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN environment variables
eval $(aws configure export-credentials --format env)

# Invoke the streaming API
curl "$API_URL" \
     --user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
     --aws-sigv4 "aws:amz:$REGION:execute-api" \
     -H "x-amz-security-token: $AWS_SESSION_TOKEN" \
     --no-buffer

Note

  • The --no-buffer flag is important for streaming responses - it ensures curl displays data as it arrives
  • The service name for API Gateway is execute-api (not lambda)
  • If you're not using temporary credentials (session token), you can omit the x-amz-security-token header

This should output the following result, with a one-second delay between each number:

1
2
3
Streaming complete!

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.

To enable public access for testing, modify the template.yaml:

StreamingApi:
  Type: AWS::Serverless::Api
  Properties:
    StageName: prod
    Auth:
      DefaultAuthorizer: NONE
    # ... rest of configuration

Then you can invoke without credentials:

curl https://abc123xyz.execute-api.us-east-1.amazonaws.com/prod/stream --no-buffer

Undeploy with SAM

When done testing, you can delete the infrastructure with this command:

sam delete --stack-name StreamingNumbers

Payload decoding

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.

The AWS documentation contains the details of the payload format. The Swift Lambda Event library contains an APIGatewayV2Request type ready to use in your projects.

Here is an example of API Gateway proxy integration payload:

{
    "version": "2.0",
    "routeKey": "ANY /stream",
    "rawPath": "/prod/stream",
    "rawQueryString": "",
    "headers": {
        "accept": "*/*",
        "content-length": "0",
        "host": "abc123xyz.execute-api.us-east-1.amazonaws.com",
        "user-agent": "curl/8.7.1",
        "x-amzn-trace-id": "Root=1-67890abc-1234567890abcdef",
        "x-forwarded-for": "203.0.113.1",
        "x-forwarded-port": "443",
        "x-forwarded-proto": "https"
    },
    "requestContext": {
        "accountId": "123456789012",
        "apiId": "abc123xyz",
        "domainName": "abc123xyz.execute-api.us-east-1.amazonaws.com",
        "domainPrefix": "abc123xyz",
        "http": {
            "method": "GET",
            "path": "/prod/stream",
            "protocol": "HTTP/1.1",
            "sourceIp": "203.0.113.1",
            "userAgent": "curl/8.7.1"
        },
        "requestId": "abc123-def456-ghi789",
        "routeKey": "ANY /stream",
        "stage": "prod",
        "time": "30/Nov/2025:10:30:00 +0000",
        "timeEpoch": 1733000000000
    },
    "isBase64Encoded": false
}

How API Gateway Streaming Works

When you configure API Gateway with responseTransferMode: STREAM:

  1. Special Lambda URI: API Gateway uses the /response-streaming-invocations endpoint instead of the standard /invocations endpoint
  2. InvokeWithResponseStream API: API Gateway calls the Lambda InvokeWithResponseStream API instead of the standard Invoke API
  3. Chunked Transfer: Responses are sent using HTTP chunked transfer encoding, allowing data to flow as it's generated
  4. IAM Permissions: The API Gateway execution role needs both lambda:InvokeFunction and lambda:InvokeWithResponseStream permissions

⚠️ Security and Reliability Notice

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:

  • Enable access logging on API Gateway (documentation)
  • Configure API Gateway throttling to protect against abuse (documentation)
  • Use AWS WAF with API Gateway for additional security (documentation)
  • Ensure Lambda function has concurrent execution limits (concurrency documentation, configuration guide)
  • Enable encryption for Lambda environment variables (documentation)
  • Configure a Dead Letter Queue (DLQ) for Lambda (documentation)
  • Use VPC configuration when Lambda needs to access private resources (documentation, code example)
  • Implement proper IAM authentication instead of public access for production APIs
  • Enable CloudWatch Logs for both API Gateway and Lambda for monitoring and debugging