From a84ab03517cecd6c7bb4505a4776d729707ef9ce Mon Sep 17 00:00:00 2001 From: Erin Sparling Date: Thu, 25 Sep 2025 22:16:34 -0400 Subject: [PATCH 1/5] Draft of the "vintage" APIGatewayV1 example --- Examples/APIGatewayV1/Package.swift | 56 ++++++++++ Examples/APIGatewayV1/README.md | 124 +++++++++++++++++++++++ Examples/APIGatewayV1/Sources/main.swift | 30 ++++++ Examples/APIGatewayV1/template.yaml | 31 ++++++ 4 files changed, 241 insertions(+) create mode 100644 Examples/APIGatewayV1/Package.swift create mode 100644 Examples/APIGatewayV1/README.md create mode 100644 Examples/APIGatewayV1/Sources/main.swift create mode 100644 Examples/APIGatewayV1/template.yaml diff --git a/Examples/APIGatewayV1/Package.swift b/Examples/APIGatewayV1/Package.swift new file mode 100644 index 00000000..04e67d08 --- /dev/null +++ b/Examples/APIGatewayV1/Package.swift @@ -0,0 +1,56 @@ +// 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: "swift-aws-lambda-runtime-example", + platforms: [.macOS(.v15)], + products: [ + .executable(name: "APIGatewayLambda", targets: ["APIGatewayLambda"]) + ], + dependencies: [ + // during CI, the dependency on local version of swift-aws-lambda-runtime is added dynamically below + .package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", revision: "a1ab8df"), //2.0.0 + .package(url: "https://github.com/swift-server/swift-aws-lambda-events.git", from: "1.0.0"), + ], + targets: [ + .executableTarget( + name: "APIGatewayLambda", + 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 +{ + // when we use the local runtime as deps, let's remove the dependency added above + let indexToRemove = package.dependencies.firstIndex { dependency in + if case .sourceControl( + name: _, + location: "https://github.com/swift-server/swift-aws-lambda-runtime.git", + requirement: _ + ) = dependency.kind { + return true + } + return false + } + if let indexToRemove { + package.dependencies.remove(at: indexToRemove) + } + + // then we add the dependency on LAMBDA_USE_LOCAL_DEPS' path (typically ../..) + print("[INFO] Compiling against swift-aws-lambda-runtime located at \(localDepsPath)") + package.dependencies += [ + .package(name: "swift-aws-lambda-runtime", path: localDepsPath) + ] +} diff --git a/Examples/APIGatewayV1/README.md b/Examples/APIGatewayV1/README.md new file mode 100644 index 00000000..ae849ba4 --- /dev/null +++ b/Examples/APIGatewayV1/README.md @@ -0,0 +1,124 @@ +# API Gateway + +This is a simple example of an AWS Lambda function invoked through an Amazon API Gateway V1. + +## Code + +The Lambda function takes all HTTP headers it receives as input and returns them as output. + +The code creates a `LambdaRuntime` struct. In it's simplest form, the initializer takes a function as argument. The function is the handler that will be invoked when the API Gateway receives an HTTP request. + +The handler is `(event: APIGatewayRequest, context: LambdaContext) -> APIGatewayResponse`. The function takes two arguments: +- the event argument is a `APIGatewayRequest`. It is the parameter passed by the API Gateway. It contains all data passed in the HTTP request and some meta data. +- the context argument is a `Lambda Context`. It is a description of the runtime context. + +The function must return a `APIGatewayResponse`. + +`APIGatewayRequest` and `APIGatewayResponse` are defined in the [Swift AWS Lambda Events](https://github.com/swift-server/swift-aws-lambda-events) library. + +## Build & Package + +To build the package, type the following commands. + +```bash +swift build +swift package archive --allow-network-connections docker +``` + +If there is no error, there is a ZIP file ready to deploy. +The ZIP file is located at `.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/APIGatewayLambda/APIGatewayLambda.zip` + +## Deploy + +The deployment must include the Lambda function and the API Gateway. We use the [Serverless Application Model (SAM)](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) to deploy the infrastructure. + +**Prerequisites** : Install the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) + +The example directory contains a file named `template.yaml` that describes the deployment. + +To actually deploy your Lambda function and create the infrastructure, type the following `sam` command. + +```bash +sam deploy \ +--resolve-s3 \ +--template-file template.yaml \ +--stack-name APIGatewayLambda \ +--capabilities CAPABILITY_IAM +``` + +At the end of the deployment, the script lists the API Gateway endpoint. +The output is similar to this one. + +``` +----------------------------------------------------------------------------------------------------------------------------- +Outputs +----------------------------------------------------------------------------------------------------------------------------- +Key APIGatewayEndpoint +Description API Gateway endpoint URL" +Value https://a5q74es3k2.execute-api.us-east-1.amazonaws.com +----------------------------------------------------------------------------------------------------------------------------- +``` + +## Invoke your Lambda function + +To invoke the Lambda function, use this `curl` command line. + +```bash +curl https://a5q74es3k2.execute-api.us-east-1.amazonaws.com +``` + +Be sure to replace the URL with the API Gateway endpoint returned in the previous step. + +This should print a JSON similar to + +```bash +{"version":"2.0","rawPath":"\/","isBase64Encoded":false,"rawQueryString":"","headers":{"user-agent":"curl\/8.7.1","accept":"*\/*","host":"a5q74es3k2.execute-api.us-east-1.amazonaws.com","content-length":"0","x-amzn-trace-id":"Root=1-66fb0388-691f744d4bd3c99c7436a78d","x-forwarded-port":"443","x-forwarded-for":"81.0.0.43","x-forwarded-proto":"https"},"requestContext":{"requestId":"e719cgNpoAMEcwA=","http":{"sourceIp":"81.0.0.43","path":"\/","protocol":"HTTP\/1.1","userAgent":"curl\/8.7.1","method":"GET"},"stage":"$default","apiId":"a5q74es3k2","time":"30\/Sep\/2024:20:01:12 +0000","timeEpoch":1727726472922,"domainPrefix":"a5q74es3k2","domainName":"a5q74es3k2.execute-api.us-east-1.amazonaws.com","accountId":"012345678901"} +``` + +If you have `jq` installed, you can use it to pretty print the output. + +```bash +curl -s https://a5q74es3k2.execute-api.us-east-1.amazonaws.com | jq +{ + "version": "2.0", + "rawPath": "/", + "requestContext": { + "domainPrefix": "a5q74es3k2", + "stage": "$default", + "timeEpoch": 1727726558220, + "http": { + "protocol": "HTTP/1.1", + "method": "GET", + "userAgent": "curl/8.7.1", + "path": "/", + "sourceIp": "81.0.0.43" + }, + "apiId": "a5q74es3k2", + "accountId": "012345678901", + "requestId": "e72KxgsRoAMEMSA=", + "domainName": "a5q74es3k2.execute-api.us-east-1.amazonaws.com", + "time": "30/Sep/2024:20:02:38 +0000" + }, + "rawQueryString": "", + "routeKey": "$default", + "headers": { + "x-forwarded-for": "81.0.0.43", + "user-agent": "curl/8.7.1", + "host": "a5q74es3k2.execute-api.us-east-1.amazonaws.com", + "accept": "*/*", + "x-amzn-trace-id": "Root=1-66fb03de-07533930192eaf5f540db0cb", + "content-length": "0", + "x-forwarded-proto": "https", + "x-forwarded-port": "443" + }, + "isBase64Encoded": false +} +``` + +## Undeploy + +When done testing, you can delete the infrastructure with this command. + +```bash +sam delete +``` \ No newline at end of file diff --git a/Examples/APIGatewayV1/Sources/main.swift b/Examples/APIGatewayV1/Sources/main.swift new file mode 100644 index 00000000..7c7d4d85 --- /dev/null +++ b/Examples/APIGatewayV1/Sources/main.swift @@ -0,0 +1,30 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import AWSLambdaEvents +import AWSLambdaRuntime + +let runtime = LambdaRuntime { + (event: APIGatewayRequest, context: LambdaContext) -> APIGatewayResponse in + + var header = HTTPHeaders() + context.logger.debug("Rest API Message received") + + header["content-type"] = "application/json" + + // echo the request in the response + return try APIGatewayResponse(statusCode: .ok, headers: header, encodableBody: event) +} + +try await runtime.run() diff --git a/Examples/APIGatewayV1/template.yaml b/Examples/APIGatewayV1/template.yaml new file mode 100644 index 00000000..adb25437 --- /dev/null +++ b/Examples/APIGatewayV1/template.yaml @@ -0,0 +1,31 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: SAM Template for APIGateway Lambda Example + +Resources: + # Lambda function + APIGatewayLambda: + Type: AWS::Serverless::Function + Properties: + CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/APIGatewayLambda/APIGatewayLambda.zip + Timeout: 60 + Handler: swift.bootstrap # ignored by the Swift runtime + Runtime: provided.al2 + MemorySize: 512 + Architectures: + - arm64 + Environment: + Variables: + # by default, AWS Lambda runtime produces no log + # use `LOG_LEVEL: debug` for for lifecycle and event handling information + # use `LOG_LEVEL: trace` for detailed input event information + LOG_LEVEL: debug + Events: + RestApi: + Type: Api + +Outputs: + # print API Gateway endpoint + APIGatewayEndpoint: + Description: "API Gateway endpoint URL" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" From 9a4cf90915863bf9d67b4cfccca6731b4d7ee0c6 Mon Sep 17 00:00:00 2001 From: Erin Sparling Date: Thu, 25 Sep 2025 22:38:06 -0400 Subject: [PATCH 2/5] Updated sample responses, and included necessary template definition --- Examples/APIGatewayV1/README.md | 73 +++++++++++++++++------------ Examples/APIGatewayV1/template.yaml | 3 ++ 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/Examples/APIGatewayV1/README.md b/Examples/APIGatewayV1/README.md index ae849ba4..ccfde270 100644 --- a/Examples/APIGatewayV1/README.md +++ b/Examples/APIGatewayV1/README.md @@ -71,8 +71,8 @@ Be sure to replace the URL with the API Gateway endpoint returned in the previou This should print a JSON similar to -```bash -{"version":"2.0","rawPath":"\/","isBase64Encoded":false,"rawQueryString":"","headers":{"user-agent":"curl\/8.7.1","accept":"*\/*","host":"a5q74es3k2.execute-api.us-east-1.amazonaws.com","content-length":"0","x-amzn-trace-id":"Root=1-66fb0388-691f744d4bd3c99c7436a78d","x-forwarded-port":"443","x-forwarded-for":"81.0.0.43","x-forwarded-proto":"https"},"requestContext":{"requestId":"e719cgNpoAMEcwA=","http":{"sourceIp":"81.0.0.43","path":"\/","protocol":"HTTP\/1.1","userAgent":"curl\/8.7.1","method":"GET"},"stage":"$default","apiId":"a5q74es3k2","time":"30\/Sep\/2024:20:01:12 +0000","timeEpoch":1727726472922,"domainPrefix":"a5q74es3k2","domainName":"a5q74es3k2.execute-api.us-east-1.amazonaws.com","accountId":"012345678901"} +```bash +{"httpMethod":"GET","queryStringParameters":{},"isBase64Encoded":false,"resource":"\/","path":"\/","headers":{"X-Forwarded-Port":"3000","X-Forwarded-Proto":"http","User-Agent":"curl\/8.7.1","Host":"localhost:3000","Accept":"*\/*"},"requestContext":{"resourcePath":"\/","identity":{"sourceIp":"127.0.0.1","userAgent":"Custom User Agent String"},"httpMethod":"GET","resourceId":"123456","accountId":"123456789012","apiId":"1234567890","requestId":"a9d2db08-8364-4da4-8237-8912bf8148c8","domainName":"localhost:3000","stage":"Prod","path":"\/"},"multiValueQueryStringParameters":{},"pathParameters":{},"multiValueHeaders":{"Accept":["*\/*"],"Host":["localhost:3000"],"X-Forwarded-Port":["3000"],"User-Agent":["curl\/8.7.1"],"X-Forwarded-Proto":["http"]},"stageVariables":{}} ``` If you have `jq` installed, you can use it to pretty print the output. @@ -80,38 +80,53 @@ If you have `jq` installed, you can use it to pretty print the output. ```bash curl -s https://a5q74es3k2.execute-api.us-east-1.amazonaws.com | jq { - "version": "2.0", - "rawPath": "/", + "stageVariables": {}, + "queryStringParameters": {}, + "multiValueHeaders": { + "Accept": [ + "*/*" + ], + "User-Agent": [ + "curl/8.7.1" + ], + "X-Forwarded-Proto": [ + "http" + ], + "Host": [ + "localhost:3000" + ], + "X-Forwarded-Port": [ + "3000" + ] + }, + "pathParameters": {}, + "isBase64Encoded": false, + "path": "/", "requestContext": { - "domainPrefix": "a5q74es3k2", - "stage": "$default", - "timeEpoch": 1727726558220, - "http": { - "protocol": "HTTP/1.1", - "method": "GET", - "userAgent": "curl/8.7.1", - "path": "/", - "sourceIp": "81.0.0.43" + "apiId": "1234567890", + "stage": "Prod", + "httpMethod": "GET", + "domainName": "localhost:3000", + "requestId": "a9d2db08-8364-4da4-8237-8912bf8148c8", + "identity": { + "userAgent": "Custom User Agent String", + "sourceIp": "127.0.0.1" }, - "apiId": "a5q74es3k2", - "accountId": "012345678901", - "requestId": "e72KxgsRoAMEMSA=", - "domainName": "a5q74es3k2.execute-api.us-east-1.amazonaws.com", - "time": "30/Sep/2024:20:02:38 +0000" + "resourceId": "123456", + "path": "/", + "resourcePath": "/", + "accountId": "123456789012" }, - "rawQueryString": "", - "routeKey": "$default", + "multiValueQueryStringParameters": {}, + "resource": "/", "headers": { - "x-forwarded-for": "81.0.0.43", - "user-agent": "curl/8.7.1", - "host": "a5q74es3k2.execute-api.us-east-1.amazonaws.com", - "accept": "*/*", - "x-amzn-trace-id": "Root=1-66fb03de-07533930192eaf5f540db0cb", - "content-length": "0", - "x-forwarded-proto": "https", - "x-forwarded-port": "443" + "Accept": "*/*", + "X-Forwarded-Proto": "http", + "X-Forwarded-Port": "3000", + "Host": "localhost:3000", + "User-Agent": "curl/8.7.1" }, - "isBase64Encoded": false + "httpMethod": "GET" } ``` diff --git a/Examples/APIGatewayV1/template.yaml b/Examples/APIGatewayV1/template.yaml index adb25437..a91b28a9 100644 --- a/Examples/APIGatewayV1/template.yaml +++ b/Examples/APIGatewayV1/template.yaml @@ -23,6 +23,9 @@ Resources: Events: RestApi: Type: Api + Properties: + Path: / + Method: GET Outputs: # print API Gateway endpoint From fab6af7019f631e1f0b00d46b1a043b1c741f32e Mon Sep 17 00:00:00 2001 From: Erin Sparling Date: Thu, 25 Sep 2025 22:44:28 -0400 Subject: [PATCH 3/5] Reverted back to semver 2.0.0, in hopes that it eventually gets tagged. --- Examples/APIGatewayV1/Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/APIGatewayV1/Package.swift b/Examples/APIGatewayV1/Package.swift index 04e67d08..f52f9d74 100644 --- a/Examples/APIGatewayV1/Package.swift +++ b/Examples/APIGatewayV1/Package.swift @@ -13,7 +13,7 @@ let package = Package( ], dependencies: [ // during CI, the dependency on local version of swift-aws-lambda-runtime is added dynamically below - .package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", revision: "a1ab8df"), //2.0.0 + .package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", from: "2.0.0"), .package(url: "https://github.com/swift-server/swift-aws-lambda-events.git", from: "1.0.0"), ], targets: [ From f1e7073cf815e4b526d4167228bd3020c6851a7c Mon Sep 17 00:00:00 2001 From: Erin Sparling Date: Thu, 25 Sep 2025 22:45:26 -0400 Subject: [PATCH 4/5] Added v1 vs v2 note --- Examples/APIGatewayV1/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Examples/APIGatewayV1/README.md b/Examples/APIGatewayV1/README.md index ccfde270..c925e527 100644 --- a/Examples/APIGatewayV1/README.md +++ b/Examples/APIGatewayV1/README.md @@ -2,6 +2,9 @@ This is a simple example of an AWS Lambda function invoked through an Amazon API Gateway V1. +> [!NOTE] +> This example uses the API Gateway V1 `Api` endpoint type, whereas the [API Gateway V2](https://github.com/swift-server/swift-aws-lambda-runtime/tree/main/Examples/APIGateway) example uses the `HttpApi` endpoint type. For more information, see [Choose between REST AIs and HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html). + ## Code The Lambda function takes all HTTP headers it receives as input and returns them as output. From a00ba601b349138be39306f3e8fdc0551399c76d Mon Sep 17 00:00:00 2001 From: Erin Sparling Date: Fri, 26 Sep 2025 08:07:59 -0400 Subject: [PATCH 5/5] Updated per @sebsto's feedback. --- Examples/APIGatewayV1/Sources/main.swift | 2 +- Examples/APIGatewayV1/template.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Examples/APIGatewayV1/Sources/main.swift b/Examples/APIGatewayV1/Sources/main.swift index 7c7d4d85..2f65d84f 100644 --- a/Examples/APIGatewayV1/Sources/main.swift +++ b/Examples/APIGatewayV1/Sources/main.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftAWSLambdaRuntime open source project // -// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Copyright (c) 2025 Apple Inc. and the SwiftAWSLambdaRuntime project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information diff --git a/Examples/APIGatewayV1/template.yaml b/Examples/APIGatewayV1/template.yaml index a91b28a9..b4b8e503 100644 --- a/Examples/APIGatewayV1/template.yaml +++ b/Examples/APIGatewayV1/template.yaml @@ -11,7 +11,7 @@ Resources: Timeout: 60 Handler: swift.bootstrap # ignored by the Swift runtime Runtime: provided.al2 - MemorySize: 512 + MemorySize: 128 Architectures: - arm64 Environment: