Skip to content

Commit 8959fdc

Browse files
committed
add streaming example and doc
1 parent 4992ba5 commit 8959fdc

File tree

8 files changed

+412
-2
lines changed

8 files changed

+412
-2
lines changed

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/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: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
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+
```bash
43+
AWS_ACCOUNT_ID=012345678901
44+
aws lambda create-function \
45+
--function-name StreamingNumbers \
46+
--zip-file fileb://.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/StreamingNumbers/StreamingNumbers.zip \
47+
--runtime provided.al2 \
48+
--handler provided \
49+
--architectures arm64 \
50+
--role arn:aws:iam::$AWS_ACCOUNT_ID:role/lambda_basic_execution \
51+
--timeout 15
52+
```
53+
54+
> [!IMPORTANT]
55+
> 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, teh sample function stream responses during 10 seconds and we set the timeout for 15 seconds.
56+
57+
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`.
58+
59+
Be sure to set `AWS_ACCOUNT_ID` with your actual AWS account ID (for example: 012345678901).
60+
61+
### Step2: Give permission to invoke that function through an URL
62+
63+
Anyone with a valid signature from your AWS account will have permission to invoke the function through its URL.
64+
65+
```bash
66+
aws lambda add-permission \
67+
--function-name StreamingNumbers \
68+
--action lambda:InvokeFunctionUrl \
69+
--principal $AWS_ACCOUNT_ID \
70+
--function-url-auth-type AWS_IAM \
71+
--statement-id allowURL
72+
```
73+
74+
Be sure to replace <YOUR_ACCOUNT_ID> with your actual AWS account ID (for example: 012345678901).
75+
76+
### Step3: Create the URL
77+
78+
This creates [an 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+
```bash
102+
URL=https://ul3nf4dogmgyr7ffl5r5rs22640fwocc.lambda-url.us-east-1.on.aws/
103+
REGION=us-east-1
104+
ACCESS_KEY=AK...
105+
SECRET_KEY=...
106+
AWS_SESSION_TOKEN=...
107+
108+
curl "$URL" \
109+
--user "$ACCESS_KEY":"$SECRET_KEY" \
110+
--aws-sigv4 "aws:amz:$REGION:lambda" \
111+
-H "x-amz-security-token: $AWS_SESSION_TOKEN" \
112+
--no-buffer
113+
```
114+
115+
Note that there is no payload required for this example.
116+
117+
This should output the following result, with a one-second delay between each numbers.
118+
119+
```
120+
1
121+
2
122+
3
123+
4
124+
5
125+
6
126+
7
127+
8
128+
9
129+
10
130+
```
131+
132+
### Undeploy
133+
134+
When done testing, you can delete the Lambda function with this command.
135+
136+
```bash
137+
aws lambda delete-function --function-name StreamingNumbers
138+
```
139+
140+
## Deploy with AWS SAM
141+
142+
Alternatively, you can use [AWS SAM](https://aws.amazon.com/serverless/sam/) to deploy the Lambda function.
143+
144+
**Prerequisites** : Install the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)
145+
146+
### SAM Template
147+
148+
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.
149+
150+
```yaml
151+
AWSTemplateFormatVersion: '2010-09-09'
152+
Transform: AWS::Serverless-2016-10-31
153+
Description: SAM Template for StreamingLambda Example
154+
155+
Resources:
156+
# Lambda function
157+
StreamingNumbers:
158+
Type: AWS::Serverless::Function
159+
Properties:
160+
CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/StreamingNumbers/StreamingNumbers.zip
161+
Timeout: 15
162+
Handler: swift.bootstrap # ignored by the Swift runtime
163+
Runtime: provided.al2
164+
MemorySize: 128
165+
Architectures:
166+
- arm64
167+
FunctionUrlConfig:
168+
AuthType: AWS_IAM
169+
InvokeMode: RESPONSE_STREAM
170+
171+
Outputs:
172+
# print Lambda function URL
173+
LambdaURL:
174+
Description: Lambda URL
175+
Value: !GetAtt StreamingNumbersUrl.FunctionUrl
176+
```
177+
178+
### Deploy with SAM
179+
180+
```bash
181+
sam deploy \
182+
--resolve-s3 \
183+
--template-file template.yaml \
184+
--stack-name StreamingNumbers \
185+
--capabilities CAPABILITY_IAM
186+
```
187+
188+
The URL of the function is provided as part of the output.
189+
190+
```
191+
CloudFormation outputs from deployed stack
192+
-----------------------------------------------------------------------------------------------------------------------------
193+
Outputs
194+
-----------------------------------------------------------------------------------------------------------------------------
195+
Key LambdaURL
196+
Description Lambda URL
197+
Value https://gaudpin2zjqizfujfnqxstnv6u0czrfu.lambda-url.us-east-1.on.aws/
198+
-----------------------------------------------------------------------------------------------------------------------------
199+
```
200+
201+
Once the function is deployed, you can invoke it with `curl`, similarly to what you did when deploying with the AWS CLI.
202+
203+
```bash
204+
curl "$URL" \
205+
--user "$ACCESS_KEY":"$SECRET_KEY" \
206+
--aws-sigv4 "aws:amz:$REGION:lambda" \
207+
-H "x-amz-security-token: $AWS_SESSION_TOKEN" \
208+
--no-buffer
209+
```
210+
211+
### Undeploy with SAM
212+
213+
When done testing, you can delete the infrastructure with this command.
214+
215+
```bash
216+
sam delete
217+
```

Examples/Streaming/Sources/main.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import AWSLambdaRuntime
2+
import NIOCore
3+
4+
struct SendNumbersWithPause: StreamingLambdaHandler {
5+
func handle(
6+
_ event: ByteBuffer,
7+
responseWriter: some LambdaResponseStreamWriter,
8+
context: LambdaContext
9+
) async throws {
10+
for i in 1...10 {
11+
// Send partial data
12+
try await responseWriter.write(ByteBuffer(string: "\(i)\n"))
13+
// Perform some long asynchronous work
14+
try await Task.sleep(for: .milliseconds(1000))
15+
}
16+
// All data has been sent. Close off the response stream.
17+
try await responseWriter.finish()
18+
}
19+
}
20+
21+
let runtime = LambdaRuntime.init(handler: SendNumbersWithPause())
22+
try await runtime.run()

Examples/Streaming/format.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/bin/bash
2+
## ===----------------------------------------------------------------------===##
3+
##
4+
## This source file is part of the Swift.org open source project
5+
##
6+
## Copyright (c) 2024 Apple Inc. and the Swift project authors
7+
## Licensed under Apache License v2.0 with Runtime Library Exception
8+
##
9+
## See https://swift.org/LICENSE.txt for license information
10+
## See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
11+
##
12+
## ===----------------------------------------------------------------------===##
13+
14+
set -euo pipefail
15+
16+
log() { printf -- "** %s\n" "$*" >&2; }
17+
error() { printf -- "** ERROR: %s\n" "$*" >&2; }
18+
fatal() { error "$@"; exit 1; }
19+
20+
21+
if [[ -f .swiftformatignore ]]; then
22+
log "Found swiftformatignore file..."
23+
24+
log "Running swift format format..."
25+
tr '\n' '\0' < .swiftformatignore| xargs -0 -I% printf '":(exclude)%" '| xargs git ls-files -z '*.swift' | xargs -0 swift format format --parallel --in-place
26+
27+
log "Running swift format lint..."
28+
29+
tr '\n' '\0' < .swiftformatignore | xargs -0 -I% printf '":(exclude)%" '| xargs git ls-files -z '*.swift' | xargs -0 swift format lint --strict --parallel
30+
else
31+
log "Running swift format format..."
32+
git ls-files -z '*.swift' | xargs -0 swift format format --parallel --in-place
33+
34+
log "Running swift format lint..."
35+
36+
git ls-files -z '*.swift' | xargs -0 swift format lint --strict --parallel
37+
fi
38+
39+
40+
41+
log "Checking for modified files..."
42+
43+
GIT_PAGER='' git diff --exit-code '*.swift'
44+
45+
log "✅ Found no formatting issues."

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 QuoteService
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

0 commit comments

Comments
 (0)