Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 7 additions & 0 deletions .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.13.0")),
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", .upToNextMajor(from: "0.3.0")),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be changed back

.package(url: "https://github.com/skelpo/swift-aws-lambda-runtime.git", .upToNextMajor(from: "0.4.0")),
.package(url: "https://github.com/vapor/vapor.git", .upToNextMajor(from: "4.0.0")),
.package(url: "https://github.com/swift-extras/swift-extras-base64", .upToNextMajor(from: "0.4.0")),
],
Expand Down
152 changes: 152 additions & 0 deletions Sources/VaporAWSLambdaRuntime/ALB.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
//
// File.swift
//
//
// Created by Ralph Küpper on 1/5/21.
//

import AWSLambdaEvents
import AWSLambdaRuntimeCore
import ExtrasBase64
import NIO
import NIOHTTP1
import Vapor

// MARK: - Handler -

struct ALBHandler: EventLoopLambdaHandler {

typealias In = ALB.TargetGroupRequest
typealias Out = ALB.TargetGroupResponse

private let application: Application
private let responder: Responder

init(application: Application, responder: Responder) {
self.application = application
self.responder = responder
}

public func handle(context: Lambda.Context, event: ALB.TargetGroupRequest)
-> EventLoopFuture<ALB.TargetGroupResponse>
{
let vaporRequest: Vapor.Request
do {
vaporRequest = try Vapor.Request(req: event, in: context, for: self.application)
} catch {
return context.eventLoop.makeFailedFuture(error)
}

return self.responder.respond(to: vaporRequest).flatMap { ALB.TargetGroupResponse.from(response: $0, in: context) }
}
}

// MARK: - Request -

extension Vapor.Request {
private static let bufferAllocator = ByteBufferAllocator()

convenience init(req: ALB.TargetGroupRequest, in ctx: Lambda.Context, for application: Application) throws {
var buffer: NIO.ByteBuffer?
switch (req.body, req.isBase64Encoded) {
case (let .some(string), true):
let bytes = try string.base64decoded()
buffer = Vapor.Request.bufferAllocator.buffer(capacity: bytes.count)
buffer!.writeBytes(bytes)

case (let .some(string), false):
buffer = Vapor.Request.bufferAllocator.buffer(capacity: string.utf8.count)
buffer!.writeString(string)

case (.none, _):
break
}

var nioHeaders = NIOHTTP1.HTTPHeaders()
req.headers?.forEach { key, value in
nioHeaders.add(name: key, value: value)
}

/*if let cookies = req., cookies.count > 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets remove this

nioHeaders.add(name: "Cookie", value: cookies.joined(separator: "; "))
}*/

var url: String = req.path
if req.queryStringParameters.count > 0 {
url += "?\(req.queryStringParameters)"
}

self.init(
application: application,
method: NIOHTTP1.HTTPMethod(rawValue: req.httpMethod.rawValue),
url: Vapor.URI(path: url),
version: HTTPVersion(major: 1, minor: 1),
headers: nioHeaders,
collectedBody: buffer,
remoteAddress: nil,
logger: ctx.logger,
on: ctx.eventLoop
)

storage[ALB.TargetGroupRequest] = req
}
}

extension ALB.TargetGroupRequest: Vapor.StorageKey {
public typealias Value = ALB.TargetGroupRequest
}

// MARK: - Response -

extension ALB.TargetGroupResponse {
static func from(response: Vapor.Response, in context: Lambda.Context) -> EventLoopFuture<ALB.TargetGroupResponse> {
// Create the headers
var headers = [String: String]()
response.headers.forEach { name, value in
if let current = headers[name] {
headers[name] = "\(current),\(value)"
} else {
headers[name] = value
}
}

// Can we access the body right away?
if let string = response.body.string {
return context.eventLoop.makeSucceededFuture(.init(
statusCode: AWSLambdaEvents.HTTPResponseStatus(code: response.status.code),
headers: headers,
body: string,
isBase64Encoded: false
))
} else if let bytes = response.body.data {
return context.eventLoop.makeSucceededFuture(.init(
statusCode: AWSLambdaEvents.HTTPResponseStatus(code: response.status.code),
headers: headers,
body: String(base64Encoding: bytes),
isBase64Encoded: true
))
} else {
// See if it is a stream and try to gather the data
return response.body.collect(on: context.eventLoop).map { (buffer) -> ALB.TargetGroupResponse in
// Was there any content
guard
var buffer = buffer,
let bytes = buffer.readBytes(length: buffer.readableBytes)
else {
return ALB.TargetGroupResponse(
statusCode: AWSLambdaEvents.HTTPResponseStatus(code: response.status.code),
headers: headers
)
}

// Done
return ALB.TargetGroupResponse(
statusCode: AWSLambdaEvents.HTTPResponseStatus(code: response.status.code),
headers: headers,
body: String(base64Encoding: bytes),
isBase64Encoded: true
)
}
}
}
}
10 changes: 6 additions & 4 deletions Sources/VaporAWSLambdaRuntime/LambdaServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ public extension Application.Lambda {
}
}

struct ConfigurationKey: StorageKey {
typealias Value = LambdaServer.Configuration
public struct ConfigurationKey: StorageKey {
public typealias Value = LambdaServer.Configuration
}
}
}
Expand All @@ -79,13 +79,13 @@ public class LambdaServer: Server {
public enum RequestSource {
case apiGateway
case apiGatewayV2
// case applicationLoadBalancer // not in this release
case applicationLoadBalancer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙌

}

var requestSource: RequestSource
var logger: Logger

init(apiService: RequestSource = .apiGatewayV2, logger: Logger) {
public init(apiService: RequestSource = .apiGatewayV2, logger: Logger) {
self.requestSource = apiService
self.logger = logger
}
Expand Down Expand Up @@ -115,6 +115,8 @@ public class LambdaServer: Server {
handler = APIGatewayHandler(application: application, responder: responder)
case .apiGatewayV2:
handler = APIGatewayV2Handler(application: application, responder: responder)
case .applicationLoadBalancer:
handler = ALBHandler(application: application, responder: responder)
}

self.lambdaLifecycle = Lambda.Lifecycle(
Expand Down
80 changes: 80 additions & 0 deletions Tests/VaporAWSLambdaRuntimeTests/ALB.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import AWSLambdaEvents
@testable import AWSLambdaRuntimeCore
import Logging
import NIO
import Vapor
@testable import VaporAWSLambdaRuntime
import XCTest

final class ALBTests: XCTestCase {
func testALBRequest() throws {
let requestdata = """
{
"requestContext": {
"elb": {
"targetGroupArn": "arn:aws:elasticloadbalancing:us-east-2:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a"
}
},
"httpMethod": "GET",
"path": "/lambda",
"queryStringParameters": {
"query": "1234ABCD"
},
"headers": {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"accept-encoding": "gzip",
"accept-language": "en-US,en;q=0.9",
"connection": "keep-alive",
"host": "lambda-alb-123578498.us-east-2.elb.amazonaws.com",
"upgrade-insecure-requests": "1",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
"x-amzn-trace-id": "Root=1-5c536348-3d683b8b04734faae651f476",
"x-forwarded-for": "72.12.164.125",
"x-forwarded-port": "80",
"x-forwarded-proto": "http",
"x-imforwards": "20"
},
"body": "",
"isBase64Encoded": false
}
"""
let decoder = JSONDecoder()
let request = try decoder.decode(ALB.TargetGroupRequest.self, from: requestdata.data(using: .utf8)!)
print("F: ", request)
}

func testCreateALBResponse() {
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) }
let eventLoop = eventLoopGroup.next()
let allocator = ByteBufferAllocator()
let logger = Logger(label: "test")

let body = #"{"hello": "world"}"#
let vaporResponse = Vapor.Response(
status: .ok,
headers: HTTPHeaders([
("Content-Type", "application/json"),
]),
body: .init(string: body)
)

let context = Lambda.Context(
requestID: "abc123",
traceID: AmazonHeaders.generateXRayTraceID(),
invokedFunctionARN: "function-arn",
deadline: .now() + .seconds(3),
logger: logger,
eventLoop: eventLoop,
allocator: allocator
)

var response: ALB.TargetGroupResponse?
XCTAssertNoThrow(response = try ALB.TargetGroupResponse.from(response: vaporResponse, in: context).wait())

XCTAssertEqual(response?.body, body)
XCTAssertEqual(response?.headers?.count, 2)
XCTAssertEqual(response?.headers?["Content-Type"], "application/json")
XCTAssertEqual(response?.headers?["content-length"], String(body.count))
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 11 additions & 11 deletions examples/Hello/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion examples/Hello/Sources/Hello/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ app.post("hello") { req -> Hello in
let name = try req.content.decode(Name.self)
return Hello(hello: name.name)
}

app.storage[Application.Lambda.Server.ConfigurationKey.self] = .init(apiService: .applicationLoadBalancer,
logger: app.logger)
app.servers.use(.lambda)
try app.run()


2 changes: 1 addition & 1 deletion examples/Hello/makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ build_lambda:
$(SWIFT_DOCKER_IMAGE) \
swift build --product Hello -c release

package_lambda: build_lambda
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason this was removed?

package_lambda:
docker run \
--rm \
--volume "$(shell pwd)/../..:/src" \
Expand Down