Skip to content

Revert streaming codable handler and provide it as an example, not an API #549

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
# We pass the list of examples here, but we can't pass an array as argument
# Instead, we pass a String with a valid JSON array.
# The workaround is mentioned here https://github.com/orgs/community/discussions/11692
examples: "[ 'APIGateway', 'APIGateway+LambdaAuthorizer', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'HummingbirdLambda', 'ResourcesPackaging', 'S3EventNotifier', 'S3_AWSSDK', 'S3_Soto', 'Streaming', 'StreamingFromEvent', 'ServiceLifecycle+Postgres', 'Testing', 'Tutorial' ]"
examples: "[ 'APIGateway', 'APIGateway+LambdaAuthorizer', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'HummingbirdLambda', 'ResourcesPackaging', 'S3EventNotifier', 'S3_AWSSDK', 'S3_Soto', 'Streaming', 'Streaming+Codable', 'ServiceLifecycle+Postgres', 'Testing', 'Tutorial' ]"
archive_plugin_examples: "[ 'HelloWorld', 'ResourcesPackaging' ]"
archive_plugin_enabled: true

Expand Down
2 changes: 1 addition & 1 deletion Examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ This directory contains example code for Lambda functions.

- **[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/)).

- **[StreamingFromEvent](StreamingFromEvent/README.md)**: a Lambda function that combines JSON input decoding with response streaming capabilities, demonstrating the new 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)).
- **[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)).

- **[Testing](Testing/README.md)**: a test suite for Lambda functions.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,28 @@ import PackageDescription
import struct Foundation.URL

let package = Package(
name: "StreamingFromEvent",
name: "StreamingCodable",
platforms: [.macOS(.v15)],
dependencies: [
// during CI, the dependency on local version of swift-aws-lambda-runtime is added dynamically below
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", from: "2.0.0-beta.1")
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", from: "2.0.0-beta.1"),
.package(url: "https://github.com/swift-server/swift-aws-lambda-events.git", from: "1.2.0"),
],
targets: [
.executableTarget(
name: "StreamingFromEvent",
name: "StreamingCodable",
dependencies: [
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime")
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
]
)
),
.testTarget(
name: "Streaming+CodableTests",
dependencies: [
"StreamingCodable",
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
]
),
]
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
# Streaming Codable Lambda function

This example demonstrates how to use the `StreamingLambdaHandlerWithEvent` protocol to create Lambda functions that:
This example demonstrates how to use a `StreamingLambdaHandlerWithEvent` protocol to create Lambda functions, exposed through a FunctionUrl, that:

1. **Receive JSON input**: Automatically decode JSON events into Swift structs
2. **Stream responses**: Send data incrementally as it becomes available
3. **Execute background work**: Perform additional processing after the response is sent

The example uses the streaming codable interface that combines the benefits of:
## When to Use This Approach

**⚠️ Important Limitations:**

1. **Function URL Only**: This streaming codable approach only works with Lambda functions exposed through [Lambda Function URLs](https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html)
2. **Limited Request Access**: This approach hides the details of the `FunctionURLRequest` (like HTTP headers, query parameters, etc.) from developers

**Decision Rule:**

- **Use this streaming codable approach when:**
- Your function is exposed through a Lambda Function URL
- You have a JSON payload that you want automatically decoded
- You don't need to inspect HTTP headers, query parameters, or other request details
- You prioritize convenience over flexibility

- **Use the ByteBuffer `StreamingLambdaHandler` approach when:**
- You need full control over the `FunctionURLRequest` details
- You're invoking the Lambda through other means (API Gateway, direct invocation, etc.)
- You need access to HTTP headers, query parameters, or request metadata
- You require maximum flexibility (requires writing more code)

This example balances convenience and flexibility. The streaming codable interface combines the benefits of:
- Type-safe JSON input decoding (like regular `LambdaHandler`)
- Response streaming capabilities (like `StreamingLambdaHandler`)
- Background work execution after response completion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
//
//===----------------------------------------------------------------------===//

import AWSLambdaEvents
import AWSLambdaRuntime
import Logging
import NIOCore

Expand Down Expand Up @@ -71,6 +73,9 @@ public struct StreamingLambdaCodableAdapter<
}

/// Handles the raw ByteBuffer by decoding it and passing to the underlying handler.
/// This function attempts to decode the event as a `FunctionURLRequest` first, which allows for
/// handling Function URL requests that may have a base64-encoded body.
/// If the decoding fails, it falls back to decoding the event "as-is" with the provided JSON type.
/// - Parameters:
/// - event: The raw ByteBuffer event to decode.
/// - responseWriter: The response writer to pass to the underlying handler.
Expand All @@ -82,43 +87,20 @@ public struct StreamingLambdaCodableAdapter<
context: LambdaContext
) async throws {

// try to decode the event as a FunctionURLRequest and extract its body
let urlRequestBody = bodyFromFunctionURLRequest(event)
var decodedBody: Handler.Event!

// decode the body or the event as user-provided JSON
let decodedEvent = try self.decoder.decode(Handler.Event.self, from: urlRequestBody ?? event)
// try to decode the event as a FunctionURLRequest, then fetch its body attribute
if let request = try? self.decoder.decode(FunctionURLRequest.self, from: event) {
// decode the body as user-provided JSON type
// this function handles the base64 decoding when needed
decodedBody = try request.decodeBody(Handler.Event.self)
} else {
// try to decode the event "as-is" with the provided JSON type
decodedBody = try self.decoder.decode(Handler.Event.self, from: event)
}

// and pass it to the handler
try await self.handler.handle(decodedEvent, responseWriter: responseWriter, context: context)
}

/// Extract the body payload from a FunctionURLRequest event.
/// This function checks if the event is a valid `FunctionURLRequest` and decodes the body if it is base64 encoded.
/// If the event is not a valid `FunctionURLRequest`, it returns nil.
/// - Parameter event: The raw ByteBuffer event to check.
/// - Returns: The base64 decoded body of the FunctionURLRequest if it is a valid FunctionURLRequest, otherwise nil.
@inlinable
package func bodyFromFunctionURLRequest(_ event: ByteBuffer) -> ByteBuffer? {
do {
// try to decode as a FunctionURLRequest
let request = try self.decoder.decode(FunctionURLRequest.self, from: event)

// if the body is encoded in base64, decode it
if request.isBase64Encoded,
let base64EncodedString = request.body,
// this is the minimal way to base64 decode without importing new dependencies
let decodedData = Data(base64Encoded: base64EncodedString),
let decodedString = String(data: decodedData, encoding: .utf8)
{

return ByteBuffer(string: decodedString)
} else {
return ByteBuffer(string: request.body ?? "")
}
} catch {
// not a FunctionURLRequest, return nil
return nil
}
try await self.handler.handle(decodedBody, responseWriter: responseWriter, context: context)
}
}

Expand Down Expand Up @@ -149,8 +131,6 @@ public struct StreamingFromEventClosureHandler<Event: Decodable>: StreamingLambd
}
}

#if FoundationJSONSupport

extension StreamingLambdaCodableAdapter {
/// Initialize with a JSON decoder and handler.
/// - Parameters:
Expand Down Expand Up @@ -203,4 +183,3 @@ extension LambdaRuntime {
self.init(handler: adapter, logger: logger)
}
}
#endif // FoundationJSONSupport
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Synchronization
import Testing

@testable import AWSLambdaRuntime
@testable import StreamingCodable

#if canImport(FoundationEssentials)
import FoundationEssentials
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ Description: SAM Template for StreamingfromEvent Example

Resources:
# Lambda function
StreamingFromEvent:
StreamingCodable:
Type: AWS::Serverless::Function
Properties:
CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/StreamingFromEvent/StreamingFromEvent.zip
CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/StreamingCodable/StreamingCodable.zip
Timeout: 15
Handler: swift.bootstrap # ignored by the Swift runtime
Runtime: provided.al2
Expand All @@ -22,4 +22,4 @@ Outputs:
# print Lambda function URL
LambdaURL:
Description: Lambda URL
Value: !GetAtt StreamingFromEventUrl.FunctionUrl
Value: !GetAtt StreamingCodableUrl.FunctionUrl

This file was deleted.

Loading