From 18a8bb754a848f1be94892ccc6a313076909214c Mon Sep 17 00:00:00 2001 From: tom doron Date: Tue, 26 May 2020 21:37:24 -0700 Subject: [PATCH 1/2] add an option to start the local debugging server based on an env variable motivation: make using the local debugging server easier to turn off/on without the need to change code when oyu are preparing to deploy changes: * add code to lambda so that in debug mode only, if the LOCAL_SERVER_ENABLED env variable is set the local degging server is started * update example code --- .../MyLambda/Sources/MyLambda/main.swift | 10 +-- .../Lambda+LocalServer.swift | 5 +- Sources/AWSLambdaRuntimeCore/Lambda.swift | 65 ++++++++++++------- 3 files changed, 50 insertions(+), 30 deletions(-) diff --git a/Examples/LocalDebugging/MyLambda/Sources/MyLambda/main.swift b/Examples/LocalDebugging/MyLambda/Sources/MyLambda/main.swift index 801a9483..2a1e1eb9 100644 --- a/Examples/LocalDebugging/MyLambda/Sources/MyLambda/main.swift +++ b/Examples/LocalDebugging/MyLambda/Sources/MyLambda/main.swift @@ -15,9 +15,9 @@ import AWSLambdaRuntime import Shared -try Lambda.withLocalServer { - Lambda.run { (_: Lambda.Context, request: Request, callback: @escaping (Result) -> Void) in - // TODO: something useful - callback(.success(Response(message: "Hello, \(request.name)!"))) - } +// set LOCAL_SERVER_ENABLED env variable to "true" to start the local server simulator +// which will allow local debugging +Lambda.run { (_, request: Request, callback: @escaping (Result) -> Void) in + // TODO: something useful + callback(.success(Response(message: "Hello, \(request.name)!"))) } diff --git a/Sources/AWSLambdaRuntimeCore/Lambda+LocalServer.swift b/Sources/AWSLambdaRuntimeCore/Lambda+LocalServer.swift index e39c1179..af78080b 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda+LocalServer.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda+LocalServer.swift @@ -35,11 +35,12 @@ extension Lambda { /// - body: Code to run within the context of the mock server. Typically this would be a Lambda.run function call. /// /// - note: This API is designed stricly for local testing and is behind a DEBUG flag - public static func withLocalServer(invocationEndpoint: String? = nil, _ body: @escaping () -> Void) throws { + @discardableResult + public static func withLocalServer(invocationEndpoint: String? = nil, _ body: @escaping () -> Value) throws -> Value { let server = LocalLambda.Server(invocationEndpoint: invocationEndpoint) try server.start().wait() defer { try! server.stop() } // FIXME: - body() + return body() } } diff --git a/Sources/AWSLambdaRuntimeCore/Lambda.swift b/Sources/AWSLambdaRuntimeCore/Lambda.swift index 1170ce65..e76a2433 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda.swift @@ -97,36 +97,55 @@ public enum Lambda { // for testing and internal use @discardableResult internal static func run(configuration: Configuration = .init(), factory: @escaping HandlerFactory) -> Result { - Backtrace.install() - var logger = Logger(label: "Lambda") - logger.logLevel = configuration.general.logLevel + let _run = { (configuration: Configuration, factory: @escaping HandlerFactory) -> Result in + Backtrace.install() + var logger = Logger(label: "Lambda") + logger.logLevel = configuration.general.logLevel - var result: Result! - MultiThreadedEventLoopGroup.withCurrentThreadAsEventLoop { eventLoop in - let lifecycle = Lifecycle(eventLoop: eventLoop, logger: logger, configuration: configuration, factory: factory) - #if DEBUG - let signalSource = trap(signal: configuration.lifecycle.stopSignal) { signal in - logger.info("intercepted signal: \(signal)") - lifecycle.shutdown() - } - #endif - - lifecycle.start().flatMap { - lifecycle.shutdownFuture - }.whenComplete { lifecycleResult in + var result: Result! + MultiThreadedEventLoopGroup.withCurrentThreadAsEventLoop { eventLoop in + let lifecycle = Lifecycle(eventLoop: eventLoop, logger: logger, configuration: configuration, factory: factory) #if DEBUG - signalSource.cancel() + let signalSource = trap(signal: configuration.lifecycle.stopSignal) { signal in + logger.info("intercepted signal: \(signal)") + lifecycle.shutdown() + } #endif - eventLoop.shutdownGracefully { error in - if let error = error { - preconditionFailure("Failed to shutdown eventloop: \(error)") + + lifecycle.start().flatMap { + lifecycle.shutdownFuture + }.whenComplete { lifecycleResult in + #if DEBUG + signalSource.cancel() + #endif + eventLoop.shutdownGracefully { error in + if let error = error { + preconditionFailure("Failed to shutdown eventloop: \(error)") + } } + result = lifecycleResult } - result = lifecycleResult } + + logger.info("shutdown completed") + return result } - logger.info("shutdown completed") - return result + // start local server for debugging in DEBUG mode only + #if DEBUG + if Lambda.env("LOCAL_SERVER_ENABLED").flatMap(Bool.init) ?? false { + do { + return try Lambda.withLocalServer { + _run(configuration, factory) + } + } catch { + return .failure(error) + } + } else { + return _run(configuration, factory) + } + #else + return _run(configuration, factory) + #endif } } From 4115008663639aa3ccb38424357aff28482a57af Mon Sep 17 00:00:00 2001 From: tom doron Date: Mon, 1 Jun 2020 12:19:47 -0700 Subject: [PATCH 2/2] fixup --- .../MyLambda/Sources/MyLambda/main.swift | 4 ++-- Examples/LocalDebugging/README.md | 15 +++++++++++---- .../AWSLambdaRuntimeCore/Lambda+LocalServer.swift | 2 +- Sources/AWSLambdaRuntimeCore/Lambda.swift | 2 +- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Examples/LocalDebugging/MyLambda/Sources/MyLambda/main.swift b/Examples/LocalDebugging/MyLambda/Sources/MyLambda/main.swift index 2a1e1eb9..a7e52304 100644 --- a/Examples/LocalDebugging/MyLambda/Sources/MyLambda/main.swift +++ b/Examples/LocalDebugging/MyLambda/Sources/MyLambda/main.swift @@ -15,8 +15,8 @@ import AWSLambdaRuntime import Shared -// set LOCAL_SERVER_ENABLED env variable to "true" to start the local server simulator -// which will allow local debugging +// set LOCAL_LAMBDA_SERVER_ENABLED env variable to "true" to start +// a local server simulator which will allow local debugging Lambda.run { (_, request: Request, callback: @escaping (Result) -> Void) in // TODO: something useful callback(.success(Response(message: "Hello, \(request.name)!"))) diff --git a/Examples/LocalDebugging/README.md b/Examples/LocalDebugging/README.md index a4a66ce1..2b5b4fe4 100644 --- a/Examples/LocalDebugging/README.md +++ b/Examples/LocalDebugging/README.md @@ -11,18 +11,25 @@ The example includes three modules: 3. [Shared](Shared) is a SwiftPM library package used for shared code between the iOS application and the Lambda function, such as the Request and Response model objects. -The local debugging experience is achieved by running the Lambda function in the context of the debug only `Lambda.withLocalServer` -function which starts a local emulator enabling the communication +The local debugging experience is achieved by running the Lambda function in the context of the +debug-only local lambda engine simulator which starts a local HTTP server enabling the communication between the iOS application and the Lambda function over HTTP. To try out this example, open the workspace in Xcode and "run" the two targets, using the relevant `MyLambda` and `MyApp` Xcode schemas. -Start with running the `MyLambda` target on the "My Mac" destination, once it is up you should see a log message in the Xcode console saying +Start with running the `MyLambda` target. +* Switch to the `MyApp` scheme and select the "My Mac" destination +* Set the `LOCAL_LAMBDA_SERVER_ENABLED` environment variable to `true` by editing the `MyLambda` scheme under `Run`. +* Hit `Run` +* Once it is up you should see a log message in the Xcode console saying `LocalLambdaServer started and listening on 127.0.0.1:7000, receiving payloads on /invoke` which means the local emulator is up and receiving traffic on port `7000` and expecting payloads on the `/invoke` endpoint. -Continue to run the `MyApp` target in a simulator destination. Once up, the application's UI should appear in the simulator allowing you +Continue to run the `MyApp` target +* Switch to the `MyApp` scheme and select a simulator destination. +* Hit `Run` +* Once up, the application's UI should appear in the simulator allowing you to interact with it. Once both targets are running, set up breakpoints in the iOS application or Lambda function to observe the system behavior. diff --git a/Sources/AWSLambdaRuntimeCore/Lambda+LocalServer.swift b/Sources/AWSLambdaRuntimeCore/Lambda+LocalServer.swift index af78080b..148dcd5a 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda+LocalServer.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda+LocalServer.swift @@ -36,7 +36,7 @@ extension Lambda { /// /// - note: This API is designed stricly for local testing and is behind a DEBUG flag @discardableResult - public static func withLocalServer(invocationEndpoint: String? = nil, _ body: @escaping () -> Value) throws -> Value { + static func withLocalServer(invocationEndpoint: String? = nil, _ body: @escaping () -> Value) throws -> Value { let server = LocalLambda.Server(invocationEndpoint: invocationEndpoint) try server.start().wait() defer { try! server.stop() } // FIXME: diff --git a/Sources/AWSLambdaRuntimeCore/Lambda.swift b/Sources/AWSLambdaRuntimeCore/Lambda.swift index e76a2433..3f4abf34 100644 --- a/Sources/AWSLambdaRuntimeCore/Lambda.swift +++ b/Sources/AWSLambdaRuntimeCore/Lambda.swift @@ -133,7 +133,7 @@ public enum Lambda { // start local server for debugging in DEBUG mode only #if DEBUG - if Lambda.env("LOCAL_SERVER_ENABLED").flatMap(Bool.init) ?? false { + if Lambda.env("LOCAL_LAMBDA_SERVER_ENABLED").flatMap(Bool.init) ?? false { do { return try Lambda.withLocalServer { _run(configuration, factory)