mirror of
https://github.com/swift-server/swift-aws-lambda-runtime.git
synced 2026-05-03 07:22:27 +00:00
add support for LOCAL_LAMBDA_PORT / HOST(#557)
Allows users to define on which port the Local server listens to, using the `LOCAL_LAMBDA_PORT` environment variable. While being at it, I also added `LOCAL_LAMBDA_HOST` if the user wants to bind on a specific IP address. I renamed `LOCAL_LAMBDA_SERVER_INVOCATION_ENDPOINT` to `LOCAL_LAMBDA_INVOCATION_ENDPOINT` for consistency. ### Motivation: Addresses https://github.com/swift-server/swift-aws-lambda-runtime/issues/556 ### Modifications: - When run outside of the Lambda execution environment, check for the value of `LOCAL_LAMBDA_PORT` and passes it down to the Lambda HTTP Local Server and runtime client. - Add a unit test ### Result: ``` LAMBDA_USE_LOCAL_DEPS=../.. LOCAL_LAMBDA_PORT=8888 swift run 2025-09-01T21:55:22+0200 info LambdaRuntime: host="127.0.0.1" port=8888 [AWSLambdaRuntime] Server started and listening ```
This commit is contained in:
committed by
GitHub
parent
d42ae6975e
commit
d8ee71fc09
@@ -21,7 +21,7 @@ import NIOHTTP1
|
||||
import NIOPosix
|
||||
import Synchronization
|
||||
|
||||
// This functionality is designed for local testing hence being a #if DEBUG flag.
|
||||
// This functionality is designed for local testing when the LocalServerSupport trait is enabled.
|
||||
|
||||
// For example:
|
||||
// try Lambda.withLocalServer {
|
||||
@@ -42,18 +42,24 @@ extension Lambda {
|
||||
/// Execute code in the context of a mock Lambda server.
|
||||
///
|
||||
/// - parameters:
|
||||
/// - host: the hostname or IP address to listen on
|
||||
/// - port: the TCP port to listen to
|
||||
/// - invocationEndpoint: The endpoint to post events to.
|
||||
/// - 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 strictly for local testing and is behind a DEBUG flag
|
||||
/// - note: This API is designed strictly for local testing when the LocalServerSupport trait is enabled.
|
||||
@usableFromInline
|
||||
static func withLocalServer(
|
||||
host: String,
|
||||
port: Int,
|
||||
invocationEndpoint: String? = nil,
|
||||
logger: Logger,
|
||||
_ body: sending @escaping () async throws -> Void
|
||||
) async throws {
|
||||
do {
|
||||
try await LambdaHTTPServer.withLocalServer(
|
||||
host: host,
|
||||
port: port,
|
||||
invocationEndpoint: invocationEndpoint,
|
||||
logger: logger
|
||||
) {
|
||||
@@ -112,9 +118,9 @@ internal struct LambdaHTTPServer {
|
||||
}
|
||||
|
||||
static func withLocalServer<Result: Sendable>(
|
||||
host: String,
|
||||
port: Int,
|
||||
invocationEndpoint: String?,
|
||||
host: String = "127.0.0.1",
|
||||
port: Int = 7000,
|
||||
eventLoopGroup: MultiThreadedEventLoopGroup = .singleton,
|
||||
logger: Logger,
|
||||
_ closure: sending @escaping () async throws -> Result
|
||||
|
||||
@@ -124,15 +124,23 @@ public final class LambdaRuntime<Handler>: Sendable where Handler: StreamingLamb
|
||||
} else {
|
||||
|
||||
#if LocalServerSupport
|
||||
|
||||
// we're not running on Lambda and we're compiled in DEBUG mode,
|
||||
// let's start a local server for testing
|
||||
|
||||
let host = Lambda.env("LOCAL_LAMBDA_HOST") ?? "127.0.0.1"
|
||||
let port = Lambda.env("LOCAL_LAMBDA_PORT").flatMap(Int.init) ?? 7000
|
||||
let endpoint = Lambda.env("LOCAL_LAMBDA_INVOCATION_ENDPOINT")
|
||||
|
||||
try await Lambda.withLocalServer(
|
||||
invocationEndpoint: Lambda.env("LOCAL_LAMBDA_SERVER_INVOCATION_ENDPOINT"),
|
||||
host: host,
|
||||
port: port,
|
||||
invocationEndpoint: endpoint,
|
||||
logger: self.logger
|
||||
) {
|
||||
|
||||
try await LambdaRuntimeClient.withRuntimeClient(
|
||||
configuration: .init(ip: "127.0.0.1", port: 7000),
|
||||
configuration: .init(ip: host, port: port),
|
||||
eventLoop: self.eventLoop,
|
||||
logger: self.logger
|
||||
) { runtimeClient in
|
||||
@@ -144,7 +152,7 @@ public final class LambdaRuntime<Handler>: Sendable where Handler: StreamingLamb
|
||||
}
|
||||
}
|
||||
#else
|
||||
// in release mode, we can't start a local server because the local server code is not compiled.
|
||||
// When the LocalServerSupport trait is disabled, we can't start a local server because the local server code is not compiled.
|
||||
throw LambdaRuntimeError(code: .missingLambdaRuntimeAPIEnvironmentVariable)
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the SwiftAWSLambdaRuntime open source project
|
||||
//
|
||||
// Copyright (c) 2025 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import Logging
|
||||
import NIOCore
|
||||
import NIOPosix
|
||||
import Testing
|
||||
|
||||
@testable import AWSLambdaRuntime
|
||||
|
||||
extension LambdaRuntimeTests {
|
||||
|
||||
@Test("Local server respects LOCAL_LAMBDA_PORT environment variable")
|
||||
func testLocalServerCustomPort() async throws {
|
||||
let customPort = 8080
|
||||
|
||||
// Set environment variable
|
||||
setenv("LOCAL_LAMBDA_PORT", "\(customPort)", 1)
|
||||
defer { unsetenv("LOCAL_LAMBDA_PORT") }
|
||||
|
||||
let result = try? await withThrowingTaskGroup(of: Bool.self) { group in
|
||||
|
||||
// start a local lambda + local server on custom port
|
||||
group.addTask {
|
||||
// Create a simple handler
|
||||
struct TestHandler: StreamingLambdaHandler {
|
||||
func handle(
|
||||
_ event: ByteBuffer,
|
||||
responseWriter: some LambdaResponseStreamWriter,
|
||||
context: LambdaContext
|
||||
) async throws {
|
||||
try await responseWriter.write(ByteBuffer(string: "test"))
|
||||
try await responseWriter.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// create the Lambda Runtime
|
||||
let runtime = LambdaRuntime(
|
||||
handler: TestHandler(),
|
||||
logger: Logger(label: "test", factory: { _ in SwiftLogNoOpLogHandler() })
|
||||
)
|
||||
|
||||
// Start runtime
|
||||
try await runtime._run()
|
||||
|
||||
// we reach this line when the group is cancelled
|
||||
return false
|
||||
}
|
||||
|
||||
// start a client to check if something responds on the custom port
|
||||
group.addTask {
|
||||
// Give server time to start
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Verify server is listening on custom port
|
||||
return try await isPortResponding(host: "127.0.0.1", port: customPort)
|
||||
}
|
||||
|
||||
let first = try await group.next()
|
||||
group.cancelAll()
|
||||
return first ?? false
|
||||
|
||||
}
|
||||
|
||||
#expect(result == true)
|
||||
}
|
||||
|
||||
private func isPortResponding(host: String, port: Int) async throws -> Bool {
|
||||
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
||||
|
||||
let bootstrap = ClientBootstrap(group: group)
|
||||
|
||||
do {
|
||||
let channel = try await bootstrap.connect(host: host, port: port).get()
|
||||
try await channel.close().get()
|
||||
try await group.shutdownGracefully()
|
||||
return true
|
||||
} catch {
|
||||
try await group.shutdownGracefully()
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,7 @@ import ServiceLifecycle
|
||||
import Testing
|
||||
import Logging
|
||||
|
||||
@Suite
|
||||
struct LambdaRuntimeServiceLifecycleTests {
|
||||
extension LambdaRuntimeTests {
|
||||
@Test
|
||||
@available(LambdaSwift 2.0, *)
|
||||
func testLambdaRuntimeGracefulShutdown() async throws {
|
||||
|
||||
@@ -20,7 +20,7 @@ import Testing
|
||||
|
||||
@testable import AWSLambdaRuntime
|
||||
|
||||
@Suite("LambdaRuntimeTests")
|
||||
@Suite(.serialized)
|
||||
struct LambdaRuntimeTests {
|
||||
|
||||
@Test("LambdaRuntime can only be run once")
|
||||
|
||||
@@ -60,7 +60,7 @@ final class MockLambdaServer<Behavior: LambdaServerBehavior> {
|
||||
init(
|
||||
behavior: Behavior,
|
||||
host: String = "127.0.0.1",
|
||||
port: Int = 7000,
|
||||
port: Int = 0,
|
||||
keepAlive: Bool = true,
|
||||
eventLoopGroup: MultiThreadedEventLoopGroup
|
||||
) {
|
||||
|
||||
@@ -464,16 +464,22 @@ curl -v --header "Content-Type:\ application/json" --data @events/create-session
|
||||
* Connection #0 to host 127.0.0.1 left intact
|
||||
{"statusCode":200,"isBase64Encoded":false,"body":"...","headers":{"Access-Control-Allow-Origin":"*","Content-Type":"application\/json; charset=utf-8","Access-Control-Allow-Headers":"*"}}
|
||||
```
|
||||
### Modifying the local endpoint
|
||||
### Modifying the local server URI
|
||||
|
||||
By default, when using the local Lambda server, it listens on the `/invoke` endpoint.
|
||||
By default, when using the local Lambda server during your tests, it listens on `http://127.0.0.1:7000/invoke`.
|
||||
|
||||
Some testing tools, such as the [AWS Lambda runtime interface emulator](https://docs.aws.amazon.com/lambda/latest/dg/images-test.html), require a different endpoint. In that case, you can use the `LOCAL_LAMBDA_SERVER_INVOCATION_ENDPOINT` environment variable to force the runtime to listen on a different endpoint.
|
||||
Some testing tools, such as the [AWS Lambda runtime interface emulator](https://docs.aws.amazon.com/lambda/latest/dg/images-test.html), require a different endpoint, the port might be used, or you may want to bind a specific IP address.
|
||||
|
||||
In these cases, you can use three environment variables to control the local server:
|
||||
|
||||
- Set `LOCAL_LAMBDA_HOST` to configure the local server to listen on a different TCP address.
|
||||
- Set `LOCAL_LAMBDA_PORT` to configure the local server to listen on a different TCP port.
|
||||
- Set `LOCAL_LAMBDA_INVOCATION_ENDPOINT` to force the local server to listen on a different endpoint.
|
||||
|
||||
Example:
|
||||
|
||||
```sh
|
||||
LOCAL_LAMBDA_SERVER_INVOCATION_ENDPOINT=/2015-03-31/functions/function/invocations swift run
|
||||
LOCAL_LAMBDA_PORT=8080 LOCAL_LAMBDA_INVOCATION_ENDPOINT=/2015-03-31/functions/function/invocations swift run
|
||||
```
|
||||
|
||||
## Deploying your Swift Lambda functions
|
||||
|
||||
Reference in New Issue
Block a user