diff --git a/Examples/APIGatewayV1/Package.swift b/Examples/APIGatewayV1/Package.swift new file mode 100644 index 00000000..f52f9d74 --- /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", from: "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..c925e527 --- /dev/null +++ b/Examples/APIGatewayV1/README.md @@ -0,0 +1,142 @@ +# API Gateway + +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. + +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 +{"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. + +```bash +curl -s https://a5q74es3k2.execute-api.us-east-1.amazonaws.com | jq +{ + "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": { + "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" + }, + "resourceId": "123456", + "path": "/", + "resourcePath": "/", + "accountId": "123456789012" + }, + "multiValueQueryStringParameters": {}, + "resource": "/", + "headers": { + "Accept": "*/*", + "X-Forwarded-Proto": "http", + "X-Forwarded-Port": "3000", + "Host": "localhost:3000", + "User-Agent": "curl/8.7.1" + }, + "httpMethod": "GET" +} +``` + +## 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..2f65d84f --- /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) 2025 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..b4b8e503 --- /dev/null +++ b/Examples/APIGatewayV1/template.yaml @@ -0,0 +1,34 @@ +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: 128 + 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 + Properties: + Path: / + Method: GET + +Outputs: + # print API Gateway endpoint + APIGatewayEndpoint: + Description: "API Gateway endpoint URL" + Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"