Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add (API Gateway) WebSockets Support to Swift for AWS Lambda Events #38

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
68 changes: 68 additions & 0 deletions Sources/AWSLambdaEvents/APIGateway+WebSockets.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftAWSLambdaRuntime open source project
//
// Copyright (c) YEARS Apple Inc. and the SwiftAWSLambdaRuntime project authors
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Copyright (c) YEARS Apple Inc. and the SwiftAWSLambdaRuntime project authors
// Copyright (c) 2023 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
//
//===----------------------------------------------------------------------===//

/// `APIGatewayWebSocketRequest` is a variation of the`APIGatewayV2Request`
/// and contains data coming from the WebSockets API Gateway.
public struct APIGatewayWebSocketRequest: Codable {
Copy link
Contributor

Choose a reason for hiding this comment

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

can any of this vibe shared with APIGatewayRequest or not worth it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh sure ... much of it can be. I just wasn't sure what the correct approach should be. I kind of aimed for a "what's the minimum to make it work" approach. :)

Copy link
Contributor

Choose a reason for hiding this comment

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

probably some shared struct they can both include as the underlying implementation

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If it would be helpful, I would be happy to look at the APIGatewayV2Request event, as well as my proposed APIGatewayWebSocketRequest event and extract those items shared between them as a struct both can use…and then update this pull request.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If it would be helpful, I would be happy to look at the APIGatewayV2Request event, as well as my proposed APIGatewayWebSocketRequest event and extract those items shared between them as a struct both can use…and then update this pull request.

/// `Context` contains information to identify the AWS account and resources invoking the Lambda function.
public struct Context: Codable {
public struct Identity: Codable {
let sourceIp: String
}

let routeKey: String
let eventType: String
let extendedRequestId: String
/// The request time in format: 23/Apr/2020:11:08:18 +0000
let requestTime: String
let messageDirection: String
let stage: String
let connectedAt: UInt64
let requestTimeEpoch: UInt64
let identity: Identity
let requestId: String
let domainName: String
let connectionId: String
let apiId: String
}

public let headers: HTTPHeaders?
public let multiValueHeaders: HTTPMultiValueHeaders?
Copy link
Contributor

@jsonfry jsonfry Mar 25, 2024

Choose a reason for hiding this comment

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

I also see

 public let queryStringParameters: [String: String]?
 public let multiValueQueryStringParameters: [String: [String]]?

come through on a CONNECT event type

public let context: Context
public let body: String?
public let isBase64Encoded: Bool?

enum CodingKeys: String, CodingKey {
case headers
case multiValueHeaders
case context = "requestContext"
case body
case isBase64Encoded
}
}

/// `APIGatewayWebSocketResponse` is a type alias for `APIGatewayV2Request`.
/// Typically, lambda WebSockets servers send clients data via
/// the ApiGatewayManagementApi mechanism. However, APIGateway does require
/// lambda servers to return some kind of status when APIGateway invokes them.
/// This can be as simple as always returning a 200 "OK" response for all
/// WebSockets requests (the ApiGatewayManagementApi can return any errors to
/// WebSockets clients).
public typealias APIGatewayWebSocketResponse = APIGatewayV2Response

#if swift(>=5.6)
extension APIGatewayWebSocketRequest: Sendable {}
extension APIGatewayWebSocketRequest.Context: Sendable {}
extension APIGatewayWebSocketRequest.Context.Identity: Sendable {}
#endif
77 changes: 77 additions & 0 deletions Tests/AWSLambdaEventsTests/APIGateway+WebsocketsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftAWSLambdaRuntime open source project
//
// Copyright (c) YEARS 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
//
//===----------------------------------------------------------------------===//

@testable import AWSLambdaEvents
import XCTest

class APIGatewayWebSocketsTests: XCTestCase {
static let exampleConnectEventBody = """
{
"headers": {
"Host": "lqrlmblaa2.execute-api.us-east-1.amazonaws.com",
"Origin": "wss://lqrlmblaa2.execute-api.us-east-1.amazonaws.com",
"Sec-WebSocket-Extensions": "permessage-deflate; client_max_window_bits; server_max_window_bits=15",
"Sec-WebSocket-Key": "am5ubWVpbHd3bmNyYXF0ag==",
"Sec-WebSocket-Version": "13",
"X-Amzn-Trace-Id": "Root=1-64b83950-42de8e247b4c2b43091ef67c",
"X-Forwarded-For": "24.148.42.16",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https"
},
"multiValueHeaders": {
"Host": [ "lqrlmblaa2.execute-api.us-east-1.amazonaws.com" ],
"Origin": [ "wss://lqrlmblaa2.execute-api.us-east-1.amazonaws.com" ],
"Sec-WebSocket-Extensions": [
"permessage-deflate; client_max_window_bits; server_max_window_bits=15"
],
"Sec-WebSocket-Key": [ "am5ubWVpbHd3bmNyYXF0ag==" ],
"Sec-WebSocket-Version": [ "13" ],
"X-Amzn-Trace-Id": [ "Root=1-64b83950-42de8e247b4c2b43091ef67c" ],
"X-Forwarded-For": [ "24.148.42.16" ],
"X-Forwarded-Port": [ "443" ],
"X-Forwarded-Proto": [ "https" ]
},
"requestContext": {
"routeKey": "$connect",
"eventType": "CONNECT",
"extendedRequestId": "IU3kkGyEoAMFwZQ=",
"requestTime": "19/Jul/2023:19:28:16 +0000",
"messageDirection": "IN",
"stage": "dev",
"connectedAt": 1689794896145,
"requestTimeEpoch": 1689794896162,
"identity": { "sourceIp": "24.148.42.16" },
"requestId": "IU3kkGyEoAMFwZQ=",
"domainName": "lqrlmblaa2.execute-api.us-east-1.amazonaws.com",
"connectionId": "IU3kkeN4IAMCJwA=",
"apiId": "lqrlmblaa2"
},
"isBase64Encoded": false
}
"""

// MARK: - Request -

// MARK: Decoding

func testRequestDecodingExampleConnectRequest() {
let data = APIGatewayWebSocketsTests.exampleConnectEventBody.data(using: .utf8)!
var req: APIGatewayWebSocketRequest?
XCTAssertNoThrow(req = try JSONDecoder().decode(APIGatewayWebSocketRequest.self, from: data))

XCTAssertEqual(req?.context.routeKey, "$connect")
XCTAssertEqual(req?.context.connectionId, "IU3kkeN4IAMCJwA=")
XCTAssertNil(req?.body)
}
}