Skip to content

Commit ad06594

Browse files
committed
Adapt SCF CustomRuntime API alpha
1 parent 2a463cf commit ad06594

25 files changed

+389
-424
lines changed

Examples/LocalDebugging/MyApp/MyApp/ContentView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ struct ContentView: View {
5252
}
5353

5454
func register() {
55-
guard let url = URL(string: "http://localhost:7000/invoke") else {
55+
guard let url = URL(string: "http://localhost:9001/invoke") else {
5656
fatalError("invalid url")
5757
}
5858
var request = URLRequest(url: url)

Examples/LocalDebugging/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ Start with running the `MyCloudFunction` target.
2323
* Set the `LOCAL_SCF_SERVER_ENABLED` environment variable to `true` by editing the `MyCloudFunction` scheme Run/Arguments options.
2424
* Hit `Run`
2525
* Once it is up you should see a log message in the Xcode console saying
26-
`LocalSCFServer started and listening on 127.0.0.1:7000, receiving events on /invoke`
27-
which means the local emulator is up and receiving traffic on port `7000` and expecting events on the `/invoke` endpoint.
26+
`LocalSCFServer started and listening on 127.0.0.1:9001, receiving events on /invoke`
27+
which means the local emulator is up and receiving traffic on port `9001` and expecting events on the `/invoke` endpoint.
2828

2929
Continue to run the `MyApp` target
3030
* Switch to the `MyApp` scheme and select a simulator destination.

Sources/MockServer/main.swift

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,19 @@ internal struct MockServer {
3333
private let group: EventLoopGroup
3434
private let host: String
3535
private let port: Int
36-
private let mode: Mode
3736

3837
public init() {
3938
self.group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
40-
self.host = env("HOST") ?? "127.0.0.1"
41-
self.port = env("PORT").flatMap(Int.init) ?? 7000
42-
self.mode = env("MODE").flatMap(Mode.init) ?? .string
39+
self.host = env("SCF_RUNTIME_API") ?? "127.0.0.1"
40+
self.port = env("SCF_RUNTIME_API_PORT").flatMap(Int.init) ?? 9001
4341
}
4442

4543
func start() throws {
4644
let bootstrap = ServerBootstrap(group: group)
4745
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
4846
.childChannelInitializer { channel in
4947
channel.pipeline.configureHTTPServerPipeline(withErrorHandling: true).flatMap { _ in
50-
channel.pipeline.addHandler(HTTPHandler(mode: self.mode))
48+
channel.pipeline.addHandler(HTTPHandler())
5149
}
5250
}
5351
try bootstrap.bind(host: self.host, port: self.port).flatMap { channel -> EventLoopFuture<Void> in
@@ -64,14 +62,8 @@ internal final class HTTPHandler: ChannelInboundHandler {
6462
public typealias InboundIn = HTTPServerRequestPart
6563
public typealias OutboundOut = HTTPServerResponsePart
6664

67-
private let mode: Mode
68-
6965
private var pending = CircularBuffer<(head: HTTPRequestHead, body: ByteBuffer?)>()
7066

71-
public init(mode: Mode) {
72-
self.mode = mode
73-
}
74-
7567
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
7668
let requestPart = unwrapInboundIn(data)
7769

@@ -98,23 +90,16 @@ internal final class HTTPHandler: ChannelInboundHandler {
9890
var responseHeaders: [(String, String)]?
9991

10092
if request.head.uri.hasSuffix("/next") {
101-
let requestId = UUID().uuidString
93+
let requestId = UUID().uuidString.lowercased()
10294
responseStatus = .ok
103-
switch self.mode {
104-
case .string:
105-
responseBody = requestId
106-
case .json:
107-
responseBody = "{ \"body\": \"\(requestId)\" }"
108-
}
109-
let deadline = Int64(Date(timeIntervalSinceNow: 60).timeIntervalSince1970 * 1000)
95+
responseBody = "{ \"body\": \"\(requestId)\" }"
11096
responseHeaders = [
111-
(AmazonHeaders.requestID, requestId),
112-
(AmazonHeaders.invokedFunctionARN, "arn:aws:lambda:us-east-1:123456789012:function:custom-runtime"),
113-
(AmazonHeaders.traceID, "Root=1-5bef4de7-ad49b0e87f6ef6c87fc2e700;Parent=9a9197af755a6419;Sampled=1"),
114-
(AmazonHeaders.deadline, String(deadline)),
97+
(SCFHeaders.requestID, requestId),
98+
(SCFHeaders.memoryLimit, "128"),
99+
(SCFHeaders.timeLimit, "3000"),
115100
]
116101
} else if request.head.uri.hasSuffix("/response") {
117-
responseStatus = .accepted
102+
responseStatus = .ok
118103
} else {
119104
responseStatus = .notFound
120105
}
@@ -151,13 +136,10 @@ internal enum ServerError: Error {
151136
case cantBind
152137
}
153138

154-
internal enum AmazonHeaders {
155-
static let requestID = "Lambda-Runtime-Aws-Request-Id"
156-
static let traceID = "Lambda-Runtime-Trace-Id"
157-
static let clientContext = "X-Amz-Client-Context"
158-
static let cognitoIdentity = "X-Amz-Cognito-Identity"
159-
static let deadline = "Lambda-Runtime-Deadline-Ms"
160-
static let invokedFunctionARN = "Lambda-Runtime-Invoked-Function-Arn"
139+
internal enum SCFHeaders {
140+
static let requestID = "request_id"
141+
static let memoryLimit = "memory_limit_in_mb"
142+
static let timeLimit = "time_limit_in_ms"
161143
}
162144

163145
internal enum Mode: String {

Sources/TencentSCFEvents/TencentCloud.swift renamed to Sources/TencentSCFEvents/TencentCloud/Region.swift

Lines changed: 1 addition & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@
1212
//
1313
//===------------------------------------------------------------------------------------===//
1414

15-
// list all available regions and zones using tccli:
15+
// list all available regions using tccli:
1616
// $ tccli cvm DescribeRegions
17-
// $ tccli cvm DescribeZones --region <Region>
1817

1918
/// Enumeration of the Tencent Cloud regions and zones.
2019
public enum TencentCloud {
@@ -105,40 +104,6 @@ public enum TencentCloud {
105104
public static var na_siliconvalley: Self { Region(rawValue: "na-siliconvalley")! } // California, US
106105
public static var na_toronto: Self { Region(rawValue: "na-toronto")! } // Canada
107106
}
108-
109-
public struct Zone: RawRepresentable, CustomStringConvertible, Equatable, Hashable {
110-
public typealias RawValue = String
111-
112-
public var rawValue: String {
113-
"\(self.region)-\(self.number)"
114-
}
115-
116-
public var description: String {
117-
self.rawValue
118-
}
119-
120-
public init?(rawValue: String) {
121-
guard let (region, number) = Self.parse(rawValue: rawValue) else {
122-
return nil
123-
}
124-
self.region = region
125-
self.number = number
126-
}
127-
128-
private static func parse(rawValue: String) -> (Region, UInt8)? {
129-
let components = rawValue.split(separator: "-")
130-
guard components.count > 2, let number = UInt8(components.last!) else {
131-
return nil
132-
}
133-
134-
let region = Region(rawValue: components.dropLast().joined(separator: "-"))!
135-
return (region, number)
136-
}
137-
138-
public let region: Region
139-
140-
public let number: UInt8
141-
}
142107
}
143108

144109
extension TencentCloud.Region: Codable {
@@ -153,21 +118,3 @@ extension TencentCloud.Region: Codable {
153118
try container.encode(self.rawValue)
154119
}
155120
}
156-
157-
extension TencentCloud.Zone: Codable {
158-
public init(from decoder: Decoder) throws {
159-
let container = try decoder.singleValueContainer()
160-
let zone = try container.decode(String.self)
161-
guard let (region, number) = Self.parse(rawValue: zone) else {
162-
throw DecodingError.dataCorruptedError(in: container, debugDescription:
163-
"Expected zone format like `ap-shanghai-3` or `ap-shenzhen-fsi-1`, but `\(zone)` does not forfill format")
164-
}
165-
self.region = region
166-
self.number = number
167-
}
168-
169-
public func encode(to encoder: Encoder) throws {
170-
var container = encoder.singleValueContainer()
171-
try container.encode(self.rawValue)
172-
}
173-
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//===------------------------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftTencentSCFRuntime open source project
4+
//
5+
// Copyright (c) 2020 stevapple and the SwiftTencentSCFRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftTencentSCFRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===------------------------------------------------------------------------------------===//
14+
15+
// list all available zones using tccli:
16+
// $ tccli cvm DescribeZones --region <Region>
17+
18+
/// Enumeration of the Tencent Cloud regions and zones.
19+
extension TencentCloud {
20+
public struct Zone: RawRepresentable, CustomStringConvertible, Equatable, Hashable {
21+
public typealias RawValue = String
22+
23+
public var rawValue: String {
24+
"\(self.region)-\(self.number)"
25+
}
26+
27+
public var description: String {
28+
self.rawValue
29+
}
30+
31+
public init?(rawValue: String) {
32+
guard let (region, number) = Self.parse(rawValue: rawValue) else {
33+
return nil
34+
}
35+
self.region = region
36+
self.number = number
37+
}
38+
39+
private static func parse(rawValue: String) -> (Region, UInt8)? {
40+
let components = rawValue.split(separator: "-")
41+
guard components.count > 2, let number = UInt8(components.last!) else {
42+
return nil
43+
}
44+
45+
let region = Region(rawValue: components.dropLast().joined(separator: "-"))!
46+
return (region, number)
47+
}
48+
49+
public let region: Region
50+
51+
public let number: UInt8
52+
}
53+
}
54+
55+
extension TencentCloud.Zone: Codable {
56+
public init(from decoder: Decoder) throws {
57+
let container = try decoder.singleValueContainer()
58+
let zone = try container.decode(String.self)
59+
guard let (region, number) = Self.parse(rawValue: zone) else {
60+
throw DecodingError.dataCorruptedError(in: container, debugDescription:
61+
"Expected zone format like `ap-shanghai-3` or `ap-shenzhen-fsi-1`, but `\(zone)` does not forfill format")
62+
}
63+
self.region = region
64+
self.number = number
65+
}
66+
67+
public func encode(to encoder: Encoder) throws {
68+
var container = encoder.singleValueContainer()
69+
try container.encode(self.rawValue)
70+
}
71+
}

Sources/TencentSCFRuntimeCore/Lambda+LocalServer.swift

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
#if DEBUG
2929
import Dispatch
30+
import struct Foundation.UUID
3031
import Logging
3132
import NIO
3233
import NIOConcurrencyHelpers
@@ -144,7 +145,7 @@ private enum LocalLambda {
144145
guard let work = request.body else {
145146
return self.writeResponse(context: context, response: .init(status: .badRequest))
146147
}
147-
let requestID = "\(DispatchTime.now().uptimeNanoseconds)" // FIXME:
148+
let requestID = UUID().uuidString.lowercased()
148149
let promise = context.eventLoop.makePromise(of: Response.self)
149150
promise.futureResult.whenComplete { result in
150151
switch result {
@@ -164,7 +165,7 @@ private enum LocalLambda {
164165
}
165166

166167
// /next endpoint is called by the lambda polling for work
167-
case (.GET, let url) where url.hasSuffix(Consts.getNextInvocationURLSuffix):
168+
case (.GET, let url) where url == Consts.getNextInvocationURL:
168169
// check if our server is in the correct state
169170
guard case .waitingForLambdaRequest = Self.invocationState else {
170171
self.logger.error("invalid invocation state \(Self.invocationState)")
@@ -195,48 +196,28 @@ private enum LocalLambda {
195196
self.writeResponse(context: context, response: invocation.makeResponse())
196197
}
197198

198-
// :requestID/response endpoint is called by the lambda posting the response
199-
case (.POST, let url) where url.hasSuffix(Consts.postResponseURLSuffix):
200-
let parts = request.head.uri.split(separator: "/")
201-
guard let requestID = parts.count > 2 ? String(parts[parts.count - 2]) : nil else {
202-
// the request is malformed, since we were expecting a requestId in the path
203-
return self.writeResponse(context: context, status: .badRequest)
204-
}
199+
// response endpoint is called by the lambda posting the response
200+
case (.POST, let url) where url == Consts.postResponseURL:
205201
guard case .waitingForLambdaResponse(let invocation) = Self.invocationState else {
206202
// a response was send, but we did not expect to receive one
207203
self.logger.error("invalid invocation state \(Self.invocationState)")
208204
return self.writeResponse(context: context, status: .unprocessableEntity)
209205
}
210-
guard requestID == invocation.requestID else {
211-
// the request's requestId is not matching the one we are expecting
212-
self.logger.error("invalid invocation state request ID \(requestID) does not match expected \(invocation.requestID)")
213-
return self.writeResponse(context: context, status: .badRequest)
214-
}
215206

216207
invocation.responsePromise.succeed(.init(status: .ok, body: request.body))
217-
self.writeResponse(context: context, status: .accepted)
208+
self.writeResponse(context: context, status: .ok)
218209
Self.invocationState = .waitingForLambdaRequest
219210

220-
// :requestID/error endpoint is called by the lambda posting an error response
221-
case (.POST, let url) where url.hasSuffix(Consts.postErrorURLSuffix):
222-
let parts = request.head.uri.split(separator: "/")
223-
guard let requestID = parts.count > 2 ? String(parts[parts.count - 2]) : nil else {
224-
// the request is malformed, since we were expecting a requestId in the path
225-
return self.writeResponse(context: context, status: .badRequest)
226-
}
211+
// error endpoint is called by the lambda posting an error response
212+
case (.POST, let url) where url == Consts.postErrorURL:
227213
guard case .waitingForLambdaResponse(let invocation) = Self.invocationState else {
228214
// a response was send, but we did not expect to receive one
229215
self.logger.error("invalid invocation state \(Self.invocationState)")
230216
return self.writeResponse(context: context, status: .unprocessableEntity)
231217
}
232-
guard requestID == invocation.requestID else {
233-
// the request's requestId is not matching the one we are expecting
234-
self.logger.error("invalid invocation state request ID \(requestID) does not match expected \(invocation.requestID)")
235-
return self.writeResponse(context: context, status: .badRequest)
236-
}
237218

238219
invocation.responsePromise.succeed(.init(status: .internalServerError, body: request.body))
239-
self.writeResponse(context: context, status: .accepted)
220+
self.writeResponse(context: context, status: .ok)
240221
Self.invocationState = .waitingForLambdaRequest
241222

242223
// unknown call
@@ -287,10 +268,9 @@ private enum LocalLambda {
287268
response.body = self.request
288269
// required headers
289270
response.headers = [
290-
(AmazonHeaders.requestID, self.requestID),
291-
(AmazonHeaders.invokedFunctionARN, "arn:aws:lambda:us-east-1:\(Int16.random(in: Int16.min ... Int16.max)):function:custom-runtime"),
292-
(AmazonHeaders.traceID, "Root=\(AmazonHeaders.generateXRayTraceID());Sampled=1"),
293-
(AmazonHeaders.deadline, "\(DispatchWallTime.distantFuture.millisSinceEpoch)"),
271+
(SCFHeaders.requestID, self.requestID),
272+
(SCFHeaders.timeLimit, "3000"),
273+
(SCFHeaders.memoryLimit, "128"),
294274
]
295275
return response
296276
}

Sources/TencentSCFRuntimeCore/LambdaConfiguration.swift

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,16 @@ extension Lambda {
8181
let requestTimeout: TimeAmount?
8282

8383
init(address: String? = nil, keepAlive: Bool? = nil, requestTimeout: TimeAmount? = nil) {
84-
let ipPort = (address ?? env("TENCENT_SCF_RUNTIME_API"))?.split(separator: ":") ?? ["127.0.0.1", "7000"]
85-
guard ipPort.count == 2, let port = Int(ipPort[1]) else {
86-
preconditionFailure("invalid ip+port configuration \(ipPort)")
84+
if let ipPort = address?.split(separator: ":") {
85+
guard ipPort.count == 2, let port = Int(ipPort[1]) else {
86+
preconditionFailure("invalid ip+port configuration \(ipPort)")
87+
}
88+
self.ip = String(ipPort[0])
89+
self.port = port
90+
} else {
91+
self.ip = env("SCF_RUNTIME_API") ?? "127.0.0.1"
92+
self.port = env("SCF_RUNTIME_API_PORT").flatMap(Int.init) ?? 9001
8793
}
88-
self.ip = String(ipPort[0])
89-
self.port = port
9094
self.keepAlive = keepAlive ?? env("KEEP_ALIVE").flatMap(Bool.init) ?? true
9195
self.requestTimeout = requestTimeout ?? env("REQUEST_TIMEOUT").flatMap(Int64.init).flatMap { .milliseconds($0) }
9296
}

0 commit comments

Comments
 (0)