diff --git a/Examples/LocalDebugging/MyLambda/Sources/MyLambda/main.swift b/Examples/LocalDebugging/MyLambda/Sources/MyLambda/main.swift index 801a9483..a7e52304 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_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 e39c1179..148dcd5a 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 + 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..3f4abf34 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_LAMBDA_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 } }