Skip to content

[WIP] Experimental generic runtime #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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 Examples/Echo/Lambda.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ struct MyLambda: LambdaHandler {
// setup your resources that you want to reuse for every invocation here.
}

func handle(_ input: String, context: LambdaContext) async throws -> String {
func handle(_ input: String, context: Context) async throws -> String {
// as an example, respond with the input's reversed
String(input.reversed())
}
Expand Down
2 changes: 1 addition & 1 deletion Examples/JSON/Lambda.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ struct MyLambda: LambdaHandler {
// setup your resources that you want to reuse for every invocation here.
}

func handle(_ event: Request, context: LambdaContext) async throws -> Response {
func handle(_ event: Request, context: Context) async throws -> Response {
// as an example, respond with the input event's reversed body
Response(body: String(event.body.reversed()))
}
Expand Down
23 changes: 15 additions & 8 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ let package = Package(
products: [
// this library exports `AWSLambdaRuntimeCore` and adds Foundation convenience methods
.library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]),
// this has all the main functionality for lambda and it does not link Foundation
// this has all the main functionality for AWS Lambda and it does not link Foundation
.library(name: "AWSLambdaRuntimeCore", targets: ["AWSLambdaRuntimeCore"]),
// // this is the supporting library for any AWS-like lambda runtime
// .library(name: "LambdaRuntimeCore", targets: ["LambdaRuntimeCore"]),
// for testing only
.library(name: "AWSLambdaTesting", targets: ["AWSLambdaTesting"]),
],
Expand All @@ -24,21 +26,26 @@ let package = Package(
.product(name: "NIOFoundationCompat", package: "swift-nio"),
]),
.target(name: "AWSLambdaRuntimeCore", dependencies: [
.byName(name: "LambdaRuntimeCore"),
.product(name: "NIOHTTP1", package: "swift-nio"),
.product(name: "NIOCore", package: "swift-nio"),
]),
.target(name: "LambdaRuntimeCore", dependencies: [
.product(name: "Logging", package: "swift-log"),
.product(name: "Backtrace", package: "swift-backtrace"),
.product(name: "NIOHTTP1", package: "swift-nio"),
.product(name: "NIOCore", package: "swift-nio"),
.product(name: "NIOConcurrencyHelpers", package: "swift-nio"),
.product(name: "NIOPosix", package: "swift-nio"),
]),
.testTarget(name: "AWSLambdaRuntimeCoreTests", dependencies: [
.byName(name: "AWSLambdaRuntimeCore"),
.product(name: "NIOTestUtils", package: "swift-nio"),
.product(name: "NIOFoundationCompat", package: "swift-nio"),
]),
// .testTarget(name: "AWSLambdaRuntimeCoreTests", dependencies: [
// .byName(name: "AWSLambdaRuntimeCore"),
// .product(name: "NIOTestUtils", package: "swift-nio"),
// .product(name: "NIOFoundationCompat", package: "swift-nio"),
// ]),
.testTarget(name: "AWSLambdaRuntimeTests", dependencies: [
.byName(name: "AWSLambdaRuntimeCore"),
.byName(name: "AWSLambdaRuntime"),
.byName(name: "AWSLambdaRuntimeCore"),
.byName(name: "LambdaRuntimeCore"),
]),
// testing helper
.target(name: "AWSLambdaTesting", dependencies: [
Expand Down
4 changes: 2 additions & 2 deletions Sources/AWSLambdaRuntime/Context+Foundation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
//
//===----------------------------------------------------------------------===//

import AWSLambdaRuntimeCore
@_spi(Lambda) import AWSLambdaRuntimeCore
import struct Foundation.Date

extension LambdaContext {
extension AWSLambda.Context {
var deadlineDate: Date {
let secondsSinceEpoch = Double(Int64(bitPattern: self.deadline.rawValue)) / -1_000_000_000
return Date(timeIntervalSince1970: secondsSinceEpoch)
Expand Down
27 changes: 27 additions & 0 deletions Sources/AWSLambdaRuntimeCore/AWSLambda.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftAWSLambdaRuntime open source project
//
// Copyright (c) 2022 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
//
//===----------------------------------------------------------------------===//

@_exported import LambdaRuntimeCore

public enum AWSLambda {}

extension AWSLambda: LambdaProvider {
public static var runtimeEngineAddress: String? {
Lambda.env("AWS_LAMBDA_RUNTIME_API")
}
}

extension ByteBufferLambdaHandler where Provider == AWSLambda {
public typealias Provider = AWSLambda
}
101 changes: 46 additions & 55 deletions Sources/AWSLambdaRuntimeCore/ControlPlaneRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the SwiftAWSLambdaRuntime open source project
//
// Copyright (c) 2017-2021 Apple Inc. and the SwiftAWSLambdaRuntime project authors
// Copyright (c) 2021-2022 Apple Inc. and the SwiftAWSLambdaRuntime project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
Expand All @@ -12,68 +12,59 @@
//
//===----------------------------------------------------------------------===//

import NIOCore
@_spi(Lambda) import LambdaRuntimeCore
import NIOHTTP1

enum ControlPlaneRequest: Hashable {
case next
case invocationResponse(String, ByteBuffer?)
case invocationError(String, ErrorResponse)
case initializationError(ErrorResponse)
}

enum ControlPlaneResponse: Hashable {
case next(Invocation, ByteBuffer)
case accepted
case error(ErrorResponse)
}
extension AWSLambda {
public struct Invocation: LambdaInvocation {
@_spi(Lambda) public var requestID: String
@_spi(Lambda) public var deadlineInMillisSinceEpoch: Int64
@_spi(Lambda) public var invokedFunctionARN: String
@_spi(Lambda) public var traceID: String
@_spi(Lambda) public var clientContext: String?
@_spi(Lambda) public var cognitoIdentity: String?

struct Invocation: Hashable {
let requestID: String
let deadlineInMillisSinceEpoch: Int64
let invokedFunctionARN: String
let traceID: String
let clientContext: String?
let cognitoIdentity: String?

init(headers: HTTPHeaders) throws {
guard let requestID = headers.first(name: AmazonHeaders.requestID), !requestID.isEmpty else {
throw Lambda.RuntimeError.invocationMissingHeader(AmazonHeaders.requestID)
init(requestID: String,
deadlineInMillisSinceEpoch: Int64,
invokedFunctionARN: String,
traceID: String,
clientContext: String?,
cognitoIdentity: String?) {
self.requestID = requestID
self.deadlineInMillisSinceEpoch = deadlineInMillisSinceEpoch
self.invokedFunctionARN = invokedFunctionARN
self.traceID = traceID
self.clientContext = clientContext
self.cognitoIdentity = cognitoIdentity
}

guard let deadline = headers.first(name: AmazonHeaders.deadline),
let unixTimeInMilliseconds = Int64(deadline)
else {
throw Lambda.RuntimeError.invocationMissingHeader(AmazonHeaders.deadline)
}
@_spi(Lambda)
public init(headers: HTTPHeaders) throws {
guard let requestID = headers.first(name: AmazonHeaders.requestID), !requestID.isEmpty else {
throw LambdaRuntimeError.invocationHeadMissingRequestID
}

guard let invokedFunctionARN = headers.first(name: AmazonHeaders.invokedFunctionARN) else {
throw Lambda.RuntimeError.invocationMissingHeader(AmazonHeaders.invokedFunctionARN)
}
guard let deadline = headers.first(name: AmazonHeaders.deadline) else {
throw LambdaRuntimeError.invocationHeadMissingDeadlineInMillisSinceEpoch
}
guard let unixTimeInMilliseconds = Int64(deadline) else {
throw LambdaRuntimeError.responseHeadInvalidDeadlineValue
}

self.requestID = requestID
self.deadlineInMillisSinceEpoch = unixTimeInMilliseconds
self.invokedFunctionARN = invokedFunctionARN
self.traceID = headers.first(name: AmazonHeaders.traceID) ?? "Root=\(AmazonHeaders.generateXRayTraceID());Sampled=0"
self.clientContext = headers["Lambda-Runtime-Client-Context"].first
self.cognitoIdentity = headers["Lambda-Runtime-Cognito-Identity"].first
}
}
guard let invokedFunctionARN = headers.first(name: AmazonHeaders.invokedFunctionARN) else {
throw LambdaRuntimeError.invocationHeadMissingFunctionARN
}

struct ErrorResponse: Hashable, Codable {
var errorType: String
var errorMessage: String
}
let traceID = headers.first(name: AmazonHeaders.traceID) ?? "Root=\(AmazonHeaders.generateXRayTraceID());Sampled=0"

extension ErrorResponse {
internal func toJSONBytes() -> [UInt8] {
var bytes = [UInt8]()
bytes.append(UInt8(ascii: "{"))
bytes.append(contentsOf: #""errorType":"#.utf8)
self.errorType.encodeAsJSONString(into: &bytes)
bytes.append(contentsOf: #","errorMessage":"#.utf8)
self.errorMessage.encodeAsJSONString(into: &bytes)
bytes.append(UInt8(ascii: "}"))
return bytes
self.init(
requestID: requestID,
deadlineInMillisSinceEpoch: unixTimeInMilliseconds,
invokedFunctionARN: invokedFunctionARN,
traceID: traceID,
clientContext: headers["Lambda-Runtime-Client-Context"].first,
cognitoIdentity: headers["Lambda-Runtime-Cognito-Identity"].first
)
}
}
}
Loading