Skip to content
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
6 changes: 6 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Note: You can use any Debian/Ubuntu based image you want.
FROM swift:6.2.0

# [Optional] Uncomment this section to install additional OS packages.
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends make
45 changes: 45 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-outside-of-docker-compose
{
"name": "Docker from Docker Compose",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",

// Use this environment variable if you need to bind mount your local source code into a new container.
"remoteEnv": {
"LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}"
},

"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {
"version": "latest",
"enableNonRootDocker": "true",
"moby": "true"
},
"ghcr.io/devcontainers/features/aws-cli:1": {}
},

// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
"lldb.library": "/usr/lib/liblldb.so"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"swiftlang.swift-vscode"
]
}
}
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],

// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "docker --version",

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
35 changes: 35 additions & 0 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
version: '3'

services:
app:
build:
context: .
dockerfile: Dockerfile

volumes:
# Forwards the local Docker socket to the container.
- /var/run/docker.sock:/var/run/docker-host.sock
# Update this to wherever you want VS Code to mount the folder of your project
- ../..:/workspaces:cached

# Overrides default command so things don't shut down after the process ends.
entrypoint: /usr/local/share/docker-init.sh
depends_on:
- localstack
environment:
- LOCALSTACK_ENDPOINT=http://localstack:4566
- AWS_ACCESS_KEY_ID=test
- AWS_SECRET_ACCESS_KEY=test
- AWS_REGION=us-east-1
command: sleep infinity

# Uncomment the next four lines if you will use a ptrace-based debuggers like C++, Go, and Rust.
cap_add:
- SYS_PTRACE
security_opt:
- seccomp:unconfined

# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)
localstack:
image: localstack/localstack
12 changes: 12 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for more information:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://containers.dev/guide/dependabot

version: 2
updates:
- package-ecosystem: "devcontainers"
directory: "/"
schedule:
interval: weekly
3 changes: 3 additions & 0 deletions .sourcekit-lsp/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"$schema": "https://raw.githubusercontent.com/swiftlang/sourcekit-lsp/refs/heads/release/6.2/config.schema.json"
}
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ coverage:
--instr-profile=$(SWIFT_BIN_PATH)/codecov/default.profdata \
--format=lcov > $(GITHUB_WORKSPACE)/lcov.info

local_invoke_demo_app:
curl -X POST 127.0.0.1:7000/invoke -H "Content-Type: application/json" -d @Tests/BreezeLambdaWebHookTests/Fixtures/get_webhook_api_gtw.json

preview_docc_lambda_api:
swift package --disable-sandbox preview-documentation --target BreezeLambdaWebHook

Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@ struct DemoLambdaHandler: BreezeLambdaWebHookHandler, Sendable {
self.handlerContext = handlerContext
}

var httpClient: HTTPClient {
handlerContext.httpClient
}

func handle(_ event: APIGatewayV2Request, context: LambdaContext) async throws -> APIGatewayV2Response {
context.logger.info("Received event: \(event)")
let request = HTTPClientRequest(url: "https://example.com")
let response = try await handlerContext.httpClient.execute(request, timeout: .seconds(5))
let response = try await httpClient.execute(request, timeout: .seconds(5))
let bytes = try await response.body.collect(upTo: 1024 * 1024) // 1 MB Buffer
let body = String(buffer: bytes)
context.logger.info("Response body: \(body)")
Expand Down
18 changes: 10 additions & 8 deletions Sources/BreezeLambdaWebHook/BreezeLambdaWebHook.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,20 @@ public struct BreezeLambdaWebHook<LambdaHandler: BreezeLambdaWebHookHandler>: Se
/// handling any errors that may occur during the process.
/// It gracefully shuts down the service on termination signals.
public func run() async throws {
let handlerContext = HandlerContext(config: config)
let lambdaHandler = LambdaHandler(handlerContext: handlerContext)
let runtime = LambdaRuntime(body: lambdaHandler.handle)

let serviceGroup = ServiceGroup(
services: [handlerContext, runtime],
gracefulShutdownSignals: [.sigterm, .sigint],
logger: config.logger
)
do {
let lambdaService = BreezeLambdaWebHookService<LambdaHandler>(
config: config
)
let serviceGroup = ServiceGroup(
services: [lambdaService],
gracefulShutdownSignals: [.sigterm, .sigint],
logger: config.logger
)
config.logger.error("Starting \(name) ...")
try await serviceGroup.run()
} catch {
try? handlerContext.syncShutdown()
config.logger.error("Error running \(name): \(error.localizedDescription)")
}
}
Expand Down
4 changes: 0 additions & 4 deletions Sources/BreezeLambdaWebHook/BreezeLambdaWebHookHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,4 @@ public extension BreezeLambdaWebHookHandler {
var handler: String? {
Lambda.env("_HANDLER")
}

var httpClient: AsyncHTTPClient.HTTPClient {
handlerContext.httpClient
}
}
108 changes: 0 additions & 108 deletions Sources/BreezeLambdaWebHook/BreezeLambdaWebHookService.swift

This file was deleted.

77 changes: 77 additions & 0 deletions Sources/BreezeLambdaWebHook/HandlerContext.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright 2023 (c) Andrea Scuderi - https://github.com/swift-serverless
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import AsyncHTTPClient
import AWSLambdaEvents
import AWSLambdaRuntime
#if canImport(FoundationEssentials)
import FoundationEssentials
#else
import Foundation
#endif
import ServiceLifecycle
import Logging

///
/// `HandlerContext` provides a context for Lambda handlers, encapsulating an HTTP client and configuration.
///
/// This struct is responsible for managing the lifecycle of the HTTP client used for outbound requests,
/// including graceful and synchronous shutdown procedures. It also provides logging for lifecycle events.
///
/// - Parameters:
/// - httpClient: The HTTP client used for making outbound HTTP requests.
/// - config: The configuration for the HTTP client, including timeout and logger.
///
/// - Conforms to: `Service`
public struct HandlerContext: Service {
/// The HTTP client used for outbound requests.
public let httpClient: HTTPClient
/// The configuration for the HTTP client.
private let config: BreezeHTTPClientConfig

/// Initializes a new `HandlerContext` with the provided configuration.
/// - Parameter config: The configuration for the HTTP client.
public init(config: BreezeHTTPClientConfig) {
let timeout = HTTPClient.Configuration.Timeout(
connect: config.timeout,
read: config.timeout
)
let configuration = HTTPClient.Configuration(timeout: timeout)
self.httpClient = HTTPClient(
eventLoopGroupProvider: .singleton,
configuration: configuration
)
self.config = config
}

/// Runs the `HandlerContext` and waits for a graceful shutdown.
public func run() async throws {
config.logger.info("BreezeHTTPClientProvider started")
try await gracefulShutdown()
config.logger.info("BreezeHTTPClientProvider is gracefully shutting down ...")
try await onGracefulShutdown()
}

/// Handles graceful shutdown of the HTTP client.
public func onGracefulShutdown() async throws {
try await httpClient.shutdown()
config.logger.info("BreezeHTTPClientProvider: HTTPClient shutdown is completed.")
}

/// Synchronously shuts down the HTTP client.
public func syncShutdown() throws {
try httpClient.syncShutdown()
config.logger.info("BreezeHTTPClientProvider: HTTPClient syncShutdown is completed.")
}
}
Loading