Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 3c5d217

Browse files
authoredNov 26, 2024··
Merge branch 'main' into ff-platform-dance
2 parents 18eabfc + 21e224e commit 3c5d217

File tree

34 files changed

+1232
-105
lines changed

34 files changed

+1232
-105
lines changed
 

‎.github/workflows/examples_matrix.yml renamed to ‎.github/workflows/integration_tests.yml

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: ExamplesMatrix
1+
name: IntegrationTests
22

33
on:
44
workflow_call:
@@ -7,10 +7,22 @@ on:
77
type: string
88
description: "The name of the workflow used for the concurrency group."
99
required: true
10-
# examples:
11-
# type: sequence
12-
# description: "The examples to run."
13-
# required: true
10+
# We pass the list of examples here, but we can't pass an array as argument
11+
# Instead, we pass a String with a valid JSON array.
12+
# The workaround is mentioned here https://github.com/orgs/community/discussions/11692
13+
examples:
14+
type: string
15+
description: "The list of examples to run. Pass a String with a valid JSON array such as \"[ 'HelloWorld', 'APIGateway' ]\""
16+
required: true
17+
default: ""
18+
examples_enabled:
19+
type: boolean
20+
description: "Boolean to enable the compilation of examples. Defaults to true."
21+
default: true
22+
archive_plugin_enabled:
23+
type: boolean
24+
description: "Boolean to enable the test of the archive plugin. Defaults to true."
25+
default: true
1426
matrix_linux_command:
1527
type: string
1628
description: "The command of the current Swift version linux matrix job to execute."
@@ -26,15 +38,14 @@ concurrency:
2638
cancel-in-progress: true
2739

2840
jobs:
29-
linux:
30-
name: Example/${{ matrix.examples }} on Linux ${{ matrix.swift.swift_version }}
41+
test-examples:
42+
name: Test Examples/${{ matrix.examples }} on ${{ matrix.swift.swift_version }}
43+
if: ${{ inputs.examples_enabled }}
3144
runs-on: ubuntu-latest
3245
strategy:
3346
fail-fast: false
3447
matrix:
35-
# This should be passed as an argument in input. Can we pass arrays as argument ?
36-
examples: ["HelloWorld", "APIGateway", "S3_AWSSDK", "S3_Soto"]
37-
# examples: ${{ inputs.examples }}
48+
examples: ${{ fromJson(inputs.examples) }}
3849

3950
# We are using only one Swift version
4051
swift:
@@ -78,3 +89,42 @@ jobs:
7889
EXAMPLE: ${{ matrix.examples }}
7990
run: |
8091
./scripts/integration_tests.sh
92+
echo "✅ The examples compile correctly"
93+
94+
test-archive-plugin:
95+
name: Test archive plugin
96+
if: ${{ inputs.archive_plugin_enabled }}
97+
runs-on: ubuntu-latest
98+
strategy:
99+
fail-fast: false
100+
steps:
101+
- name: Checkout repository
102+
uses: actions/checkout@v4
103+
with:
104+
persist-credentials: false
105+
- name: Mark the workspace as safe
106+
# https://github.com/actions/checkout/issues/766
107+
run: git config --global --add safe.directory ${GITHUB_WORKSPACE}
108+
- name: Test the archive plugin
109+
env:
110+
EXAMPLE: HelloWorld
111+
OUTPUT_FILE: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/MyLambda/bootstrap
112+
ZIP_FILE: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/MyLambda/MyLambda.zip
113+
run: |
114+
pushd Examples/${EXAMPLE}
115+
116+
# package the example (docker and swift toolchain are installed on the GH runner)
117+
LAMBDA_USE_LOCAL_DEPS=../.. swift package archive --allow-network-connections docker
118+
119+
# did the plugin generated a Linux binary?
120+
[ -f ${OUTPUT_FILE} ]
121+
file ${OUTPUT_FILE} | grep --silent ELF
122+
123+
# did the plugin created a ZIP file?
124+
[ -f ${ZIP_FILE} ]
125+
126+
# does the ZIP file contain the bootstrap?
127+
unzip -l ${ZIP_FILE} | grep --silent bootstrap
128+
129+
echo "✅ The archive plugin is OK"
130+
popd

‎.github/workflows/pull_request.yml

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ jobs:
1111
with:
1212
license_header_check_project_name: "SwiftAWSLambdaRuntime"
1313
shell_check_enabled: false
14+
python_lint_check_enabled: false
1415
api_breakage_check_container_image: "swift:6.0-noble"
1516
docs_check_container_image: "swift:6.0-noble"
1617
format_check_container_image: "swiftlang/swift:nightly-6.0-jammy"
@@ -19,21 +20,39 @@ jobs:
1920
name: Unit tests
2021
uses: apple/swift-nio/.github/workflows/unit_tests.yml@main
2122
with:
22-
linux_5_8_enabled: false
2323
linux_5_9_enabled: false
2424
linux_5_10_enabled: false
2525
linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error"
2626
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error"
2727

2828
integration-tests:
2929
name: Integration Tests
30-
uses: ./.github/workflows/examples_matrix.yml
30+
uses: ./.github/workflows/integration_tests.yml
3131
with:
32-
# We should pass the list of examples here, but we can't pass an array as argument
33-
# examples: [ "HelloWorld", "APIGateway" ]
3432
name: "Integration tests"
33+
examples_enabled: true
3534
matrix_linux_command: "LAMBDA_USE_LOCAL_DEPS=../.. swift build"
35+
# We pass the list of examples here, but we can't pass an array as argument
36+
# Instead, we pass a String with a valid JSON array.
37+
# The workaround is mentioned here https://github.com/orgs/community/discussions/11692
38+
examples: "[ 'APIGateway', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'S3_AWSSDK', 'S3_Soto', 'Streaming' ]"
39+
40+
archive_plugin_enabled: true
3641

3742
swift-6-language-mode:
3843
name: Swift 6 Language Mode
3944
uses: apple/swift-nio/.github/workflows/swift_6_language_mode.yml@main
45+
46+
# until there is a support for musl in swiftlang/github-workflows
47+
# https://github.com/swiftlang/github-workflows/issues/34
48+
musl:
49+
runs-on: ubuntu-latest
50+
container: swift:6.0.2-noble
51+
timeout-minutes: 30
52+
steps:
53+
- name: Check out code
54+
uses: actions/checkout@v4
55+
- name: Install SDK
56+
run: swift sdk install https://download.swift.org/swift-6.0.2-release/static-sdk/swift-6.0.2-RELEASE/swift-6.0.2-RELEASE_static-linux-0.0.1.artifactbundle.tar.gz --checksum aa5515476a403797223fc2aad4ca0c3bf83995d5427fb297cab1d93c68cee075
57+
- name: Build
58+
run: swift build --swift-sdk x86_64-swift-linux-musl

‎.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.DS_Store
22
*.build
3+
*.index-build
34
/.xcodeproj
45
*.pem
56
.podspecs
@@ -10,3 +11,4 @@ Package.resolved
1011
.serverless
1112
.vscode
1213
Makefile
14+
.devcontainer

‎Examples/APIGateway/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ To build the package, type the following commands.
2222

2323
```bash
2424
swift build
25-
swift package archive --allow-network-access docker
25+
swift package archive --allow-network-connections docker
2626
```
2727

2828
If there is no error, there is a ZIP file ready to deploy.

‎Examples/APIGateway/template.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
AWSTemplateFormatVersion: '2010-09-09'
22
Transform: AWS::Serverless-2016-10-31
3-
Description: SAM Template for QuoteService
3+
Description: SAM Template for APIGateway Lambda Example
44

55
Resources:
66
# Lambda function

‎Examples/BackgroundTasks/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// swift-tools-version:6.0
2+
3+
import PackageDescription
4+
5+
// needed for CI to test the local version of the library
6+
import struct Foundation.URL
7+
8+
#if os(macOS)
9+
let platforms: [PackageDescription.SupportedPlatform]? = [.macOS(.v15)]
10+
#else
11+
let platforms: [PackageDescription.SupportedPlatform]? = nil
12+
#endif
13+
14+
let package = Package(
15+
name: "swift-aws-lambda-runtime-example",
16+
platforms: platforms,
17+
products: [
18+
.executable(name: "BackgroundTasks", targets: ["BackgroundTasks"])
19+
],
20+
dependencies: [
21+
// during CI, the dependency on local version of swift-aws-lambda-runtime is added dynamically below
22+
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "main")
23+
],
24+
targets: [
25+
.executableTarget(
26+
name: "BackgroundTasks",
27+
dependencies: [
28+
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime")
29+
],
30+
path: "."
31+
)
32+
]
33+
)
34+
35+
if let localDepsPath = Context.environment["LAMBDA_USE_LOCAL_DEPS"],
36+
localDepsPath != "",
37+
let v = try? URL(fileURLWithPath: localDepsPath).resourceValues(forKeys: [.isDirectoryKey]),
38+
v.isDirectory == true
39+
{
40+
// when we use the local runtime as deps, let's remove the dependency added above
41+
let indexToRemove = package.dependencies.firstIndex { dependency in
42+
if case .sourceControl(
43+
name: _,
44+
location: "https://github.com/swift-server/swift-aws-lambda-runtime.git",
45+
requirement: _
46+
) = dependency.kind {
47+
return true
48+
}
49+
return false
50+
}
51+
if let indexToRemove {
52+
package.dependencies.remove(at: indexToRemove)
53+
}
54+
55+
// then we add the dependency on LAMBDA_USE_LOCAL_DEPS' path (typically ../..)
56+
print("[INFO] Compiling against swift-aws-lambda-runtime located at \(localDepsPath)")
57+
package.dependencies += [
58+
.package(name: "swift-aws-lambda-runtime", path: localDepsPath)
59+
]
60+
}

‎Examples/BackgroundTasks/README.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Background Tasks
2+
3+
This is an example for running background tasks in an AWS Lambda function.
4+
5+
Background tasks allow code to execute asynchronously after the main response has been returned, enabling additional processing without affecting response latency. This approach is ideal for scenarios like logging, data updates, or notifications that can be deferred. The code leverages Lambda's "Response Streaming" feature, which is effective for balancing real-time user responsiveness with the ability to perform extended tasks post-response.
6+
7+
For more information about Lambda background tasks, see [this AWS blog post](https://aws.amazon.com/blogs/compute/running-code-after-returning-a-response-from-an-aws-lambda-function/).
8+
9+
## Code
10+
11+
The sample code creates a `BackgroundProcessingHandler` struct that conforms to the `LambdaWithBackgroundProcessingHandler` protocol provided by the Swift AWS Lambda Runtime.
12+
13+
The `BackgroundProcessingHandler` struct defines the input and output JSON received and returned by the Handler.
14+
15+
The `handle(...)` method of this protocol receives incoming events as `Input` and returns the output as a `Greeting`. The `handle(...)` methods receives an `outputWriter` parameter to write the output before the function returns, giving some opportunities to run long-lasting tasks after the response has been returned to the client but before the function returns.
16+
17+
The `handle(...)` method uses the `outputWriter` to return the response as soon as possible. It then waits for 10 seconds to simulate a long background work. When the 10 seconds elapsed, the function returns. The billing cycle ends when the function returns.
18+
19+
The `handle(...)` method is marked as `mutating` to allow handlers to be implemented with a `struct`.
20+
21+
Once the struct is created and the `handle(...)` method is defined, the sample code creates a `LambdaCodableAdapter` adapter to adapt the `LambdaWithBackgroundProcessingHandler` to a type accepted by the `LambdaRuntime` struct. Then, the sample code initializes the `LambdaRuntime` with the adapter just created. Finally, the code calls `run()` to start the interaction with the AWS Lambda control plane.
22+
23+
## Build & Package
24+
25+
To build & archive the package, type the following commands.
26+
27+
```bash
28+
swift package archive --allow-network-connections docker
29+
```
30+
31+
If there is no error, there is a ZIP file ready to deploy.
32+
The ZIP file is located at `.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/BackgroundTasks/BackgroundTasks.zip`
33+
34+
## Deploy with the AWS CLI
35+
36+
Here is how to deploy using the `aws` command line.
37+
38+
### Create the function
39+
40+
```bash
41+
AWS_ACCOUNT_ID=012345678901
42+
aws lambda create-function \
43+
--function-name BackgroundTasks \
44+
--zip-file fileb://.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/BackgroundTasks/BackgroundTasks.zip \
45+
--runtime provided.al2 \
46+
--handler provided \
47+
--architectures arm64 \
48+
--role arn:aws:iam::${AWS_ACCOUNT_ID}:role/lambda_basic_execution \
49+
--environment "Variables={LOG_LEVEL=debug}" \
50+
--timeout 15
51+
```
52+
53+
> [!IMPORTANT]
54+
> The timeout value must be bigger than the time it takes for your function to complete its background tasks. Otherwise, the Lambda control plane will terminate the execution environment before your code has a chance to finish the tasks. Here, the sample function waits for 10 seconds and we set the timeout for 15 seconds.
55+
56+
The `--environment` arguments sets the `LOG_LEVEL` environment variable to `debug`. This will ensure the debugging statements in the handler `context.logger.debug("...")` are printed in the Lambda function logs.
57+
58+
The `--architectures` flag is only required when you build the binary on an Apple Silicon machine (Apple M1 or more recent). It defaults to `x64`.
59+
60+
Be sure to set `AWS_ACCOUNT_ID` with your actual AWS account ID (for example: 012345678901).
61+
62+
### Invoke your Lambda function
63+
64+
To invoke the Lambda function, use `aws` command line.
65+
```bash
66+
aws lambda invoke \
67+
--function-name BackgroundTasks \
68+
--cli-binary-format raw-in-base64-out \
69+
--payload '{ "message" : "Hello Background Tasks" }' \
70+
response.json
71+
```
72+
73+
This should immediately output the following result.
74+
75+
```
76+
{
77+
"StatusCode": 200,
78+
"ExecutedVersion": "$LATEST"
79+
}
80+
```
81+
82+
The response is visible in the `response.json` file.
83+
84+
```bash
85+
cat response.json
86+
{"echoedMessage":"Hello Background Tasks"}
87+
```
88+
89+
### View the function's logs
90+
91+
You can observe additional messages being logged after the response is received.
92+
93+
To tail the log, use the AWS CLI:
94+
```bash
95+
aws logs tail /aws/lambda/BackgroundTasks --follow
96+
```
97+
98+
This produces an output like:
99+
```text
100+
INIT_START Runtime Version: provided:al2.v59 Runtime Version ARN: arn:aws:lambda:us-east-1::runtime:974c4a90f22278a2ef1c3f53c5c152167318aaf123fbb07c055a4885a4e97e52
101+
START RequestId: 4c8edd74-d776-4df9-9714-19086ab59bfd Version: $LATEST
102+
debug LambdaRuntime : [BackgroundTasks] BackgroundProcessingHandler - message received
103+
debug LambdaRuntime : [BackgroundTasks] BackgroundProcessingHandler - response sent. Performing background tasks.
104+
debug LambdaRuntime : [BackgroundTasks] BackgroundProcessingHandler - Background tasks completed. Returning
105+
END RequestId: 4c8edd74-d776-4df9-9714-19086ab59bfd
106+
REPORT RequestId: 4c8edd74-d776-4df9-9714-19086ab59bfd Duration: 10160.89 ms Billed Duration: 10250 ms Memory Size: 128 MB Max Memory Used: 27 MB Init Duration: 88.20 ms
107+
```
108+
> [!NOTE]
109+
> The `debug` message are sent by the code inside the `handler()` function. Note that the `Duration` and `Billed Duration` on the last line are for 10.1 and 10.2 seconds respectively.
110+
111+
Type CTRL-C to stop tailing the logs.
112+
113+
## Cleanup
114+
115+
When done testing, you can delete the Lambda function with this command.
116+
117+
```bash
118+
aws lambda delete-function --function-name BackgroundTasks
119+
```
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import AWSLambdaRuntime
16+
import Foundation
17+
18+
struct BackgroundProcessingHandler: LambdaWithBackgroundProcessingHandler {
19+
struct Input: Decodable {
20+
let message: String
21+
}
22+
23+
struct Greeting: Encodable {
24+
let echoedMessage: String
25+
}
26+
27+
typealias Event = Input
28+
typealias Output = Greeting
29+
30+
func handle(
31+
_ event: Event,
32+
outputWriter: some LambdaResponseWriter<Output>,
33+
context: LambdaContext
34+
) async throws {
35+
// Return result to the Lambda control plane
36+
context.logger.debug("BackgroundProcessingHandler - message received")
37+
try await outputWriter.write(Greeting(echoedMessage: event.message))
38+
39+
// Perform some background work, e.g:
40+
context.logger.debug("BackgroundProcessingHandler - response sent. Performing background tasks.")
41+
try await Task.sleep(for: .seconds(10))
42+
43+
// Exit the function. All asynchronous work has been executed before exiting the scope of this function.
44+
// Follows structured concurrency principles.
45+
context.logger.debug("BackgroundProcessingHandler - Background tasks completed. Returning")
46+
return
47+
}
48+
}
49+
50+
let adapter = LambdaCodableAdapter(handler: BackgroundProcessingHandler())
51+
let runtime = LambdaRuntime.init(handler: adapter)
52+
try await runtime.run()

‎Examples/HelloJSON/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
response.json
2+
samconfig.toml
3+
template.yaml
4+
Makefile

‎Examples/HelloJSON/Package.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// swift-tools-version:6.0
2+
3+
import PackageDescription
4+
5+
// needed for CI to test the local version of the library
6+
import struct Foundation.URL
7+
8+
#if os(macOS)
9+
let platforms: [PackageDescription.SupportedPlatform]? = [.macOS(.v15)]
10+
#else
11+
let platforms: [PackageDescription.SupportedPlatform]? = nil
12+
#endif
13+
14+
let package = Package(
15+
name: "swift-aws-lambda-runtime-example",
16+
platforms: platforms,
17+
products: [
18+
.executable(name: "HelloJSON", targets: ["HelloJSON"])
19+
],
20+
dependencies: [
21+
// during CI, the dependency on local version of swift-aws-lambda-runtime is added dynamically below
22+
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "main")
23+
],
24+
targets: [
25+
.executableTarget(
26+
name: "HelloJSON",
27+
dependencies: [
28+
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime")
29+
]
30+
)
31+
]
32+
)
33+
34+
if let localDepsPath = Context.environment["LAMBDA_USE_LOCAL_DEPS"],
35+
localDepsPath != "",
36+
let v = try? URL(fileURLWithPath: localDepsPath).resourceValues(forKeys: [.isDirectoryKey]),
37+
v.isDirectory == true
38+
{
39+
// when we use the local runtime as deps, let's remove the dependency added above
40+
let indexToRemove = package.dependencies.firstIndex { dependency in
41+
if case .sourceControl(
42+
name: _,
43+
location: "https://github.com/swift-server/swift-aws-lambda-runtime.git",
44+
requirement: _
45+
) = dependency.kind {
46+
return true
47+
}
48+
return false
49+
}
50+
if let indexToRemove {
51+
package.dependencies.remove(at: indexToRemove)
52+
}
53+
54+
// then we add the dependency on LAMBDA_USE_LOCAL_DEPS' path (typically ../..)
55+
print("[INFO] Compiling against swift-aws-lambda-runtime located at \(localDepsPath)")
56+
package.dependencies += [
57+
.package(name: "swift-aws-lambda-runtime", path: localDepsPath)
58+
]
59+
}

‎Examples/HelloJSON/README.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Hello JSON
2+
3+
This is a simple example of an AWS Lambda function that takes a JSON structure as an input parameter and returns a JSON structure as a response.
4+
5+
The runtime takes care of decoding the input and encoding the output.
6+
7+
## Code
8+
9+
The code defines `HelloRequest` and `HelloResponse` data structures to represent the input and output payloads. These structures are typically shared with a client project, such as an iOS application.
10+
11+
The code creates a `LambdaRuntime` struct. In it's simplest form, the initializer takes a function as an argument. The function is the handler that will be invoked when an event triggers the Lambda function.
12+
13+
The handler is `(event: HelloRequest, context: LambdaContext)`. The function takes two arguments:
14+
- the event argument is a `HelloRequest`. It is the parameter passed when invoking the function.
15+
- the context argument is a `Lambda Context`. It is a description of the runtime context.
16+
17+
The function return value will be encoded to a `HelloResponse` as your Lambda function response.
18+
19+
## Build & Package
20+
21+
To build & archive the package, type the following commands.
22+
23+
```bash
24+
swift package archive --allow-network-connections docker
25+
```
26+
27+
If there is no error, there is a ZIP file ready to deploy.
28+
The ZIP file is located at `.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/HelloJSON/HelloJSON.zip`
29+
30+
## Deploy
31+
32+
Here is how to deploy using the `aws` command line.
33+
34+
```bash
35+
# Replace with your AWS Account ID
36+
AWS_ACCOUNT_ID=012345678901
37+
38+
aws lambda create-function \
39+
--function-name HelloJSON \
40+
--zip-file fileb://.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/HelloJSON/HelloJSON.zip \
41+
--runtime provided.al2 \
42+
--handler provided \
43+
--architectures arm64 \
44+
--role arn:aws:iam::${AWS_ACCOUNT_ID}:role/lambda_basic_execution
45+
```
46+
47+
The `--architectures` flag is only required when you build the binary on an Apple Silicon machine (Apple M1 or more recent). It defaults to `x64`.
48+
49+
Be sure to define the `AWS_ACCOUNT_ID` environment variable with your actual AWS account ID (for example: 012345678901).
50+
51+
## Invoke your Lambda function
52+
53+
To invoke the Lambda function, use this `aws` command line.
54+
55+
```bash
56+
aws lambda invoke \
57+
--function-name HelloJSON \
58+
--payload $(echo '{ "name" : "Seb", "age" : 50 }' | base64) \
59+
out.txt && cat out.txt && rm out.txt
60+
```
61+
62+
Note that the payload is expected to be a valid JSON string.
63+
64+
This should output the following result.
65+
66+
```
67+
{
68+
"StatusCode": 200,
69+
"ExecutedVersion": "$LATEST"
70+
}
71+
{"greetings":"Hello Seb. You look younger than your age."}
72+
```
73+
74+
## Undeploy
75+
76+
When done testing, you can delete the Lambda function with this command.
77+
78+
```bash
79+
aws lambda delete-function --function-name HelloJSON
80+
```

‎Examples/HelloJSON/Sources/main.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import AWSLambdaRuntime
16+
17+
// in this example we are receiving and responding with JSON structures
18+
19+
// the data structure to represent the input parameter
20+
struct HelloRequest: Decodable {
21+
let name: String
22+
let age: Int
23+
}
24+
25+
// the data structure to represent the output response
26+
struct HelloResponse: Encodable {
27+
let greetings: String
28+
}
29+
30+
// the Lambda runtime
31+
let runtime = LambdaRuntime {
32+
(event: HelloRequest, context: LambdaContext) in
33+
34+
HelloResponse(
35+
greetings: "Hello \(event.name). You look \(event.age > 30 ? "younger" : "older") than your age."
36+
)
37+
}
38+
39+
// start the loop
40+
try await runtime.run()

‎Examples/HelloWorld/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ To build & archive the package, type the following commands.
1818

1919
```bash
2020
swift build
21-
swift package archive --allow-network-access docker
21+
swift package archive --allow-network-connections docker
2222
```
2323

2424
If there is no error, there is a ZIP file ready to deploy.

‎Examples/README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
This directory contains example code for Lambda functions.
2+
3+
## Pre-requisites
4+
5+
- Ensure you have the Swift 6.x toolchain installed. You can [install Swift toolchains](https://www.swift.org/install/macos/) from Swift.org
6+
7+
- When developing on macOS, be sure you use macOS 15 (Sequoia) or a more recent macOS version.
8+
9+
- To build and archive your Lambda functions, you need to [install docker](https://docs.docker.com/desktop/install/mac-install/).
10+
11+
- To deploy your Lambda functions and invoke them, you must have [an AWS account](https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-creating.html) and [install and configure the `aws` command line](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html).
12+
13+
- Some examples are using [AWS SAM](https://aws.amazon.com/serverless/sam/). Install the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) before deploying these examples.
14+
15+
## Examples
16+
17+
- **[API Gateway](APIGateway/README.md)**: an HTTPS REST API with [Amazon API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html) and a Lambda function as backend (requires [AWS SAM](https://aws.amazon.com/serverless/sam/)).
18+
19+
- **[BackgroundTasks](BackgroundTasks/README.md)**: a Lambda function that continues to run background tasks after having sent the response (requires [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)).
20+
21+
- **[HelloJSON](HelloJSON/README.md)**: a Lambda function that accepts JSON as an input parameter and responds with a JSON output (requires [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)).
22+
23+
- **[HelloWorld](HelloWorld/README.md)**: a simple Lambda function (requires [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)).
24+
25+
- **[S3_AWSSDK](S3_AWSSDK/README.md)**: a Lambda function that uses the [AWS SDK for Swift](https://docs.aws.amazon.com/sdk-for-swift/latest/developer-guide/getting-started.html) 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/)).
26+
27+
- **[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/)).
28+
29+
- **[Streaming]**: 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/)).
30+
31+
## AWS Credentials and Signature
32+
33+
This section is a short tutorial on the AWS Signature protocol and the AWS credentials.
34+
35+
**What is AWS SigV4?**
36+
37+
AWS SigV4, short for "Signature Version 4," is a protocol AWS uses to authenticate and secure requests. When you, as a developer, send a request to an AWS service, AWS SigV4 makes sure the request is verified and hasn’t been tampered with. This is done through a digital signature, which is created by combining your request details with your secret AWS credentials. This signature tells AWS that the request is genuine and is coming from a user who has the right permissions.
38+
39+
**How to Obtain AWS Access Keys and Session Tokens**
40+
41+
To start making authenticated requests with AWS SigV4, you’ll need three main pieces of information:
42+
43+
1. **Access Key ID**: This is a unique identifier for your AWS account, IAM (Identity and Access Management) user, or federated user.
44+
45+
2. **Secret Access Key**: This is a secret code that only you and AWS know. It works together with your access key ID to sign requests.
46+
47+
3. **Session Token (Optional)**: If you're using temporary security credentials, AWS will also provide a session token. This is usually required if you're using temporary access (e.g., through AWS STS, which provides short-lived, temporary credentials, or for federated users).
48+
49+
To obtain these keys, you need an AWS account:
50+
51+
1. **Sign up or Log in to AWS Console**: Go to the [AWS Management Console](https://aws.amazon.com/console/), log in, or create an AWS account if you don’t have one.
52+
53+
2. **Create IAM User**: In the console, go to IAM (Identity and Access Management) and create a new user. Ensure you set permissions that match what the user will need for your application (e.g., permissions to access specific AWS services, such as AWS Lambda).
54+
55+
3. **Generate Access Key and Secret Access Key**: In the IAM user settings, find the option to generate an "Access Key" and "Secret Access Key." Save these securely! You’ll need them to authenticate your requests.
56+
57+
4. **(Optional) Generate Temporary Security Credentials**: If you’re using temporary credentials (which are more secure for short-term access), use AWS Security Token Service (STS). You can call the `GetSessionToken` or `AssumeRole` API to generate temporary credentials, including a session token.
58+
59+
With these in hand, you can use AWS SigV4 to securely sign your requests and interact with AWS services from your Swift app.

‎Examples/S3_AWSSDK/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ To build the package, type the following commands.
2525

2626
```bash
2727
swift build
28-
swift package archive --allow-network-access docker
28+
swift package archive --allow-network-connections docker
2929
```
3030

3131
If there is no error, there is a ZIP file ready to deploy.

‎Examples/S3_Soto/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ To build the package, type the following command.
2525

2626
```bash
2727
swift build
28-
swift package archive --allow-network-access docker
28+
swift package archive --allow-network-connections docker
2929
```
3030

3131
If there is no error, there is a ZIP file ready to deploy.

‎Examples/Streaming/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
xcuserdata/
5+
DerivedData/
6+
.swiftpm/configuration/registries.json
7+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8+
.netrc

‎Examples/Streaming/Package.swift

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// swift-tools-version:6.0
2+
3+
import PackageDescription
4+
5+
// needed for CI to test the local version of the library
6+
import struct Foundation.URL
7+
8+
#if os(macOS)
9+
let platforms: [PackageDescription.SupportedPlatform]? = [.macOS(.v15)]
10+
#else
11+
let platforms: [PackageDescription.SupportedPlatform]? = nil
12+
#endif
13+
14+
let package = Package(
15+
name: "swift-aws-lambda-runtime-example",
16+
platforms: platforms,
17+
products: [
18+
.executable(name: "StreamingNumbers", targets: ["StreamingNumbers"])
19+
],
20+
dependencies: [
21+
// during CI, the dependency on local version of swift-aws-lambda-runtime is added dynamically below
22+
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "main")
23+
],
24+
targets: [
25+
.executableTarget(
26+
name: "StreamingNumbers",
27+
dependencies: [
28+
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime")
29+
],
30+
path: "."
31+
)
32+
]
33+
)
34+
35+
if let localDepsPath = Context.environment["LAMBDA_USE_LOCAL_DEPS"],
36+
localDepsPath != "",
37+
let v = try? URL(fileURLWithPath: localDepsPath).resourceValues(forKeys: [.isDirectoryKey]),
38+
v.isDirectory == true
39+
{
40+
// when we use the local runtime as deps, let's remove the dependency added above
41+
let indexToRemove = package.dependencies.firstIndex { dependency in
42+
if case .sourceControl(
43+
name: _,
44+
location: "https://github.com/swift-server/swift-aws-lambda-runtime.git",
45+
requirement: _
46+
) = dependency.kind {
47+
return true
48+
}
49+
return false
50+
}
51+
if let indexToRemove {
52+
package.dependencies.remove(at: indexToRemove)
53+
}
54+
55+
// then we add the dependency on LAMBDA_USE_LOCAL_DEPS' path (typically ../..)
56+
print("[INFO] Compiling against swift-aws-lambda-runtime located at \(localDepsPath)")
57+
package.dependencies += [
58+
.package(name: "swift-aws-lambda-runtime", path: localDepsPath)
59+
]
60+
}

‎Examples/Streaming/README.md

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
# Streaming Lambda function
2+
3+
You can configure your Lambda function to stream response payloads back to clients. 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 20 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](https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html), the AWS SDK, or using the Lambda [InvokeWithResponseStream](https://docs.aws.amazon.com/lambda/latest/dg/API_InvokeWithResponseStream.html) API. In this example, we create an authenticated Lambda function URL.
8+
9+
10+
## Code
11+
12+
The sample code creates a `SendNumbersWithPause` struct that conforms to the `StreamingLambdaHandler` protocol provided by the Swift AWS Lambda Runtime.
13+
14+
The `handle(...)` method of this protocol receives incoming events as a Swift NIO `ByteBuffer` and returns the output as a `ByteBuffer`.
15+
16+
The response is streamed through the `LambdaResponseStreamWriter`, which is passed as an argument in the `handle` function. The code calls the `write(_:)` function of the `LambdaResponseStreamWriter` with partial data repeatedly written before
17+
finally closing the response stream by calling `finish()`. Developers can also choose to return the entire output and not
18+
stream the response by calling `writeAndFinish(_:)`.
19+
20+
An error is thrown if `finish()` is called multiple times or if it is called after having called `writeAndFinish(_:)`.
21+
22+
The `handle(...)` method is marked as `mutating` to allow handlers to be implemented with a `struct`.
23+
24+
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.
25+
26+
## Build & Package
27+
28+
To build & archive the package, type the following commands.
29+
30+
```bash
31+
swift package archive --allow-network-connections docker
32+
```
33+
34+
If there is no error, there is a ZIP file ready to deploy.
35+
The ZIP file is located at `.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/StreamingNumbers/StreamingNumbers.zip`
36+
37+
## Deploy with the AWS CLI
38+
39+
Here is how to deploy using the `aws` command line.
40+
41+
### Step 1: Create the function
42+
43+
```bash
44+
# Replace with your AWS Account ID
45+
AWS_ACCOUNT_ID=012345678901
46+
aws lambda create-function \
47+
--function-name StreamingNumbers \
48+
--zip-file fileb://.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/StreamingNumbers/StreamingNumbers.zip \
49+
--runtime provided.al2 \
50+
--handler provided \
51+
--architectures arm64 \
52+
--role arn:aws:iam::${AWS_ACCOUNT_ID}:role/lambda_basic_execution \
53+
--timeout 15
54+
```
55+
56+
> [!IMPORTANT]
57+
> 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. Here, the sample function stream responses during 10 seconds and we set the timeout for 15 seconds.
58+
59+
The `--architectures` flag is only required when you build the binary on an Apple Silicon machine (Apple M1 or more recent). It defaults to `x64`.
60+
61+
Be sure to set `AWS_ACCOUNT_ID` with your actual AWS account ID (for example: 012345678901).
62+
63+
### Step2: Give permission to invoke that function through an URL
64+
65+
Anyone with a valid signature from your AWS account will have permission to invoke the function through its URL.
66+
67+
```bash
68+
aws lambda add-permission \
69+
--function-name StreamingNumbers \
70+
--action lambda:InvokeFunctionUrl \
71+
--principal ${AWS_ACCOUNT_ID} \
72+
--function-url-auth-type AWS_IAM \
73+
--statement-id allowURL
74+
```
75+
76+
### Step3: Create the URL
77+
78+
This creates [a URL with IAM authentication](https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html). Only calls with a valid signature will be authorized.
79+
80+
```bash
81+
aws lambda create-function-url-config \
82+
--function-name StreamingNumbers \
83+
--auth-type AWS_IAM \
84+
--invoke-mode RESPONSE_STREAM
85+
```
86+
This calls return various information, including the URL to invoke your function.
87+
88+
```json
89+
{
90+
"FunctionUrl": "https://ul3nf4dogmgyr7ffl5r5rs22640fwocc.lambda-url.us-east-1.on.aws/",
91+
"FunctionArn": "arn:aws:lambda:us-east-1:012345678901:function:StreamingNumbers",
92+
"AuthType": "AWS_IAM",
93+
"CreationTime": "2024-10-22T07:57:23.112599Z",
94+
"InvokeMode": "RESPONSE_STREAM"
95+
}
96+
```
97+
98+
### Invoke your Lambda function
99+
100+
To invoke the Lambda function, use `curl` with the AWS Sigv4 option to generate the signature.
101+
102+
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.
103+
104+
When you have the `aws` command line installed and configured, you will find the credentials in the `~/.aws/credentials` file.
105+
106+
```bash
107+
URL=https://ul3nf4dogmgyr7ffl5r5rs22640fwocc.lambda-url.us-east-1.on.aws/
108+
REGION=us-east-1
109+
ACCESS_KEY=AK...
110+
SECRET_KEY=...
111+
AWS_SESSION_TOKEN=...
112+
113+
curl "$URL" \
114+
--user "${ACCESS_KEY}":"${SECRET_KEY}" \
115+
--aws-sigv4 "aws:amz:${REGION}:lambda" \
116+
-H "x-amz-security-token: ${AWS_SESSION_TOKEN}" \
117+
--no-buffer
118+
```
119+
120+
Note that there is no payload required for this example.
121+
122+
This should output the following result, with a one-second delay between each numbers.
123+
124+
```
125+
1
126+
2
127+
3
128+
4
129+
5
130+
6
131+
7
132+
8
133+
9
134+
10
135+
```
136+
137+
### Undeploy
138+
139+
When done testing, you can delete the Lambda function with this command.
140+
141+
```bash
142+
aws lambda delete-function --function-name StreamingNumbers
143+
```
144+
145+
## Deploy with AWS SAM
146+
147+
Alternatively, you can use [AWS SAM](https://aws.amazon.com/serverless/sam/) to deploy the Lambda function.
148+
149+
**Prerequisites** : Install the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)
150+
151+
### SAM Template
152+
153+
The template file is provided as part of the example in the `template.yaml` file. It defines a Lambda function based on the binary ZIP file. It creates the function url with IAM authentication and sets the function timeout to 15 seconds.
154+
155+
```yaml
156+
AWSTemplateFormatVersion: '2010-09-09'
157+
Transform: AWS::Serverless-2016-10-31
158+
Description: SAM Template for StreamingLambda Example
159+
160+
Resources:
161+
# Lambda function
162+
StreamingNumbers:
163+
Type: AWS::Serverless::Function
164+
Properties:
165+
CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/StreamingNumbers/StreamingNumbers.zip
166+
Timeout: 15
167+
Handler: swift.bootstrap # ignored by the Swift runtime
168+
Runtime: provided.al2
169+
MemorySize: 128
170+
Architectures:
171+
- arm64
172+
FunctionUrlConfig:
173+
AuthType: AWS_IAM
174+
InvokeMode: RESPONSE_STREAM
175+
176+
Outputs:
177+
# print Lambda function URL
178+
LambdaURL:
179+
Description: Lambda URL
180+
Value: !GetAtt StreamingNumbersUrl.FunctionUrl
181+
```
182+
183+
### Deploy with SAM
184+
185+
```bash
186+
sam deploy \
187+
--resolve-s3 \
188+
--template-file template.yaml \
189+
--stack-name StreamingNumbers \
190+
--capabilities CAPABILITY_IAM
191+
```
192+
193+
The URL of the function is provided as part of the output.
194+
195+
```
196+
CloudFormation outputs from deployed stack
197+
-----------------------------------------------------------------------------------------------------------------------------
198+
Outputs
199+
-----------------------------------------------------------------------------------------------------------------------------
200+
Key LambdaURL
201+
Description Lambda URL
202+
Value https://gaudpin2zjqizfujfnqxstnv6u0czrfu.lambda-url.us-east-1.on.aws/
203+
-----------------------------------------------------------------------------------------------------------------------------
204+
```
205+
206+
Once the function is deployed, you can invoke it with `curl`, similarly to what you did when deploying with the AWS CLI.
207+
208+
```bash
209+
curl "$URL" \
210+
--user "$ACCESS_KEY":"$SECRET_KEY" \
211+
--aws-sigv4 "aws:amz:${REGION}:lambda" \
212+
-H "x-amz-security-token: $AWS_SESSION_TOKEN" \
213+
--no-buffer
214+
```
215+
216+
### Undeploy with SAM
217+
218+
When done testing, you can delete the infrastructure with this command.
219+
220+
```bash
221+
sam delete
222+
```

‎Examples/Streaming/Sources/main.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import AWSLambdaRuntime
16+
import NIOCore
17+
18+
struct SendNumbersWithPause: StreamingLambdaHandler {
19+
func handle(
20+
_ event: ByteBuffer,
21+
responseWriter: some LambdaResponseStreamWriter,
22+
context: LambdaContext
23+
) async throws {
24+
for i in 1...10 {
25+
// Send partial data
26+
try await responseWriter.write(ByteBuffer(string: "\(i)\n"))
27+
// Perform some long asynchronous work
28+
try await Task.sleep(for: .milliseconds(1000))
29+
}
30+
// All data has been sent. Close off the response stream.
31+
try await responseWriter.finish()
32+
}
33+
}
34+
35+
let runtime = LambdaRuntime.init(handler: SendNumbersWithPause())
36+
try await runtime.run()

‎Examples/Streaming/template.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
Transform: AWS::Serverless-2016-10-31
3+
Description: SAM Template for Streaming Example
4+
5+
Resources:
6+
# Lambda function
7+
StreamingNumbers:
8+
Type: AWS::Serverless::Function
9+
Properties:
10+
CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/StreamingNumbers/StreamingNumbers.zip
11+
Timeout: 15
12+
Handler: swift.bootstrap # ignored by the Swift runtime
13+
Runtime: provided.al2
14+
MemorySize: 128
15+
Architectures:
16+
- arm64
17+
FunctionUrlConfig:
18+
AuthType: AWS_IAM
19+
InvokeMode: RESPONSE_STREAM
20+
21+
Outputs:
22+
# print Lambda function URL
23+
LambdaURL:
24+
Description: Lambda URL
25+
Value: !GetAtt StreamingNumbersUrl.FunctionUrl

‎Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ let package = Package(
1717
.library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]),
1818
],
1919
dependencies: [
20-
.package(url: "https://github.com/apple/swift-nio.git", from: "2.72.0"),
20+
.package(url: "https://github.com/apple/swift-nio.git", from: "2.76.0"),
2121
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.4"),
2222
],
2323
targets: [

‎Plugins/AWSLambdaPackager/Plugin.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ struct AWSLambdaPackager: CommandPlugin {
7474
"\(archives.count > 0 ? archives.count.description : "no") archive\(archives.count != 1 ? "s" : "") created"
7575
)
7676
for (product, archivePath) in archives {
77-
print(" * \(product.name) at \(archivePath)")
77+
print(" * \(product.name) at \(archivePath.path())")
7878
}
7979
}
8080

@@ -101,7 +101,7 @@ struct AWSLambdaPackager: CommandPlugin {
101101
try Utils.execute(
102102
executable: dockerToolPath,
103103
arguments: ["pull", baseImage],
104-
logLevel: .output
104+
logLevel: verboseLogging ? .debug : .output
105105
)
106106
}
107107

@@ -287,7 +287,8 @@ struct AWSLambdaPackager: CommandPlugin {
287287
288288
REQUIREMENTS: To use this plugin, you must have docker installed and started.
289289
290-
USAGE: swift package --allow-network-access docker archive [--help] [--verbose]
290+
USAGE: swift package --allow-network-connections docker archive
291+
[--help] [--verbose]
291292
[--output-directory <path>]
292293
[--products <list of products>]
293294
[--configuration debug | release]

‎Plugins/AWSLambdaPackager/PluginUtils.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ struct Utils {
2727
logLevel: ProcessLogLevel
2828
) throws -> String {
2929
if logLevel >= .debug {
30-
print("\(executable.absoluteString) \(arguments.joined(separator: " "))")
30+
print("\(executable.path()) \(arguments.joined(separator: " "))")
31+
if let customWorkingDirectory {
32+
print("Working directory: \(customWorkingDirectory.path())")
33+
}
3134
}
3235

3336
let fd = dup(1)
@@ -83,10 +86,10 @@ struct Utils {
8386
let process = Process()
8487
process.standardOutput = pipe
8588
process.standardError = pipe
86-
process.executableURL = URL(fileURLWithPath: executable.description)
89+
process.executableURL = executable
8790
process.arguments = arguments
88-
if let workingDirectory = customWorkingDirectory {
89-
process.currentDirectoryURL = URL(fileURLWithPath: workingDirectory.path())
91+
if let customWorkingDirectory {
92+
process.currentDirectoryURL = URL(fileURLWithPath: customWorkingDirectory.path())
9093
}
9194
process.terminationHandler = { _ in
9295
outputQueue.async {

‎Sources/AWSLambdaRuntime/Docs.docc/index.md

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Swift AWS Lambda Runtime was designed to make building Lambda functions in Swift
1616

1717
## Getting started
1818

19-
If you have never used AWS Lambda or Docker before, check out this [getting started guide](https://fabianfett.de/getting-started-with-swift-aws-lambda-runtime) which helps you with every step from zero to a running Lambda.
19+
If you have never used AWS Lambda or Docker before, check out this [getting started guide](https://swiftpackageindex.com/swift-server/swift-aws-lambda-runtime/1.0.0-alpha.3/tutorials/table-of-content) which helps you with every step from zero to a running Lambda.
2020

2121
First, create a SwiftPM project and pull Swift AWS Lambda Runtime as dependency into your project
2222

@@ -120,32 +120,6 @@ First, add a dependency on the event packages:
120120

121121
Modeling Lambda functions as Closures is both simple and safe. Swift AWS Lambda Runtime will ensure that the user-provided code is offloaded from the network processing thread such that even if the code becomes slow to respond or gets stuck, the underlying process can continue to function. This safety comes at a small performance penalty from context switching between threads. In many cases, the simplicity and safety of using the Closure based API is often preferred over the complexity of the performance-oriented API.
122122

123-
### Using EventLoopLambdaHandler
124-
125-
Performance sensitive Lambda functions may choose to use a more complex API which allows user code to run on the same thread as the networking handlers. Swift AWS Lambda Runtime uses [SwiftNIO](https://github.com/apple/swift-nio) as its underlying networking engine which means the APIs are based on [SwiftNIO](https://github.com/apple/swift-nio) concurrency primitives like the `EventLoop` and `EventLoopFuture`. For example:
126-
127-
```swift
128-
// Import the modules
129-
import AWSLambdaRuntime
130-
import AWSLambdaEvents
131-
import NIO
132-
133-
// Our Lambda handler, conforms to EventLoopLambdaHandler
134-
struct Handler: EventLoopLambdaHandler {
135-
typealias In = SNS.Message // Request type
136-
typealias Out = Void // Response type
137-
138-
// In this example we are receiving an SNS Message, with no response (Void).
139-
func handle(context: Lambda.Context, event: In) -> EventLoopFuture<Out> {
140-
...
141-
context.eventLoop.makeSucceededFuture(Void())
142-
}
143-
}
144-
145-
Lambda.run(Handler())
146-
```
147-
148-
Beyond the small cognitive complexity of using the `EventLoopFuture` based APIs, note these APIs should be used with extra care. An [`EventLoopLambdaHandler`][ellh] will execute the user code on the same `EventLoop` (thread) as the library, making processing faster but requiring the user code to never call blocking APIs as it might prevent the underlying process from functioning.
149123

150124
## Deploying to AWS Lambda
151125

‎Sources/AWSLambdaRuntime/Lambda+Codable.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@ public struct LambdaJSONOutputEncoder<Output: Encodable>: LambdaOutputEncoder {
6060
extension LambdaCodableAdapter {
6161
/// Initializes an instance given an encoder, decoder, and a handler with a non-`Void` output.
6262
/// - Parameters:
63-
/// - encoder: The encoder object that will be used to encode the generic `Output` obtained from the `handler`'s `outputWriter` into a `ByteBuffer`.
64-
/// - decoder: The decoder object that will be used to decode the received `ByteBuffer` event into the generic `Event` type served to the `handler`.
63+
/// - encoder: The encoder object that will be used to encode the generic `Output` obtained from the `handler`'s `outputWriter` into a `ByteBuffer`. By default, a JSONEncoder is used.
64+
/// - decoder: The decoder object that will be used to decode the received `ByteBuffer` event into the generic `Event` type served to the `handler`. By default, a JSONDecoder is used.
6565
/// - handler: The handler object.
6666
public init(
67-
encoder: JSONEncoder,
68-
decoder: JSONDecoder,
67+
encoder: JSONEncoder = JSONEncoder(),
68+
decoder: JSONDecoder = JSONDecoder(),
6969
handler: Handler
7070
)
7171
where
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
swift package --allow-network-access docker plugin archive
1+
swift package archive --allow-network-connections docker
22

‎Sources/AWSLambdaRuntimeCore/Documentation.docc/Resources/code/04-01-03-plugin-archive.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
swift package --allow-network-access docker archive
1+
swift package archive --allow-network-connections docker
22

33
-------------------------------------------------------------------------
44
building "squarenumberlambda" in docker

‎Sources/AWSLambdaRuntimeCore/Documentation.docc/Resources/code/04-01-04-plugin-archive.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
swift package --allow-network-access docker archive
1+
swift package archive --allow-network-connections docker
22

33
-------------------------------------------------------------------------
44
building "squarenumberlambda" in docker

‎Sources/AWSLambdaRuntimeCore/Documentation.docc/quick-setup.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ AWS Lambda runtime runs on Amazon Linux. You must compile your code for Amazon L
106106
> Be sure to have [Docker](https://docs.docker.com/desktop/install/mac-install/) installed for this step.
107107
108108
```sh
109-
swift package --allow-network-access docker archive
109+
swift package --allow-network-connections docker archive
110110

111111
-------------------------------------------------------------------------
112112
building "squarenumberlambda" in docker

‎Sources/AWSLambdaRuntimeCore/Lambda+LocalServer.swift

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import NIOCore
2020
import NIOHTTP1
2121
import NIOPosix
2222

23-
// This functionality is designed for local testing hence beind a #if DEBUG flag.
23+
// This functionality is designed for local testing hence being a #if DEBUG flag.
2424
// For example:
2525
//
2626
// try Lambda.withLocalServer {
@@ -32,16 +32,18 @@ extension Lambda {
3232
/// Execute code in the context of a mock Lambda server.
3333
///
3434
/// - parameters:
35-
/// - invocationEndpoint: The endpoint to post events to.
35+
/// - invocationEndpoint: The endpoint to post events to.
3636
/// - body: Code to run within the context of the mock server. Typically this would be a Lambda.run function call.
3737
///
3838
/// - note: This API is designed strictly for local testing and is behind a DEBUG flag
39-
static func withLocalServer<Value>(invocationEndpoint: String? = nil, _ body: @escaping () -> Value) throws -> Value
40-
{
39+
static func withLocalServer<Value>(
40+
invocationEndpoint: String? = nil,
41+
_ body: @escaping () async throws -> Value
42+
) async throws -> Value {
4143
let server = LocalLambda.Server(invocationEndpoint: invocationEndpoint)
42-
try server.start().wait()
44+
try await server.start().get()
4345
defer { try! server.stop() }
44-
return body()
46+
return try await body()
4547
}
4648
}
4749

@@ -61,7 +63,7 @@ private enum LocalLambda {
6163
self.logger = logger
6264
self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
6365
self.host = "127.0.0.1"
64-
self.port = 0
66+
self.port = 7000
6567
self.invocationEndpoint = invocationEndpoint ?? "/invoke"
6668
}
6769

@@ -129,6 +131,10 @@ private enum LocalLambda {
129131
}
130132

131133
func processRequest(context: ChannelHandlerContext, request: (head: HTTPRequestHead, body: ByteBuffer?)) {
134+
135+
let eventLoop = context.eventLoop
136+
let loopBoundContext = NIOLoopBound(context, eventLoop: eventLoop)
137+
132138
switch (request.head.method, request.head.uri) {
133139
// this endpoint is called by the client invoking the lambda
134140
case (.POST, let url) where url.hasSuffix(self.invocationEndpoint):
@@ -138,6 +144,7 @@ private enum LocalLambda {
138144
let requestID = "\(DispatchTime.now().uptimeNanoseconds)" // FIXME:
139145
let promise = context.eventLoop.makePromise(of: Response.self)
140146
promise.futureResult.whenComplete { result in
147+
let context = loopBoundContext.value
141148
switch result {
142149
case .failure(let error):
143150
self.logger.error("invocation error: \(error)")
@@ -174,6 +181,7 @@ private enum LocalLambda {
174181
// create a promise that we can fullfill when we get a new task
175182
let promise = context.eventLoop.makePromise(of: Invocation.self)
176183
promise.futureResult.whenComplete { result in
184+
let context = loopBoundContext.value
177185
switch result {
178186
case .failure(let error):
179187
self.logger.error("invocation error: \(error)")
@@ -185,7 +193,7 @@ private enum LocalLambda {
185193
}
186194
Self.invocationState = .waitingForInvocation(promise)
187195
case .some(let invocation):
188-
// if there is a task pending, we can immediatly respond with it.
196+
// if there is a task pending, we can immediately respond with it.
189197
Self.invocationState = .waitingForLambdaResponse(invocation)
190198
self.writeResponse(context: context, response: invocation.makeResponse())
191199
}

‎Sources/AWSLambdaRuntimeCore/LambdaRuntime.swift

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,16 @@ public final class LambdaRuntime<Handler>: @unchecked Sendable where Handler: St
3333
) {
3434
self.handlerMutex = NIOLockedValueBox(handler)
3535
self.eventLoop = eventLoop
36+
37+
// by setting the log level here, we understand it can not be changed dynamically at runtime
38+
// developers have to wait for AWS Lambda to dispose and recreate a runtime environment to pickup a change
39+
// this approach is less flexible but more performant than reading the value of the environment variable at each invocation
40+
var log = logger
41+
log.logLevel = Lambda.env("LOG_LEVEL").flatMap(Logger.Level.init) ?? .info
3642
self.logger = logger
3743
}
3844

3945
public func run() async throws {
40-
guard let runtimeEndpoint = Lambda.env("AWS_LAMBDA_RUNTIME_API") else {
41-
throw LambdaRuntimeError(code: .missingLambdaRuntimeAPIEnvironmentVariable)
42-
}
43-
44-
let ipAndPort = runtimeEndpoint.split(separator: ":", maxSplits: 1)
45-
let ip = String(ipAndPort[0])
46-
guard let port = Int(ipAndPort[1]) else { throw LambdaRuntimeError(code: .invalidPort) }
47-
4846
let handler = self.handlerMutex.withLockedValue { handler in
4947
let result = handler
5048
handler = nil
@@ -55,16 +53,51 @@ public final class LambdaRuntime<Handler>: @unchecked Sendable where Handler: St
5553
throw LambdaRuntimeError(code: .runtimeCanOnlyBeStartedOnce)
5654
}
5755

58-
try await LambdaRuntimeClient.withRuntimeClient(
59-
configuration: .init(ip: ip, port: port),
60-
eventLoop: self.eventLoop,
61-
logger: self.logger
62-
) { runtimeClient in
63-
try await Lambda.runLoop(
64-
runtimeClient: runtimeClient,
65-
handler: handler,
56+
// are we running inside an AWS Lambda runtime environment ?
57+
// AWS_LAMBDA_RUNTIME_API is set when running on Lambda
58+
// https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html
59+
if let runtimeEndpoint = Lambda.env("AWS_LAMBDA_RUNTIME_API") {
60+
61+
let ipAndPort = runtimeEndpoint.split(separator: ":", maxSplits: 1)
62+
let ip = String(ipAndPort[0])
63+
guard let port = Int(ipAndPort[1]) else { throw LambdaRuntimeError(code: .invalidPort) }
64+
65+
try await LambdaRuntimeClient.withRuntimeClient(
66+
configuration: .init(ip: ip, port: port),
67+
eventLoop: self.eventLoop,
6668
logger: self.logger
67-
)
69+
) { runtimeClient in
70+
try await Lambda.runLoop(
71+
runtimeClient: runtimeClient,
72+
handler: handler,
73+
logger: self.logger
74+
)
75+
}
76+
77+
} else {
78+
79+
#if DEBUG
80+
// we're not running on Lambda and we're compiled in DEBUG mode,
81+
// let's start a local server for testing
82+
try await Lambda.withLocalServer(invocationEndpoint: Lambda.env("LOCAL_LAMBDA_SERVER_INVOCATION_ENDPOINT"))
83+
{
84+
85+
try await LambdaRuntimeClient.withRuntimeClient(
86+
configuration: .init(ip: "127.0.0.1", port: 7000),
87+
eventLoop: self.eventLoop,
88+
logger: self.logger
89+
) { runtimeClient in
90+
try await Lambda.runLoop(
91+
runtimeClient: runtimeClient,
92+
handler: handler,
93+
logger: self.logger
94+
)
95+
}
96+
}
97+
#else
98+
// in release mode, we can't start a local server because the local server code is not compiled.
99+
throw LambdaRuntimeError(code: .missingLambdaRuntimeAPIEnvironmentVariable)
100+
#endif
68101
}
69102
}
70103
}

‎readme.md

Lines changed: 223 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@
44
> [!WARNING]
55
> The Swift AWS Runtime v2 is work in progress. We will add more documentation and code examples over time.
66
7+
## The Swift AWS Lambda Runtime
8+
9+
Many modern systems have client components like iOS, macOS or watchOS applications as well as server components that those clients interact with. Serverless functions are often the easiest and most efficient way for client application developers to extend their applications into the cloud.
10+
11+
Serverless functions are increasingly becoming a popular choice for running event-driven or otherwise ad-hoc compute tasks in the cloud. They power mission critical microservices and data intensive workloads. In many cases, serverless functions allow developers to more easily scale and control compute costs given their on-demand nature.
12+
13+
When using serverless functions, attention must be given to resource utilization as it directly impacts the costs of the system. This is where Swift shines! With its low memory footprint, deterministic performance, and quick start time, Swift is a fantastic match for the serverless functions architecture.
14+
15+
Combine this with Swift's developer friendliness, expressiveness, and emphasis on safety, and we have a solution that is great for developers at all skill levels, scalable, and cost effective.
16+
17+
Swift AWS Lambda Runtime was designed to make building Lambda functions in Swift simple and safe. The library is an implementation of the [AWS Lambda Runtime API](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html) and uses an embedded asynchronous HTTP Client based on [SwiftNIO](http://github.com/apple/swift-nio) that is fine-tuned for performance in the AWS Runtime context. The library provides a multi-tier API that allows building a range of Lambda functions: From quick and simple closures to complex, performance-sensitive event handlers.
18+
719
## Pre-requisites
820

921
- Ensure you have the Swift 6.x toolchain installed. You can [install Swift toolchains](https://www.swift.org/install/macos/) from Swift.org
@@ -16,7 +28,11 @@
1628

1729
- Some examples are using [AWS SAM](https://aws.amazon.com/serverless/sam/). Install the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) before deploying these examples.
1830

19-
## TL;DR
31+
## Getting started
32+
33+
To get started, read [the Swift AWS Lambda runtime v1 tutorial](https://swiftpackageindex.com/swift-server/swift-aws-lambda-runtime/1.0.0-alpha.3/tutorials/table-of-content). It provides developers with detailed step-by-step instructions to develop, build, and deploy a Lambda function.
34+
35+
Or, if you're impatient to start with runtime v2, try these six steps:
2036

2137
1. Create a new Swift executable project
2238

@@ -83,7 +99,7 @@ try await runtime.run()
8399

84100
```bash
85101
swift build
86-
swift package archive --allow-network-access docker
102+
swift package archive --allow-network-connections docker
87103
```
88104

89105
If there is no error, there is a ZIP file ready to deploy.
@@ -128,38 +144,227 @@ This should print
128144
"dlroW olleH"
129145
```
130146
131-
## Swift AWS Lambda Runtime
132-
133-
Many modern systems have client components like iOS, macOS or watchOS applications as well as server components that those clients interact with. Serverless functions are often the easiest and most efficient way for client application developers to extend their applications into the cloud.
147+
## Developing your Swift Lambda functions
134148
135-
Serverless functions are increasingly becoming a popular choice for running event-driven or otherwise ad-hoc compute tasks in the cloud. They power mission critical microservices and data intensive workloads. In many cases, serverless functions allow developers to more easily scale and control compute costs given their on-demand nature.
149+
### Receive and respond with JSON objects
136150
137-
When using serverless functions, attention must be given to resource utilization as it directly impacts the costs of the system. This is where Swift shines! With its low memory footprint, deterministic performance, and quick start time, Swift is a fantastic match for the serverless functions architecture.
151+
Typically, your Lambda functions will receive an input parameter expressed as JSON and will respond with some other JSON. The Swift AWS Lambda runtime automatically takes care of encoding and decoding JSON objects when your Lambda function handler accepts `Decodable` and returns `Encodable` conforming types.
138152
139-
Combine this with Swift's developer friendliness, expressiveness, and emphasis on safety, and we have a solution that is great for developers at all skill levels, scalable, and cost effective.
153+
Here is an example of a minimal function that accepts a JSON object as input and responds with another JSON object.
140154
141-
Swift AWS Lambda Runtime was designed to make building Lambda functions in Swift simple and safe. The library is an implementation of the [AWS Lambda Runtime API](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html) and uses an embedded asynchronous HTTP Client based on [SwiftNIO](http://github.com/apple/swift-nio) that is fine-tuned for performance in the AWS Runtime context. The library provides a multi-tier API that allows building a range of Lambda functions: From quick and simple closures to complex, performance-sensitive event handlers.
155+
```swift
156+
import AWSLambdaRuntime
142157
143-
## Design Principles
158+
// the data structure to represent the input parameter
159+
struct HelloRequest: Decodable {
160+
let name: String
161+
let age: Int
162+
}
144163
145-
tbd + reference to the `v2-api.md` design doc.
164+
// the data structure to represent the output response
165+
struct HelloResponse: Encodable {
166+
let greetings: String
167+
}
146168
147-
## Tutorial
169+
// the Lambda runtime
170+
let runtime = LambdaRuntime {
171+
(event: HelloRequest, context: LambdaContext) in
148172
149-
link to [updated docc tutorial](https://swiftpackageindex.com/swift-server/swift-aws-lambda-runtime/1.0.0-alpha.3/tutorials/table-of-content)
173+
HelloResponse(
174+
greetings: "Hello \(event.name). You look \(event.age > 30 ? "younger" : "older") than your age."
175+
)
176+
}
150177
151-
## AWSLambdaRuntime API
178+
// start the loop
179+
try await runtime.run()
180+
```
152181

153-
tbd
182+
You can learn how to deploy and invoke this function in [the Hello JSON example README file](Examples/HelloJSON/README.md).
154183

155184
### Lambda Streaming Response
156185

157-
tbd + link to docc
186+
You can configure your Lambda function to stream response payloads back to clients. 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 20 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.
187+
188+
Streaming responses incurs a cost. For more information, see [AWS Lambda Pricing](https://aws.amazon.com/lambda/pricing/).
189+
190+
You can stream responses through [Lambda function URLs](https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html), the AWS SDK, or using the Lambda [InvokeWithResponseStream](https://docs.aws.amazon.com/lambda/latest/dg/API_InvokeWithResponseStream.html) API. In this example, we create an authenticated Lambda function URL.
191+
192+
Here is an example of a minimal function that streams 10 numbers with an interval of one second for each number.
193+
194+
```swift
195+
import AWSLambdaRuntime
196+
import NIOCore
197+
198+
struct SendNumbersWithPause: StreamingLambdaHandler {
199+
func handle(
200+
_ event: ByteBuffer,
201+
responseWriter: some LambdaResponseStreamWriter,
202+
context: LambdaContext
203+
) async throws {
204+
for i in 1...10 {
205+
// Send partial data
206+
try await responseWriter.write(ByteBuffer(string: "\(i)\n"))
207+
// Perform some long asynchronous work
208+
try await Task.sleep(for: .milliseconds(1000))
209+
}
210+
// All data has been sent. Close off the response stream.
211+
try await responseWriter.finish()
212+
}
213+
}
214+
215+
let runtime = LambdaRuntime.init(handler: SendNumbersWithPause())
216+
try await runtime.run()
217+
```
218+
219+
You can learn how to deploy and invoke this function in [the streaming example README file](Examples/Streaming/README.md).
220+
221+
### Integration with AWS Services
222+
223+
Most Lambda functions are triggered by events originating in other AWS services such as `Amazon SNS`, `Amazon SQS` or `AWS APIGateway`.
224+
225+
The [Swift AWS Lambda Events](http://github.com/swift-server/swift-aws-lambda-events) package includes an `AWSLambdaEvents` module that provides implementations for most common AWS event types further simplifying writing Lambda functions.
226+
227+
Here is an example Lambda function invoked when the AWS APIGateway receives an HTTP request.
228+
229+
```swift
230+
import AWSLambdaEvents
231+
import AWSLambdaRuntime
232+
233+
let runtime = LambdaRuntime {
234+
(event: APIGatewayV2Request, context: LambdaContext) -> APIGatewayV2Response in
235+
236+
APIGatewayV2Response(statusCode: .ok, body: "Hello World!")
237+
}
238+
239+
try await runtime.run()
240+
```
241+
242+
You can learn how to deploy and invoke this function in [the API Gateway example README file](Examples/APIGateway/README.md).
158243

159244
### Integration with Swift Service LifeCycle
160245

161246
tbd + link to docc
162247

163-
### Background Tasks
248+
### Use Lambda Background Tasks
164249

165-
tbd + link to docc
250+
Background tasks allow code to execute asynchronously after the main response has been returned, enabling additional processing without affecting response latency. This approach is ideal for scenarios like logging, data updates, or notifications that can be deferred. The code leverages Lambda's "Response Streaming" feature, which is effective for balancing real-time user responsiveness with the ability to perform extended tasks post-response. For more information about Lambda background tasks, see [this AWS blog post](https://aws.amazon.com/blogs/compute/running-code-after-returning-a-response-from-an-aws-lambda-function/).
251+
252+
253+
Here is an example of a minimal function that waits 10 seconds after it returned a response but before the handler returns.
254+
```swift
255+
import AWSLambdaRuntime
256+
import Foundation
257+
258+
struct BackgroundProcessingHandler: LambdaWithBackgroundProcessingHandler {
259+
struct Input: Decodable {
260+
let message: String
261+
}
262+
263+
struct Greeting: Encodable {
264+
let echoedMessage: String
265+
}
266+
267+
typealias Event = Input
268+
typealias Output = Greeting
269+
270+
func handle(
271+
_ event: Event,
272+
outputWriter: some LambdaResponseWriter<Output>,
273+
context: LambdaContext
274+
) async throws {
275+
// Return result to the Lambda control plane
276+
context.logger.debug("BackgroundProcessingHandler - message received")
277+
try await outputWriter.write(Greeting(echoedMessage: event.message))
278+
279+
// Perform some background work, e.g:
280+
context.logger.debug("BackgroundProcessingHandler - response sent. Performing background tasks.")
281+
try await Task.sleep(for: .seconds(10))
282+
283+
// Exit the function. All asynchronous work has been executed before exiting the scope of this function.
284+
// Follows structured concurrency principles.
285+
context.logger.debug("BackgroundProcessingHandler - Background tasks completed. Returning")
286+
return
287+
}
288+
}
289+
290+
let adapter = LambdaCodableAdapter(handler: BackgroundProcessingHandler())
291+
let runtime = LambdaRuntime.init(handler: adapter)
292+
try await runtime.run()
293+
```
294+
295+
You can learn how to deploy and invoke this function in [the background tasks example README file](Examples/BackgroundTasks/README.md).
296+
297+
## Testing Locally
298+
299+
Before deploying your code to AWS Lambda, you can test it locally by running the executable target on your local machine. It will look like this on CLI:
300+
301+
```sh
302+
swift run
303+
```
304+
305+
When not running inside a Lambda execution environment, it starts a local HTTP server listening on port 7000. You can invoke your local Lambda function by sending an HTTP POST request to `http://127.0.0.1:7000/invoke`.
306+
307+
The request must include the JSON payload expected as an `event` by your function. You can create a text file with the JSON payload documented by AWS or captured from a trace. In this example, we used [the APIGatewayv2 JSON payload from the documentation](https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway.html#apigateway-example-event), saved as `events/create-session.json` text file.
308+
309+
Then we use curl to invoke the local endpoint with the test JSON payload.
310+
311+
```sh
312+
curl -v --header "Content-Type:\ application/json" --data @events/create-session.json http://127.0.0.1:7000/invoke
313+
* Trying 127.0.0.1:7000...
314+
* Connected to 127.0.0.1 (127.0.0.1) port 7000
315+
> POST /invoke HTTP/1.1
316+
> Host: 127.0.0.1:7000
317+
> User-Agent: curl/8.4.0
318+
> Accept: */*
319+
> Content-Type:\ application/json
320+
> Content-Length: 1160
321+
>
322+
< HTTP/1.1 200 OK
323+
< content-length: 247
324+
<
325+
* Connection #0 to host 127.0.0.1 left intact
326+
{"statusCode":200,"isBase64Encoded":false,"body":"...","headers":{"Access-Control-Allow-Origin":"*","Content-Type":"application\/json; charset=utf-8","Access-Control-Allow-Headers":"*"}}
327+
```
328+
### Modifying the local endpoint
329+
330+
By default, when using the local Lambda server, it listens on the `/invoke` endpoint.
331+
332+
Some testing tools, such as the [AWS Lambda runtime interface emulator](https://docs.aws.amazon.com/lambda/latest/dg/images-test.html), require a different endpoint. In that case, you can use the `LOCAL_LAMBDA_SERVER_INVOCATION_ENDPOINT` environment variable to force the runtime to listen on a different endpoint.
333+
334+
Example:
335+
336+
```sh
337+
LOCAL_LAMBDA_SERVER_INVOCATION_ENDPOINT=/2015-03-31/functions/function/invocations swift run
338+
```
339+
340+
## Deploying your Swift Lambda functions
341+
342+
343+
TODO
344+
345+
346+
## Swift AWS Lambda Runtime - Design Principles
347+
348+
The [design document](Sources/AWSLambdaRuntimeCore/Documentation.docc/Proposals/0001-v2-api.md) details the v2 API proposal for the swift-aws-lambda-runtime library, which aims to enhance the developer experience for building serverless functions in Swift.
349+
350+
The proposal has been reviewed and [incorporated feedback from the community](https://forums.swift.org/t/aws-lambda-v2-api-proposal/73819). The full v2 API design document is available [in this repository](Sources/AWSLambdaRuntimeCore/Documentation.docc/Proposals/0001-v2-api.md).
351+
352+
### Key Design Principles
353+
354+
The v2 API prioritizes the following principles:
355+
356+
- Readability and Maintainability: Extensive use of `async`/`await` improves code clarity and simplifies maintenance.
357+
358+
- Developer Control: Developers own the `main()` function and have the flexibility to inject dependencies into the `LambdaRuntime`. This allows you to manage service lifecycles efficiently using [Swift Service Lifecycle](https://github.com/swift-server/swift-service-lifecycle) for structured concurrency.
359+
360+
- Simplified Codable Support: The `LambdaCodableAdapter` struct eliminates the need for verbose boilerplate code when encoding and decoding events and responses.
361+
362+
### New Capabilities
363+
364+
The v2 API introduces two new features:
365+
366+
[Response Streaming](https://aws.amazon.com/blogs/compute/introducing-aws-lambda-response-streaming/]): This functionality is ideal for handling large responses that need to be sent incrementally.  
367+
368+
[Background Work](https://aws.amazon.com/blogs/compute/running-code-after-returning-a-response-from-an-aws-lambda-function/): Schedule tasks to run after returning a response to the AWS Lambda control plane.
369+
370+
These new capabilities provide greater flexibility and control when building serverless functions in Swift with the swift-aws-lambda-runtime library.

0 commit comments

Comments
 (0)
Please sign in to comment.