Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


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

Expand Down
5 changes: 5 additions & 0 deletions Examples/MultiSourceAPI/.aws-sam/build.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# This file is auto generated by SAM CLI build command

[function_build_definitions]

[layer_build_definitions]
147 changes: 147 additions & 0 deletions Examples/MultiSourceAPI/.aws-sam/build/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Multi-source API Lambda function with ALB and API Gateway V2
Resources:
MultiSourceAPIFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: ../../.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/MultiSourceAPI/MultiSourceAPI.zip
Handler: provided
Runtime: provided.al2
Architectures:
- arm64
MemorySize: 256
Timeout: 30
Environment:
Variables:
LOG_LEVEL: trace
Events:
ApiGatewayEvent:
Type: HttpApi
Properties:
Path: /{proxy+}
Method: ANY
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: ''
MapPublicIpOnLaunch: true
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId:
Ref: VPC
CidrBlock: 10.0.2.0/24
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: ''
MapPublicIpOnLaunch: true
InternetGateway:
Type: AWS::EC2::InternetGateway
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId:
Ref: VPC
InternetGatewayId:
Ref: InternetGateway
RouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VPC
Route:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId:
Ref: RouteTable
DestinationCidrBlock: '0.0.0.0/0'
GatewayId:
Ref: InternetGateway
SubnetRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: PublicSubnet1
RouteTableId:
Ref: RouteTable
SubnetRouteTableAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId:
Ref: PublicSubnet2
RouteTableId:
Ref: RouteTable
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for ALB
VpcId:
Ref: VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: '0.0.0.0/0'
ApplicationLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Scheme: internet-facing
Subnets:
- Ref: PublicSubnet1
- Ref: PublicSubnet2
SecurityGroups:
- Ref: ALBSecurityGroup
ALBTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
DependsOn: ALBLambdaInvokePermission
Properties:
TargetType: lambda
Targets:
- Id:
Fn::GetAtt:
- MultiSourceAPIFunction
- Arn
ALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn:
Ref: ApplicationLoadBalancer
Port: 80
Protocol: HTTP
DefaultActions:
- Type: forward
TargetGroupArn:
Ref: ALBTargetGroup
ALBLambdaInvokePermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName:
Fn::GetAtt:
- MultiSourceAPIFunction
- Arn
Action: lambda:InvokeFunction
Principal: elasticloadbalancing.amazonaws.com
Outputs:
ApiGatewayUrl:
Description: API Gateway endpoint URL
Value:
Fn::Sub: https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com
ALBUrl:
Description: Application Load Balancer URL
Value:
Fn::Sub: http://${ApplicationLoadBalancer.DNSName}
9 changes: 9 additions & 0 deletions Examples/MultiSourceAPI/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
Package.resolved
53 changes: 53 additions & 0 deletions Examples/MultiSourceAPI/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// swift-tools-version:6.2

import PackageDescription

// needed for CI to test the local version of the library
import struct Foundation.URL

let package = Package(
name: "MultiSourceAPI",
platforms: [.macOS(.v15)],
products: [
.executable(name: "MultiSourceAPI", targets: ["MultiSourceAPI"])
],
dependencies: [
.package(url: "https://github.com/awslabs/swift-aws-lambda-runtime.git", from: "2.0.0"),
.package(url: "https://github.com/awslabs/swift-aws-lambda-events.git", from: "1.0.0"),
],
targets: [
.executableTarget(
name: "MultiSourceAPI",
dependencies: [
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
],
path: "Sources"
)
]
)

if let localDepsPath = Context.environment["LAMBDA_USE_LOCAL_DEPS"],
localDepsPath != "",
let v = try? URL(fileURLWithPath: localDepsPath).resourceValues(forKeys: [.isDirectoryKey]),
v.isDirectory == true
{
let indexToRemove = package.dependencies.firstIndex { dependency in
if case .sourceControl(
name: _,
location: "https://github.com/awslabs/swift-aws-lambda-runtime.git",
requirement: _
) = dependency.kind {
return true
}
return false
}
if let indexToRemove {
package.dependencies.remove(at: indexToRemove)
}

print("[INFO] Compiling against swift-aws-lambda-runtime located at \(localDepsPath)")
package.dependencies += [
.package(name: "swift-aws-lambda-runtime", path: localDepsPath)
]
}
65 changes: 65 additions & 0 deletions Examples/MultiSourceAPI/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Multi-Source API Example

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.

## Overview

The Lambda handler receives events as `ByteBuffer` and attempts to decode them as either:
- `ALBTargetGroupRequest` - for requests from Application Load Balancer
- `APIGatewayV2Request` - for requests from API Gateway V2

Based on the successfully decoded type, it returns an appropriate response.

## Building

```bash
swift package archive --allow-network-connections docker
```

## Deploying

Deploy using SAM:

```bash
sam deploy \
--resolve-s3 \
--template-file template.yaml \
--stack-name MultiSourceAPI \
--capabilities CAPABILITY_IAM
```

## Testing

After deployment, SAM will output two URLs:

### Test API Gateway V2:
```bash
curl https://<api-id>.execute-api.<region>.amazonaws.com/apigw/test
```

Expected response:
```json
{"source":"APIGatewayV2","path":"/apigw/test"}
```

### Test ALB:
```bash
curl http://<alb-dns-name>/alb/test
```

Expected response:
```json
{"source":"ALB","path":"/alb/test"}
```

## How It Works

The handler uses Swift's type-safe decoding to determine the event source:

1. Receives raw `ByteBuffer` event
2. Attempts to decode as `ALBTargetGroupRequest`
3. If that fails, attempts to decode as `APIGatewayV2Request`
4. Returns appropriate response based on the decoded type
5. Throws error if neither decoding succeeds

This pattern is useful when a single Lambda function needs to handle requests from multiple sources.
66 changes: 66 additions & 0 deletions Examples/MultiSourceAPI/Sources/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import AWSLambdaEvents
import AWSLambdaRuntime
import HTTPTypes
import NIOCore

#if canImport(FoundationEssentials)
import FoundationEssentials
#else
import Foundation
#endif

struct MultiSourceHandler: StreamingLambdaHandler {
func handle(
_ event: ByteBuffer,
responseWriter: some LambdaResponseStreamWriter,
context: LambdaContext
) async throws {
let decoder = JSONDecoder()
let data = Data(event.readableBytesView)

// Try to decode as ALBTargetGroupRequest first
if let albRequest = try? decoder.decode(ALBTargetGroupRequest.self, from: data) {
context.logger.info("Received ALB request to path: \(albRequest.path)")

let response = ALBTargetGroupResponse(
statusCode: .ok,
headers: ["Content-Type": "application/json"],
body: "{\"source\":\"ALB\",\"path\":\"\(albRequest.path)\"}"
)

let encoder = JSONEncoder()
let responseData = try encoder.encode(response)
try await responseWriter.write(ByteBuffer(bytes: responseData))
try await responseWriter.finish()
return
}

// Try to decode as APIGatewayV2Request
if let apiGwRequest = try? decoder.decode(APIGatewayV2Request.self, from: data) {
context.logger.info("Received API Gateway V2 request to path: \(apiGwRequest.rawPath)")

let response = APIGatewayV2Response(
statusCode: .ok,
headers: ["Content-Type": "application/json"],
body: "{\"source\":\"APIGatewayV2\",\"path\":\"\(apiGwRequest.rawPath)\"}"
)

let encoder = JSONEncoder()
let responseData = try encoder.encode(response)
try await responseWriter.write(ByteBuffer(bytes: responseData))
try await responseWriter.finish()
return
}

// Unknown event type
context.logger.error("Unable to decode event as ALB or API Gateway V2 request")
throw LambdaError.invalidEvent
}
}

enum LambdaError: Error {
case invalidEvent
}

let runtime = LambdaRuntime(handler: MultiSourceHandler())
try await runtime.run()
12 changes: 12 additions & 0 deletions Examples/MultiSourceAPI/samconfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version = 0.1

[default.deploy.parameters]
stack_name = "sam-app"
resolve_s3 = true
s3_prefix = "sam-app"
region = "eu-west-3"
capabilities = "CAPABILITY_IAM"
image_repositories = []

[default.global.parameters]
region = "eu-west-3"
Loading
Loading