此库为 AWS Lambda transport for Swift OpenAPI generator 提供了一个 AWS Lambda 传输
此库允许将 Swift OpenAPI generator 生成的服务器端 Swift OpenAPI 实现公开为 AWS Lambda 函数。
此库提供两个功能
APIGatewayV2
) 事件类型的绑定。也支持其他 Lambda 函数绑定(事件类型),具体取决于您的需求。 我们包含有关创建与 Amazon API Gateway (REST API 模式) 绑定的说明
要基于 OpenAPI API 定义编写和部署 AWS Lambda 函数,您需要以下内容
如果您已经有一个 OpenAPI 定义,您已经生成了服务器存根,并且编写了一个实现,那么以下是将您的 OpenAPI 服务实现公开为 AWS Lambda 函数和 Amazon API Gateway HTTP API (又名 APIGatewayV2
) 的附加步骤。
如果您不知道如何开始,请阅读下一节,其中有一个 包含分步说明的教程。
要将您的 OpenAPI 实现公开为 AWS Lambda 函数
将依赖项添加到您的 Package.swift
项目依赖项
dependencies: [
.package(url: "https://github.com/apple/swift-openapi-generator.git", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-openapi-runtime.git", from: "1.0.0"),
// add these three dependencies
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", from: "1.0.0-alpha.1"),
.package(url: "https://github.com/swift-server/swift-aws-lambda-events.git", from: "0.1.0"),
.package(url: "https://github.com/sebsto/swift-openapi-lambda", from: "0.1.1")
],
目标依赖项
.executableTarget(
name: "YourOpenAPIService",
dependencies: [
.product(name: "OpenAPIRuntime",package: "swift-openapi-runtime"),
// add these three dependencies
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
.product(name: "OpenAPILambda",package: "swift-openapi-lambda"),
],
只需对您现有的实现进行四处更改
import Foundation
import OpenAPIRuntime
import OpenAPILambda // <-- 1. import this library
@main // <-- 2. flag this struct as the executable target entrypoint
struct QuoteServiceImpl: APIProtocol, OpenAPILambdaHttpApi { // <-- 3. add the OpenAPILambdaHttpApi protocol
init(transport: OpenAPILambdaTransport) throws { // <-- 4. add this constructor (don't remove the call to `registerHandlers(on:)`)
try self.registerHandlers(on: transport)
}
// the rest below is unmodified
func getQuote(_ input: Operations.getQuote.Input) async throws -> Operations.getQuote.Output {
let symbol = input.path.symbol
let price = Components.Schemas.quote(
symbol: symbol,
price: Double.random(in: 100..<150).rounded(),
change: Double.random(in: -5..<5).rounded(),
changePercent: Double.random(in: -0.05..<0.05),
volume: Double.random(in: 10000..<100000).rounded(),
timestamp: Date())
return .ok(.init(body: .json(price)))
}
}
APIGatewayV2
)🎉 尽情享用吧!
mkdir quoteapi && cd quoteapi
swift package init --name quoteapi --type executable
#
# the $ signs are escaped (\$) to work with the cat << EOF command
# if you choose to copy the content directly to a text editor,
# be sure to remove the \ (that means \$ becomes $)
#
cat << EOF > Sources/openapi.yaml
openapi: 3.1.0
info:
title: StockQuoteService
version: 1.0.0
components:
schemas:
quote:
type: object
properties:
symbol:
type: string
price:
type: number
change:
type: number
changePercent:
type: number
volume:
type: number
timestamp:
type: string
format: date-time
paths:
/stocks/{symbol}:
get:
summary: Get the latest quote for a stock
operationId: getQuote
parameters:
- name: symbol
in: path
required: true
schema:
type: string
tags:
- stocks
responses:
200:
description: OK
content:
application/json:
schema:
\$ref: '#/components/schemas/quote'
400:
description: Bad Request
404:
description: Not Found
EOF
cat << EOF > Sources/openapi-generator-config.yaml
generate:
- types
- server
EOF
Package.swift
文件定义目标及其依赖项cat << EOF > Package.swift
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "QuoteService",
platforms: [
.macOS(.v13), .iOS(.v15), .tvOS(.v15), .watchOS(.v6),
],
products: [
.executable(name: "QuoteService", targets: ["QuoteService"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-openapi-generator.git", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-openapi-runtime.git", from: "1.0.0"),
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", from: "1.0.0-alpha.1"),
.package(url: "https://github.com/swift-server/swift-aws-lambda-events.git", from: "0.1.0"),
.package(url: "https://github.com/sebsto/swift-openapi-lambda", from: "0.1.1")
],
targets: [
.executableTarget(
name: "QuoteService",
dependencies: [
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
.product(name: "OpenAPIRuntime",package: "swift-openapi-runtime"),
.product(name: "OpenAPILambda",package: "swift-openapi-lambda"),
],
path: "Sources",
resources: [
.copy("openapi.yaml"),
.copy("openapi-generator-config.yaml")
],
plugins: [
.plugin(
name: "OpenAPIGenerator",
package: "swift-openapi-generator"
)
]
),
]
)
EOF
swift build
main.swift
替换为您自己的实现rm Sources/main.swift
cat << EOF > Sources/QuoteService.swift
import Foundation
import OpenAPIRuntime
import OpenAPILambda
@main
struct QuoteServiceImpl: APIProtocol, OpenAPILambdaHttpApi {
init(transport: OpenAPILambdaTransport) throws {
try self.registerHandlers(on: transport)
}
func getQuote(_ input: Operations.getQuote.Input) async throws -> Operations.getQuote.Output {
let symbol = input.path.symbol
let price = Components.Schemas.quote(
symbol: symbol,
price: Double.random(in: 100..<150).rounded(),
change: Double.random(in: -5..<5).rounded(),
changePercent: Double.random(in: -0.05..<0.05),
volume: Double.random(in: 10000..<100000).rounded(),
timestamp: Date())
return .ok(.init(body: .json(price)))
}
}
EOF
swift build
#
# the $ signs are escaped (\$) to work with the cat << EOF command
# if you choose to copy the content directly to a text editor,
# be sure to remove the \ (that means \$ becomes $)
#
cat << EOF > Dockerfile
# image used to compile your Swift code
FROM public.ecr.aws/docker/library/swift:5.9.1-amazonlinux2
RUN yum -y install git jq tar zip openssl-devel
EOF
cat << EOF > Makefile
### Add functions here and link them to builder-bot format MUST BE "build-FunctionResourceName in template.yaml"
build-QuoteService: builder-bot
# Helper commands
deploy:
sam deploy
logs:
sam logs --stack-name QuoteService --name QuoteService
tail:
sam logs --stack-name QuoteService --name QuoteService --tail
###################### No Change required below this line ##########################
builder-bot:
\$(eval \$@PRODUCT = \$(subst build-,,\$(MAKECMDGOALS)))
\$(eval \$@BUILD_DIR = \$(PWD)/.aws-sam/build-swift)
\$(eval \$@STAGE = \$(\$@BUILD_DIR)/lambda)
\$(eval \$@ARTIFACTS_DIR = \$(PWD)/.aws-sam/build/\$(\$@PRODUCT))
# build docker image to compile Swift for Linux
docker build -f Dockerfile . -t swift-builder
# prep directories
mkdir -p \$(\$@BUILD_DIR)/lambda \$(\$@ARTIFACTS_DIR)
# compile application inside Docker image using source code from local project folder
docker run --rm -v \$(\$@BUILD_DIR):/build-target -v \`pwd\`:/build-src -w /build-src swift-builder bash -cl "swift build --static-swift-stdlib --product \$(\$@PRODUCT) -c release --build-path /build-target"
# create lambda bootstrap file
docker run --rm -v \$(\$@BUILD_DIR):/build-target -v \`pwd\`:/build-src -w /build-src swift-builder bash -cl "cd /build-target/lambda && ln -s \$(\$@PRODUCT) /bootstrap"
# copy binary to stage
cp \$(\$@BUILD_DIR)/release/\$(\$@PRODUCT) \$(\$@STAGE)/bootstrap
# copy app from stage to artifacts dir
cp \$(\$@STAGE)/* \$(\$@ARTIFACTS_DIR)
EOF
#
# the $ signs are escaped (\$) to work with the cat << EOF command
# if you choose to copy the content directly to a text editor,
# be sure to remove the \ (that means \$ becomes $)
#
cat << EOF > template.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: SAM Template for QuoteService
Globals:
Function:
Timeout: 60
CodeUri: .
Handler: swift.bootstrap
Runtime: provided.al2
MemorySize: 512
Architectures:
- arm64
Resources:
# Lambda function
QuoteService:
Type: AWS::Serverless::Function
Properties:
Events:
# pass through all HTTP verbs and paths
Api:
Type: HttpApi
Properties:
Path: /{proxy+}
Method: ANY
Metadata:
BuildMethod: makefile
# print API endpoint and name of database table
Outputs:
SwiftAPIEndpoint:
Description: "API Gateway endpoint URL for your application"
Value: !Sub "https://\${ServerlessHttpApi}.execute-api.\${AWS::Region}.amazonaws.com"
EOF
sam build
# use --guided for the first deployment only.
# SAM cli collects a few parameters and store them in `samconfig.toml`
sam deploy --guided --stack-name QuoteService
接受所有默认值,除了
QuoteService has no authentication. Is this okay? [y/N]: <-- answer Y here
此命令输出 API Gateway 的 URL,例如
Outputs
-----------------------------------------------------------------------------------------------------------------------------
Key SwiftAPIEndpoint
Description API Gateway endpoint URL for your application
Value https://747ukfmah7.execute-api.us-east-1.amazonaws.com
-----------------------------------------------------------------------------------------------------------------------------
curl [[ Replace with SWIFTAPIEndpoint value ]]/stocks/AAPL
{
"change" : -4,
"changePercent" : -0.030052760210257923,
"price" : 111,
"symbol" : "AAPL",
"timestamp" : "2023-12-13T03:12:35Z",
"volume" : 63812
}
在新的 AWS 账户上,部署和测试此示例代码不收取任何费用,每月最多 100 万次调用。它属于 AWS Lambda 和 Amazon API Gateway 的永久 AWS 免费套餐
当您的账户超过一年时,您将被收取每百万次 API Gateway 调用 1.0 美元的费用。AWS Lambda 函数调用在每月最多 400 万次调用和 400,000 GB 秒计算时间内仍然免费。
要删除 AWS Lambda 函数、API Gateway 以及使用 sam
创建的角色和权限,只需键入
sam delete
git clone https://github.com/sebsto/swift-openapi-lambda.git && cd swift-openapi-lambda
# In the directory of the Swift OpenAPI Lambda transport project
LOCAL_LAMBDA_SERVER_ENABLED=true swift run
# from another terminal, in the directory of the QuoteAPI sample project
curl -v -X POST --header "Content-Type: application/json" --data @events/GetQuote.json http://127.0.0.1:7000/invoke
当您将 AWS Lambda 函数公开给其他事件类型时,您必须专门化 OpenAPILambda
协议并实现两种方法,这两种方法将您的 Lambda 函数输入数据转换为 OpenAPIRequest
,反之亦然,将 OpenAPIResponse
转换为您的 Lambda 函数输出类型。
这是一个 Amazon API Gateway (Rest Api)(又名原始 API Gateway)的示例。
我从一个 OpenAPI 存根实现开始 - 未修改。
import Foundation
import OpenAPIRuntime
struct QuoteServiceImpl: APIProtocol {
func getQuote(_ input: Operations.getQuote.Input) async throws -> Operations.getQuote.Output {
let symbol = input.path.symbol
let price = Components.Schemas.quote(
symbol: symbol,
price: Double.random(in: 100..<150).rounded(),
change: Double.random(in: -5..<5).rounded(),
changePercent: Double.random(in: -0.05..<0.05),
volume: Double.random(in: 10000..<100000).rounded(),
timestamp: Date())
return .ok(.init(body: .json(price)))
}
}
接下来,我实现一个符合 OpenAPILambda
的结构体。此结构体定义
init(transport:)
@main
)这是一个使用 APIGatewayRequest
和 APIGatewayResponse
的示例
@main
struct QuoteServiceLambda: OpenAPILambda {
typealias Event = APIGatewayRequest
typealias Output = APIGatewayResponse
public init(transport: OpenAPILambdaTransport) throws {
let openAPIHandler = QuoteServiceImpl()
try openAPIHandler.registerHandlers(on: transport)
}
}
下一步是从 OpenAPILambda
协议实现两种方法,以将您的 Lambda 函数输入数据 (APIGatewayRequest
) 转换为 OpenAPIRequest
,反之亦然,将 OpenAPIResponse
转换为您的 Lambda 函数输出类型 (APIGatewayResponses
)。
extension OpenAPILambda where Event == APIGatewayRequest {
/// Transform a Lambda input (`APIGatewayRequest` and `LambdaContext`) to an OpenAPILambdaRequest (`HTTPRequest`, `String?`)
public func request(context: LambdaContext, from request: Event) throws -> OpenAPILambdaRequest {
(try request.httpRequest(), request.body)
}
}
extension OpenAPILambda where Output == APIGatewayResponse {
/// Transform an OpenAPI response (`HTTPResponse`, `String?`) to a Lambda Output (`APIGatewayResponse`)
public func output(from response: OpenAPILambdaResponse) -> Output {
var apiResponse = APIGatewayResponse(from: response.0)
apiResponse.body = response.1
return apiResponse
}
}
为了使上面的代码简短、简单且易于阅读,我们建议在 Lambda 源事件类型上实现任何扩展。以下是支持上述代码所需的扩展。这些是从一种类型到另一种类型的简单数据转换方法。
extension APIGatewayRequest {
/// Return an `HTTPRequest.Method` for this `APIGatewayRequest`
public func httpRequestMethod() throws -> HTTPRequest.Method {
guard let method = HTTPRequest.Method(rawValue: self.httpMethod.rawValue) else {
throw OpenAPILambdaHttpError.invalidMethod(self.httpMethod.rawValue)
}
return method
}
/// Return an `HTTPRequest` for this `APIGatewayV2Request`
public func httpRequest() throws -> HTTPRequest {
try HTTPRequest(
method: self.httpRequestMethod(),
scheme: "https",
authority: "",
path: self.path,
headerFields: self.headers.httpFields()
)
}
}
extension APIGatewayResponse {
/// Create a `APIGatewayV2Response` from an `HTTPResponse`
public init(from response: HTTPResponse) {
self = APIGatewayResponse(
statusCode: .init(code: UInt(response.status.code)),
headers: .init(from: response.headerFields),
isBase64Encoded: false
)
}
}
您可以应用相同的设计来支持其他 AWS Lambda 事件类型。但是,请记住,OpenAPILambda
实现严重偏向于接收、路由和响应 HTTP 请求。
要开始使用 Swift OpenAPI generator,请查看完整的文档,其中包含 一个分步教程。
AWS Lambda 的 Swift 运行时允许您使用 Swift 编程语言编写 AWS Lambda 函数。
阅读“什么是 SAM”以了解并开始使用 SAM。