Skip to content

Commit 2b1bf54

Browse files
author
Sebastien Stormacq
committed
Add a multi Source API example
1 parent b1553d2 commit 2b1bf54

File tree

8 files changed

+495
-0
lines changed

8 files changed

+495
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# This file is auto generated by SAM CLI build command
2+
3+
[function_build_definitions]
4+
5+
[layer_build_definitions]
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
Transform: AWS::Serverless-2016-10-31
3+
Description: Multi-source API Lambda function with ALB and API Gateway V2
4+
Resources:
5+
MultiSourceAPIFunction:
6+
Type: AWS::Serverless::Function
7+
Properties:
8+
CodeUri: ../../.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/MultiSourceAPI/MultiSourceAPI.zip
9+
Handler: provided
10+
Runtime: provided.al2
11+
Architectures:
12+
- arm64
13+
MemorySize: 256
14+
Timeout: 30
15+
Environment:
16+
Variables:
17+
LOG_LEVEL: trace
18+
Events:
19+
ApiGatewayEvent:
20+
Type: HttpApi
21+
Properties:
22+
Path: /{proxy+}
23+
Method: ANY
24+
VPC:
25+
Type: AWS::EC2::VPC
26+
Properties:
27+
CidrBlock: 10.0.0.0/16
28+
EnableDnsHostnames: true
29+
EnableDnsSupport: true
30+
PublicSubnet1:
31+
Type: AWS::EC2::Subnet
32+
Properties:
33+
VpcId:
34+
Ref: VPC
35+
CidrBlock: 10.0.1.0/24
36+
AvailabilityZone:
37+
Fn::Select:
38+
- 0
39+
- Fn::GetAZs: ''
40+
MapPublicIpOnLaunch: true
41+
PublicSubnet2:
42+
Type: AWS::EC2::Subnet
43+
Properties:
44+
VpcId:
45+
Ref: VPC
46+
CidrBlock: 10.0.2.0/24
47+
AvailabilityZone:
48+
Fn::Select:
49+
- 1
50+
- Fn::GetAZs: ''
51+
MapPublicIpOnLaunch: true
52+
InternetGateway:
53+
Type: AWS::EC2::InternetGateway
54+
AttachGateway:
55+
Type: AWS::EC2::VPCGatewayAttachment
56+
Properties:
57+
VpcId:
58+
Ref: VPC
59+
InternetGatewayId:
60+
Ref: InternetGateway
61+
RouteTable:
62+
Type: AWS::EC2::RouteTable
63+
Properties:
64+
VpcId:
65+
Ref: VPC
66+
Route:
67+
Type: AWS::EC2::Route
68+
DependsOn: AttachGateway
69+
Properties:
70+
RouteTableId:
71+
Ref: RouteTable
72+
DestinationCidrBlock: '0.0.0.0/0'
73+
GatewayId:
74+
Ref: InternetGateway
75+
SubnetRouteTableAssociation1:
76+
Type: AWS::EC2::SubnetRouteTableAssociation
77+
Properties:
78+
SubnetId:
79+
Ref: PublicSubnet1
80+
RouteTableId:
81+
Ref: RouteTable
82+
SubnetRouteTableAssociation2:
83+
Type: AWS::EC2::SubnetRouteTableAssociation
84+
Properties:
85+
SubnetId:
86+
Ref: PublicSubnet2
87+
RouteTableId:
88+
Ref: RouteTable
89+
ALBSecurityGroup:
90+
Type: AWS::EC2::SecurityGroup
91+
Properties:
92+
GroupDescription: Security group for ALB
93+
VpcId:
94+
Ref: VPC
95+
SecurityGroupIngress:
96+
- IpProtocol: tcp
97+
FromPort: 80
98+
ToPort: 80
99+
CidrIp: '0.0.0.0/0'
100+
ApplicationLoadBalancer:
101+
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
102+
Properties:
103+
Scheme: internet-facing
104+
Subnets:
105+
- Ref: PublicSubnet1
106+
- Ref: PublicSubnet2
107+
SecurityGroups:
108+
- Ref: ALBSecurityGroup
109+
ALBTargetGroup:
110+
Type: AWS::ElasticLoadBalancingV2::TargetGroup
111+
DependsOn: ALBLambdaInvokePermission
112+
Properties:
113+
TargetType: lambda
114+
Targets:
115+
- Id:
116+
Fn::GetAtt:
117+
- MultiSourceAPIFunction
118+
- Arn
119+
ALBListener:
120+
Type: AWS::ElasticLoadBalancingV2::Listener
121+
Properties:
122+
LoadBalancerArn:
123+
Ref: ApplicationLoadBalancer
124+
Port: 80
125+
Protocol: HTTP
126+
DefaultActions:
127+
- Type: forward
128+
TargetGroupArn:
129+
Ref: ALBTargetGroup
130+
ALBLambdaInvokePermission:
131+
Type: AWS::Lambda::Permission
132+
Properties:
133+
FunctionName:
134+
Fn::GetAtt:
135+
- MultiSourceAPIFunction
136+
- Arn
137+
Action: lambda:InvokeFunction
138+
Principal: elasticloadbalancing.amazonaws.com
139+
Outputs:
140+
ApiGatewayUrl:
141+
Description: API Gateway endpoint URL
142+
Value:
143+
Fn::Sub: https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com
144+
ALBUrl:
145+
Description: Application Load Balancer URL
146+
Value:
147+
Fn::Sub: http://${ApplicationLoadBalancer.DNSName}

Examples/MultiSourceAPI/.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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
9+
Package.resolved
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// swift-tools-version:6.2
2+
3+
import PackageDescription
4+
5+
// needed for CI to test the local version of the library
6+
import struct Foundation.URL
7+
8+
let package = Package(
9+
name: "MultiSourceAPI",
10+
platforms: [.macOS(.v15)],
11+
products: [
12+
.executable(name: "MultiSourceAPI", targets: ["MultiSourceAPI"])
13+
],
14+
dependencies: [
15+
.package(url: "https://github.com/awslabs/swift-aws-lambda-runtime.git", from: "2.0.0"),
16+
.package(url: "https://github.com/awslabs/swift-aws-lambda-events.git", from: "1.0.0"),
17+
],
18+
targets: [
19+
.executableTarget(
20+
name: "MultiSourceAPI",
21+
dependencies: [
22+
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
23+
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
24+
],
25+
path: "Sources"
26+
)
27+
]
28+
)
29+
30+
if let localDepsPath = Context.environment["LAMBDA_USE_LOCAL_DEPS"],
31+
localDepsPath != "",
32+
let v = try? URL(fileURLWithPath: localDepsPath).resourceValues(forKeys: [.isDirectoryKey]),
33+
v.isDirectory == true
34+
{
35+
let indexToRemove = package.dependencies.firstIndex { dependency in
36+
if case .sourceControl(
37+
name: _,
38+
location: "https://github.com/awslabs/swift-aws-lambda-runtime.git",
39+
requirement: _
40+
) = dependency.kind {
41+
return true
42+
}
43+
return false
44+
}
45+
if let indexToRemove {
46+
package.dependencies.remove(at: indexToRemove)
47+
}
48+
49+
print("[INFO] Compiling against swift-aws-lambda-runtime located at \(localDepsPath)")
50+
package.dependencies += [
51+
.package(name: "swift-aws-lambda-runtime", path: localDepsPath)
52+
]
53+
}

Examples/MultiSourceAPI/README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Multi-Source API Example
2+
3+
This example demonstrates a Lambda function that handles requests from both Application Load Balancer (ALB) and API Gateway V2 by accepting a raw `ByteBuffer` and decoding the appropriate event type.
4+
5+
## Overview
6+
7+
The Lambda handler receives events as `ByteBuffer` and attempts to decode them as either:
8+
- `ALBTargetGroupRequest` - for requests from Application Load Balancer
9+
- `APIGatewayV2Request` - for requests from API Gateway V2
10+
11+
Based on the successfully decoded type, it returns an appropriate response.
12+
13+
## Building
14+
15+
```bash
16+
swift package archive --allow-network-connections docker
17+
```
18+
19+
## Deploying
20+
21+
Deploy using SAM:
22+
23+
```bash
24+
sam deploy \
25+
--resolve-s3 \
26+
--template-file template.yaml \
27+
--stack-name MultiSourceAPI \
28+
--capabilities CAPABILITY_IAM
29+
```
30+
31+
## Testing
32+
33+
After deployment, SAM will output two URLs:
34+
35+
### Test API Gateway V2:
36+
```bash
37+
curl https://<api-id>.execute-api.<region>.amazonaws.com/apigw/test
38+
```
39+
40+
Expected response:
41+
```json
42+
{"source":"APIGatewayV2","path":"/apigw/test"}
43+
```
44+
45+
### Test ALB:
46+
```bash
47+
curl http://<alb-dns-name>/alb/test
48+
```
49+
50+
Expected response:
51+
```json
52+
{"source":"ALB","path":"/alb/test"}
53+
```
54+
55+
## How It Works
56+
57+
The handler uses Swift's type-safe decoding to determine the event source:
58+
59+
1. Receives raw `ByteBuffer` event
60+
2. Attempts to decode as `ALBTargetGroupRequest`
61+
3. If that fails, attempts to decode as `APIGatewayV2Request`
62+
4. Returns appropriate response based on the decoded type
63+
5. Throws error if neither decoding succeeds
64+
65+
This pattern is useful when a single Lambda function needs to handle requests from multiple sources.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import AWSLambdaEvents
2+
import AWSLambdaRuntime
3+
import HTTPTypes
4+
import NIOCore
5+
6+
#if canImport(FoundationEssentials)
7+
import FoundationEssentials
8+
#else
9+
import Foundation
10+
#endif
11+
12+
struct MultiSourceHandler: StreamingLambdaHandler {
13+
func handle(
14+
_ event: ByteBuffer,
15+
responseWriter: some LambdaResponseStreamWriter,
16+
context: LambdaContext
17+
) async throws {
18+
let decoder = JSONDecoder()
19+
let data = Data(event.readableBytesView)
20+
21+
// Try to decode as ALBTargetGroupRequest first
22+
if let albRequest = try? decoder.decode(ALBTargetGroupRequest.self, from: data) {
23+
context.logger.info("Received ALB request to path: \(albRequest.path)")
24+
25+
let response = ALBTargetGroupResponse(
26+
statusCode: .ok,
27+
headers: ["Content-Type": "application/json"],
28+
body: "{\"source\":\"ALB\",\"path\":\"\(albRequest.path)\"}"
29+
)
30+
31+
let encoder = JSONEncoder()
32+
let responseData = try encoder.encode(response)
33+
try await responseWriter.write(ByteBuffer(bytes: responseData))
34+
try await responseWriter.finish()
35+
return
36+
}
37+
38+
// Try to decode as APIGatewayV2Request
39+
if let apiGwRequest = try? decoder.decode(APIGatewayV2Request.self, from: data) {
40+
context.logger.info("Received API Gateway V2 request to path: \(apiGwRequest.rawPath)")
41+
42+
let response = APIGatewayV2Response(
43+
statusCode: .ok,
44+
headers: ["Content-Type": "application/json"],
45+
body: "{\"source\":\"APIGatewayV2\",\"path\":\"\(apiGwRequest.rawPath)\"}"
46+
)
47+
48+
let encoder = JSONEncoder()
49+
let responseData = try encoder.encode(response)
50+
try await responseWriter.write(ByteBuffer(bytes: responseData))
51+
try await responseWriter.finish()
52+
return
53+
}
54+
55+
// Unknown event type
56+
context.logger.error("Unable to decode event as ALB or API Gateway V2 request")
57+
throw LambdaError.invalidEvent
58+
}
59+
}
60+
61+
enum LambdaError: Error {
62+
case invalidEvent
63+
}
64+
65+
let runtime = LambdaRuntime(handler: MultiSourceHandler())
66+
try await runtime.run()
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
version = 0.1
2+
3+
[default.deploy.parameters]
4+
stack_name = "sam-app"
5+
resolve_s3 = true
6+
s3_prefix = "sam-app"
7+
region = "eu-west-3"
8+
capabilities = "CAPABILITY_IAM"
9+
image_repositories = []
10+
11+
[default.global.parameters]
12+
region = "eu-west-3"

0 commit comments

Comments
 (0)