From edecf5507d9458997a06bdaf159aff11fedb9e57 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 20 Feb 2022 22:24:15 +0100 Subject: [PATCH 1/8] Add a test server --- Package.resolved | 126 ++++++++++++++++++ Package.swift | 28 +++- README.md | 42 +++--- Resources/Views/index.leaf | 9 ++ Sources/LighthouseClient/Connection.swift | 1 + Sources/LighthouseClient/Constants.swift | 3 - Sources/LighthouseDemo/LighthouseDemo.swift | 1 + .../Authentication.swift | 0 .../Color.swift | 0 Sources/LighthouseProtocol/Constants.swift | 3 + .../Display.swift | 0 .../Protocol.swift | 34 ++++- Sources/LighthouseTestServer/configure.swift | 12 ++ Sources/LighthouseTestServer/main.swift | 8 ++ Sources/LighthouseTestServer/routes.swift | 7 + Sources/Package.swift | 34 +++++ 16 files changed, 282 insertions(+), 26 deletions(-) create mode 100644 Resources/Views/index.leaf rename Sources/{LighthouseClient => LighthouseProtocol}/Authentication.swift (100%) rename Sources/{LighthouseClient => LighthouseProtocol}/Color.swift (100%) create mode 100644 Sources/LighthouseProtocol/Constants.swift rename Sources/{LighthouseClient => LighthouseProtocol}/Display.swift (100%) rename Sources/{LighthouseClient => LighthouseProtocol}/Protocol.swift (73%) create mode 100644 Sources/LighthouseTestServer/configure.swift create mode 100644 Sources/LighthouseTestServer/main.swift create mode 100644 Sources/LighthouseTestServer/routes.swift create mode 100644 Sources/Package.swift diff --git a/Package.resolved b/Package.resolved index 2e0bdcb..76cfaf2 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,6 +1,51 @@ { "object": { "pins": [ + { + "package": "async-http-client", + "repositoryURL": "https://github.com/swift-server/async-http-client.git", + "state": { + "branch": null, + "revision": "7a4dfe026f6ee0f8ad741b58df74c60af296365d", + "version": "1.9.0" + } + }, + { + "package": "async-kit", + "repositoryURL": "https://github.com/vapor/async-kit.git", + "state": { + "branch": null, + "revision": "e2f741640364c1d271405da637029ea6a33f754e", + "version": "1.11.1" + } + }, + { + "package": "console-kit", + "repositoryURL": "https://github.com/vapor/console-kit.git", + "state": { + "branch": null, + "revision": "75ea3b627d88221440b878e5dfccc73fd06842ed", + "version": "4.2.7" + } + }, + { + "package": "leaf", + "repositoryURL": "https://github.com/vapor/leaf.git", + "state": { + "branch": null, + "revision": "41741b782ac49959af2f7612661154326ac24d00", + "version": "4.1.5" + } + }, + { + "package": "leaf-kit", + "repositoryURL": "https://github.com/vapor/leaf-kit.git", + "state": { + "branch": null, + "revision": "983fcbe89e7153c4d5870bdc76bf57a56817225f", + "version": "1.4.0" + } + }, { "package": "MessagePack", "repositoryURL": "https://github.com/Flight-School/MessagePack.git", @@ -10,6 +55,24 @@ "version": "1.2.4" } }, + { + "package": "multipart-kit", + "repositoryURL": "https://github.com/vapor/multipart-kit.git", + "state": { + "branch": null, + "revision": "2dd9368a3c9580792b77c7ef364f3735909d9996", + "version": "4.5.1" + } + }, + { + "package": "routing-kit", + "repositoryURL": "https://github.com/vapor/routing-kit.git", + "state": { + "branch": null, + "revision": "5603b81ceb744b8318feab1e60943704977a866b", + "version": "4.3.1" + } + }, { "package": "swift-argument-parser", "repositoryURL": "https://github.com/apple/swift-argument-parser.git", @@ -19,6 +82,24 @@ "version": "1.0.3" } }, + { + "package": "swift-backtrace", + "repositoryURL": "https://github.com/swift-server/swift-backtrace.git", + "state": { + "branch": null, + "revision": "d3e04a9d4b3833363fb6192065b763310b156d54", + "version": "1.3.1" + } + }, + { + "package": "swift-crypto", + "repositoryURL": "https://github.com/apple/swift-crypto.git", + "state": { + "branch": null, + "revision": "a8911e0fadc25aef1071d582355bd1037a176060", + "version": "2.0.4" + } + }, { "package": "swift-log", "repositoryURL": "https://github.com/apple/swift-log.git", @@ -28,6 +109,15 @@ "version": "1.4.2" } }, + { + "package": "swift-metrics", + "repositoryURL": "https://github.com/apple/swift-metrics.git", + "state": { + "branch": null, + "revision": "3edd2f57afc4e68e23c3e4956bc8b65ca6b5b2ff", + "version": "2.2.0" + } + }, { "package": "swift-nio", "repositoryURL": "https://github.com/apple/swift-nio.git", @@ -37,6 +127,24 @@ "version": "2.38.0" } }, + { + "package": "swift-nio-extras", + "repositoryURL": "https://github.com/apple/swift-nio-extras.git", + "state": { + "branch": null, + "revision": "f73ca5ee9c6806800243f1ac415fcf82de9a4c91", + "version": "1.10.2" + } + }, + { + "package": "swift-nio-http2", + "repositoryURL": "https://github.com/apple/swift-nio-http2.git", + "state": { + "branch": null, + "revision": "000ca94f9de92c95b9ac85d44600b7b0fe25a3e5", + "version": "1.19.2" + } + }, { "package": "swift-nio-ssl", "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", @@ -46,6 +154,24 @@ "version": "2.17.2" } }, + { + "package": "swift-nio-transport-services", + "repositoryURL": "https://github.com/apple/swift-nio-transport-services.git", + "state": { + "branch": null, + "revision": "8ab824b140d0ebcd87e9149266ddc353e3705a3e", + "version": "1.11.4" + } + }, + { + "package": "vapor", + "repositoryURL": "https://github.com/vapor/vapor.git", + "state": { + "branch": null, + "revision": "18e9419cae5049e43ca1e8002ca3cf0449f2c8ed", + "version": "4.55.0" + } + }, { "package": "websocket-kit", "repositoryURL": "https://github.com/vapor/websocket-kit.git", diff --git a/Package.swift b/Package.swift index 84c280a..b7a7a76 100644 --- a/Package.swift +++ b/Package.swift @@ -10,12 +10,16 @@ let package = Package( // Products define the executables and libraries a package produces, and make them visible to other packages. .library( name: "LighthouseClient", - targets: ["LighthouseClient"] + targets: ["LighthouseProtocol", "LighthouseClient"] ), .executable( name: "LighthouseDemo", targets: ["LighthouseDemo"] - ) + ), + .executable( + name: "LighthouseTestServer", + targets: ["LighthouseTestServer"] + ), ], dependencies: [ // Dependencies declare other packages that this package depends on. @@ -23,13 +27,22 @@ let package = Package( .package(url: "https://github.com/Flight-School/MessagePack.git", from: "1.2.4"), .package(url: "https://github.com/apple/swift-log.git", from: "1.4.2"), .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.0.3"), + .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), + .package(url: "https://github.com/vapor/leaf.git", from: "4.0.0"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "LighthouseProtocol", + dependencies: [ + .product(name: "Logging", package: "swift-log"), + ] + ), .target( name: "LighthouseClient", dependencies: [ + .target(name: "LighthouseProtocol"), .product(name: "WebSocketKit", package: "websocket-kit"), .product(name: "MessagePack", package: "MessagePack"), .product(name: "Logging", package: "swift-log"), @@ -38,11 +51,22 @@ let package = Package( .executableTarget( name: "LighthouseDemo", dependencies: [ + .target(name: "LighthouseProtocol"), .target(name: "LighthouseClient"), .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "Logging", package: "swift-log"), ] ), + .executableTarget( + name: "LighthouseTestServer", + dependencies: [ + .target(name: "LighthouseProtocol"), + .product(name: "ArgumentParser", package: "swift-argument-parser"), + .product(name: "Logging", package: "swift-log"), + .product(name: "Vapor", package: "vapor"), + .product(name: "Leaf", package: "leaf"), + ] + ), // .testTarget( // name: "LighthouseClientTests", // dependencies: [ diff --git a/README.md b/README.md index 0d682cf..494c376 100644 --- a/README.md +++ b/README.md @@ -7,25 +7,29 @@ An API client for a light installation at the University of Kiel using Swift 5.5 ## Example ```swift -// Prepare connection -let conn = Connection(authentication: Authentication( - username: "[your username]", - token: "[your token]" -)) - -// Handle incoming input events -conn.onInput { input in - print("Got input \(input)") -} - -// Connect to the lighthouse server and request events -try await conn.connect() -try await conn.requestStream() - -// Repeatedly send colored displays to the lighthouse -while true { - try await conn.send(display: Display(fill: .random())) - try await Task.sleep(nanoseconds: 1_000_000_000) +import LighthouseClient + +func runApp() async throws { + // Prepare connection + let conn = Connection(authentication: Authentication( + username: "[your username]", + token: "[your token]" + )) + + // Handle incoming input events + conn.onInput { input in + print("Got input \(input)") + } + + // Connect to the lighthouse server and request events + try await conn.connect() + try await conn.requestStream() + + // Repeatedly send colored displays to the lighthouse + while true { + try await conn.send(display: Display(fill: .random())) + try await Task.sleep(nanoseconds: 1_000_000_000) + } } ``` diff --git a/Resources/Views/index.leaf b/Resources/Views/index.leaf new file mode 100644 index 0000000..515521d --- /dev/null +++ b/Resources/Views/index.leaf @@ -0,0 +1,9 @@ + + + + Lighthouse Test Server + + + Test + + diff --git a/Sources/LighthouseClient/Connection.swift b/Sources/LighthouseClient/Connection.swift index 1233284..a571ce6 100644 --- a/Sources/LighthouseClient/Connection.swift +++ b/Sources/LighthouseClient/Connection.swift @@ -3,6 +3,7 @@ import Logging import MessagePack import NIO import WebSocketKit +import LighthouseProtocol private let log = Logger(label: "LighthouseClient.Connection") diff --git a/Sources/LighthouseClient/Constants.swift b/Sources/LighthouseClient/Constants.swift index 301e1f5..04a7f10 100644 --- a/Sources/LighthouseClient/Constants.swift +++ b/Sources/LighthouseClient/Constants.swift @@ -1,6 +1,3 @@ import Foundation -public let lighthouseRows: Int = 14 -public let lighthouseCols: Int = 28 -public let lighthouseSize: Int = lighthouseRows * lighthouseCols public let lighthouseUrl: URL = URL(string: "wss://lighthouse.uni-kiel.de/websocket")! diff --git a/Sources/LighthouseDemo/LighthouseDemo.swift b/Sources/LighthouseDemo/LighthouseDemo.swift index 80fc455..824a78c 100644 --- a/Sources/LighthouseDemo/LighthouseDemo.swift +++ b/Sources/LighthouseDemo/LighthouseDemo.swift @@ -2,6 +2,7 @@ import ArgumentParser import Dispatch import Foundation import Logging +import LighthouseProtocol import LighthouseClient let log = Logger(label: "LighthouseDemo") diff --git a/Sources/LighthouseClient/Authentication.swift b/Sources/LighthouseProtocol/Authentication.swift similarity index 100% rename from Sources/LighthouseClient/Authentication.swift rename to Sources/LighthouseProtocol/Authentication.swift diff --git a/Sources/LighthouseClient/Color.swift b/Sources/LighthouseProtocol/Color.swift similarity index 100% rename from Sources/LighthouseClient/Color.swift rename to Sources/LighthouseProtocol/Color.swift diff --git a/Sources/LighthouseProtocol/Constants.swift b/Sources/LighthouseProtocol/Constants.swift new file mode 100644 index 0000000..44b6f92 --- /dev/null +++ b/Sources/LighthouseProtocol/Constants.swift @@ -0,0 +1,3 @@ +public let lighthouseRows: Int = 14 +public let lighthouseCols: Int = 28 +public let lighthouseSize: Int = lighthouseRows * lighthouseCols diff --git a/Sources/LighthouseClient/Display.swift b/Sources/LighthouseProtocol/Display.swift similarity index 100% rename from Sources/LighthouseClient/Display.swift rename to Sources/LighthouseProtocol/Display.swift diff --git a/Sources/LighthouseClient/Protocol.swift b/Sources/LighthouseProtocol/Protocol.swift similarity index 73% rename from Sources/LighthouseClient/Protocol.swift rename to Sources/LighthouseProtocol/Protocol.swift index d83f9ef..9631564 100644 --- a/Sources/LighthouseClient/Protocol.swift +++ b/Sources/LighthouseProtocol/Protocol.swift @@ -56,9 +56,25 @@ public enum Protocol { public var requestId: Int public var verb: String public var path: [String] - public var meta: [String: String] = [:] + public var meta: [String: String] public var authentication: Authentication - public var payload: Payload = .other + public var payload: Payload + + public init( + requestId: Int, + verb: String, + path: [String], + meta: [String: String] = [:], + authentication: Authentication, + payload: Payload + ) { + self.requestId = requestId + self.verb = verb + self.path = path + self.meta = meta + self.authentication = authentication + self.payload = payload + } } /// A message originating from the lighthouse server. @@ -76,5 +92,19 @@ public enum Protocol { public var warnings: [String]? public var response: String? public var payload: Payload + + public init( + code: Int, + requestId: Int, + warnings: [String]? = nil, + response: String? = nil, + payload: Payload + ) { + self.code = code + self.requestId = requestId + self.warnings = warnings + self.response = response + self.payload = payload + } } } diff --git a/Sources/LighthouseTestServer/configure.swift b/Sources/LighthouseTestServer/configure.swift new file mode 100644 index 0000000..79da61f --- /dev/null +++ b/Sources/LighthouseTestServer/configure.swift @@ -0,0 +1,12 @@ +import Vapor +import Leaf + +public func configure(_ app: Application) throws { + // Serve from /Public + app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) + + app.views.use(.leaf) + + // Register routes + try routes(app) +} diff --git a/Sources/LighthouseTestServer/main.swift b/Sources/LighthouseTestServer/main.swift new file mode 100644 index 0000000..a291b28 --- /dev/null +++ b/Sources/LighthouseTestServer/main.swift @@ -0,0 +1,8 @@ +import Vapor + +var env = try Environment.detect() +try LoggingSystem.bootstrap(from: &env) +let app = Application(env) +defer { app.shutdown() } +try configure(app) +try app.run() diff --git a/Sources/LighthouseTestServer/routes.swift b/Sources/LighthouseTestServer/routes.swift new file mode 100644 index 0000000..5e45ed4 --- /dev/null +++ b/Sources/LighthouseTestServer/routes.swift @@ -0,0 +1,7 @@ +import Vapor + +func routes(_ app: Application) throws { + app.get { req in + req.view.render("index") + } +} diff --git a/Sources/Package.swift b/Sources/Package.swift new file mode 100644 index 0000000..33c4609 --- /dev/null +++ b/Sources/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version:5.5 +import PackageDescription + +let package = Package( + name: "LighthouseTestServer", + platforms: [ + .macOS(.v10_15) + ], + dependencies: [ + // 💧 A server-side Swift web framework. + .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), + .package(url: "https://github.com/vapor/leaf.git", from: "4.0.0"), + ], + targets: [ + .target( + name: "App", + dependencies: [ + .product(name: "Leaf", package: "leaf"), + .product(name: "Vapor", package: "vapor") + ], + swiftSettings: [ + // Enable better optimizations when building in Release configuration. Despite the use of + // the `.unsafeFlags` construct required by SwiftPM, this flag is recommended for Release + // builds. See for details. + .unsafeFlags(["-cross-module-optimization"], .when(configuration: .release)) + ] + ), + .executableTarget(name: "Run", dependencies: [.target(name: "App")]), + .testTarget(name: "AppTests", dependencies: [ + .target(name: "App"), + .product(name: "XCTVapor", package: "vapor"), + ]) + ] +) From 2d9d3728f43d30295028192cf811ea0bc42e0e3c Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 20 Feb 2022 22:39:36 +0100 Subject: [PATCH 2/8] Actually use the arg URL in the demo --- Sources/LighthouseDemo/LighthouseDemo.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/LighthouseDemo/LighthouseDemo.swift b/Sources/LighthouseDemo/LighthouseDemo.swift index 824a78c..33921ae 100644 --- a/Sources/LighthouseDemo/LighthouseDemo.swift +++ b/Sources/LighthouseDemo/LighthouseDemo.swift @@ -5,8 +5,8 @@ import Logging import LighthouseProtocol import LighthouseClient -let log = Logger(label: "LighthouseDemo") -let env = ProcessInfo.processInfo.environment +private let log = Logger(label: "LighthouseDemo") +private let env = ProcessInfo.processInfo.environment @main struct LighthouseDemo: ParsableCommand { @@ -25,7 +25,7 @@ struct LighthouseDemo: ParsableCommand { // Prepare connection let auth = Authentication(username: username, token: token) - let conn = Connection(authentication: auth) + let conn = Connection(authentication: auth, url: url) // Handle incoming input events conn.onInput { input in From 576708048a0a85a644d406f8654c667138488fe4 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 20 Feb 2022 22:39:49 +0100 Subject: [PATCH 3/8] Handle WebSocket connections in the test server --- Package.swift | 5 +- .../ConnectionHandler.swift | 51 +++++++++++++++++++ Sources/LighthouseTestServer/routes.swift | 5 ++ Sources/Package.swift | 34 ------------- 4 files changed, 59 insertions(+), 36 deletions(-) create mode 100644 Sources/LighthouseTestServer/ConnectionHandler.swift delete mode 100644 Sources/Package.swift diff --git a/Package.swift b/Package.swift index b7a7a76..737fe9e 100644 --- a/Package.swift +++ b/Package.swift @@ -43,9 +43,9 @@ let package = Package( name: "LighthouseClient", dependencies: [ .target(name: "LighthouseProtocol"), - .product(name: "WebSocketKit", package: "websocket-kit"), - .product(name: "MessagePack", package: "MessagePack"), .product(name: "Logging", package: "swift-log"), + .product(name: "MessagePack", package: "MessagePack"), + .product(name: "WebSocketKit", package: "websocket-kit"), ] ), .executableTarget( @@ -63,6 +63,7 @@ let package = Package( .target(name: "LighthouseProtocol"), .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "Logging", package: "swift-log"), + .product(name: "MessagePack", package: "MessagePack"), .product(name: "Vapor", package: "vapor"), .product(name: "Leaf", package: "leaf"), ] diff --git a/Sources/LighthouseTestServer/ConnectionHandler.swift b/Sources/LighthouseTestServer/ConnectionHandler.swift new file mode 100644 index 0000000..84df735 --- /dev/null +++ b/Sources/LighthouseTestServer/ConnectionHandler.swift @@ -0,0 +1,51 @@ +import NIO +import MessagePack +import Vapor +import LighthouseProtocol + +private let log = Logger(label: "LighthouseTestServer.MessagingHandler") + +/// Handles the server side of the LighthouseTestServer's web sockets. +class ConnectionHandler { + private var clients: [UUID: ClientState] = [:] + + private class ClientState { + private let ws: WebSocket + var name: String? = nil + + init(ws: WebSocket) { + self.ws = ws + } + + func handleReceive(of data: Data) { + do { + let message = try MessagePackDecoder().decode(Protocol.ClientMessage.self, from: data) + + log.info("Got \(message)") + // TODO: Handle it + } catch { + log.warning("Error while decoding message: \(error)") + } + } + } + + func connect(_ ws: WebSocket) { + let uuid = UUID() + clients[uuid] = ClientState(ws: ws) + + log.info("Opened connection to \(uuid)") + + ws.onBinary { [weak self] _, buf in + guard let data = buf.getData(at: 0, length: buf.readableBytes) else { + log.warning("Could not read data from WebSocket") + return + } + self?.clients[uuid]?.handleReceive(of: data) + } + + ws.onClose.whenComplete { [weak self] _ in + self?.clients[uuid] = nil + log.info("Closed connection to \(uuid)") + } + } +} diff --git a/Sources/LighthouseTestServer/routes.swift b/Sources/LighthouseTestServer/routes.swift index 5e45ed4..44e69af 100644 --- a/Sources/LighthouseTestServer/routes.swift +++ b/Sources/LighthouseTestServer/routes.swift @@ -4,4 +4,9 @@ func routes(_ app: Application) throws { app.get { req in req.view.render("index") } + + let handler = ConnectionHandler() + app.webSocket("websocket") { req, ws in + handler.connect(ws) + } } diff --git a/Sources/Package.swift b/Sources/Package.swift deleted file mode 100644 index 33c4609..0000000 --- a/Sources/Package.swift +++ /dev/null @@ -1,34 +0,0 @@ -// swift-tools-version:5.5 -import PackageDescription - -let package = Package( - name: "LighthouseTestServer", - platforms: [ - .macOS(.v10_15) - ], - dependencies: [ - // 💧 A server-side Swift web framework. - .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), - .package(url: "https://github.com/vapor/leaf.git", from: "4.0.0"), - ], - targets: [ - .target( - name: "App", - dependencies: [ - .product(name: "Leaf", package: "leaf"), - .product(name: "Vapor", package: "vapor") - ], - swiftSettings: [ - // Enable better optimizations when building in Release configuration. Despite the use of - // the `.unsafeFlags` construct required by SwiftPM, this flag is recommended for Release - // builds. See for details. - .unsafeFlags(["-cross-module-optimization"], .when(configuration: .release)) - ] - ), - .executableTarget(name: "Run", dependencies: [.target(name: "App")]), - .testTarget(name: "AppTests", dependencies: [ - .target(name: "App"), - .product(name: "XCTVapor", package: "vapor"), - ]) - ] -) From 7b88d2fa843a591410c7583f7f6258e9fd96ea4a Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 20 Feb 2022 22:57:31 +0100 Subject: [PATCH 4/8] Handle basic requests in test server --- Sources/LighthouseProtocol/Protocol.swift | 2 +- .../ConnectionHandler.swift | 59 +++++++++++++++---- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/Sources/LighthouseProtocol/Protocol.swift b/Sources/LighthouseProtocol/Protocol.swift index 9631564..e576cce 100644 --- a/Sources/LighthouseProtocol/Protocol.swift +++ b/Sources/LighthouseProtocol/Protocol.swift @@ -98,7 +98,7 @@ public enum Protocol { requestId: Int, warnings: [String]? = nil, response: String? = nil, - payload: Payload + payload: Payload = .other ) { self.code = code self.requestId = requestId diff --git a/Sources/LighthouseTestServer/ConnectionHandler.swift b/Sources/LighthouseTestServer/ConnectionHandler.swift index 84df735..eda1b03 100644 --- a/Sources/LighthouseTestServer/ConnectionHandler.swift +++ b/Sources/LighthouseTestServer/ConnectionHandler.swift @@ -11,28 +11,36 @@ class ConnectionHandler { private class ClientState { private let ws: WebSocket + var receivesStream: Bool = false + var display: Display = .init() var name: String? = nil init(ws: WebSocket) { self.ws = ws } - func handleReceive(of data: Data) { - do { - let message = try MessagePackDecoder().decode(Protocol.ClientMessage.self, from: data) + func respond(to requestId: Int, code: Int, response: String? = nil) throws { + log.info("Responding with \(code) (id: \(requestId))") + try send(message: Protocol.ServerMessage( + code: code, + requestId: requestId, + response: response + )) + } - log.info("Got \(message)") - // TODO: Handle it - } catch { - log.warning("Error while decoding message: \(error)") - } + func send(message: Message) throws where Message: Encodable { + let data = try MessagePackEncoder().encode(message) + send(data: data) + } + + func send(data: Data) { + ws.send(Array(data)) } } func connect(_ ws: WebSocket) { let uuid = UUID() clients[uuid] = ClientState(ws: ws) - log.info("Opened connection to \(uuid)") ws.onBinary { [weak self] _, buf in @@ -40,7 +48,7 @@ class ConnectionHandler { log.warning("Could not read data from WebSocket") return } - self?.clients[uuid]?.handleReceive(of: data) + self?.handleReceive(of: data, for: uuid) } ws.onClose.whenComplete { [weak self] _ in @@ -48,4 +56,35 @@ class ConnectionHandler { log.info("Closed connection to \(uuid)") } } + + func handleReceive(of data: Data, for uuid: UUID) { + do { + guard let client = clients[uuid] else { + log.warning("No client associated with \(uuid)") + return + } + + let message = try MessagePackDecoder().decode(Protocol.ClientMessage.self, from: data) + log.info("Got \(message.verb) \(message.path.joined(separator: "/")) (id: \(message.requestId))") + + let name = message.authentication.username + client.name = name + + switch message.verb { + case "PUT": + log.info("Got a PUT, forwarding it to others") + // TODO + case "STREAM": + client.receivesStream = true + log.info("Enabled stream for \(name)") + try client.respond(to: message.requestId, code: 200) + default: + log.warning("Got unknown verb '\(message.verb)'") + try client.respond(to: message.requestId, code: 400, response: "Bad Request") + break + } + } catch { + log.warning("Error while handling message: \(error)") + } + } } From 781553d1ab93977b49f051c0ea6d478c8e84b125 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 20 Feb 2022 23:03:51 +0100 Subject: [PATCH 5/8] Handle PUT and STREAM requests in the test server --- .../ConnectionHandler.swift | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/Sources/LighthouseTestServer/ConnectionHandler.swift b/Sources/LighthouseTestServer/ConnectionHandler.swift index eda1b03..6c95ccd 100644 --- a/Sources/LighthouseTestServer/ConnectionHandler.swift +++ b/Sources/LighthouseTestServer/ConnectionHandler.swift @@ -28,6 +28,14 @@ class ConnectionHandler { )) } + func send(display: Display) throws { + try send(message: Protocol.ServerMessage( + code: 200, + requestId: 0, // TODO: Set this to some value? + payload: .display(display) + )) + } + func send(message: Message) throws where Message: Encodable { let data = try MessagePackEncoder().encode(message) send(data: data) @@ -70,16 +78,21 @@ class ConnectionHandler { let name = message.authentication.username client.name = name - switch message.verb { - case "PUT": - log.info("Got a PUT, forwarding it to others") - // TODO - case "STREAM": + switch (message.verb, message.payload) { + case ("PUT", .display(let display)): + log.info("Updating \(name)'s display and notifying streaming clients...") + client.display = display + try client.respond(to: message.requestId, code: 200) + + for rcvClient in clients.values where rcvClient.receivesStream { + try rcvClient.send(display: client.display) + } + case ("STREAM", _): client.receivesStream = true log.info("Enabled stream for \(name)") try client.respond(to: message.requestId, code: 200) default: - log.warning("Got unknown verb '\(message.verb)'") + log.warning("Got unknown request \(message.verb) with payload \(message.payload)") try client.respond(to: message.requestId, code: 400, response: "Bad Request") break } From 116fd68fede332c4e419b50d3f4e96eef2977805 Mon Sep 17 00:00:00 2001 From: fwcd Date: Sun, 20 Feb 2022 23:47:11 +0100 Subject: [PATCH 6/8] Implement web client for test server --- Public/scripts.js | 65 ++++++++++++++++++++++++++++++++++++++ Resources/Views/index.leaf | 18 +++++++++-- 2 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 Public/scripts.js diff --git a/Public/scripts.js b/Public/scripts.js new file mode 100644 index 0000000..6af2cca --- /dev/null +++ b/Public/scripts.js @@ -0,0 +1,65 @@ +let connection = null; +let auth = null; + +function setUpConnection() { + connection = new WebSocket(`${location.origin.replace(/^http/, "ws")}/websocket`); + connection.binaryType = "arraybuffer"; + + connection.addEventListener("open", () => { + console.log("Connected!"); + }); + + connection.addEventListener("message", event => { + try { + const message = MessagePack.decode(new Uint8Array(event.data)); + + if (message.PAYL instanceof Uint8Array) { + const display = document.getElementById("display"); + display.innerText = message.PAYL.map(x => `${x}`).join(", "); + } + } catch (e) { + console.log(`Error while decoding message from WebSocket: ${e}`); + } + }); +} + +function setUpFormListener() { + const form = document.getElementById("auth-form"); + const fieldset = document.getElementById("auth-form-fieldset"); + const usernameField = document.getElementById("username"); + const tokenField = document.getElementById("token"); + + form.addEventListener("submit", event => { + event.preventDefault(); + + if (auth) { + alert("You are already authenticated!"); + return; + } + + const username = usernameField.value; + const token = tokenField.value; + + if (!username) { + alert("Please provide a username!"); + return; + } + + auth = { USER: username, TOKEN: token }; + fieldset.disabled = true; + + connection.send(MessagePack.encode({ + VERB: "STREAM", + PATH: ["user", username, "model"], + AUTH: auth, + META: {}, + REID: 0, + PAYL: null, + })); + }); +} + +window.addEventListener("load", () => { + setUpConnection(); + setUpFormListener(); +}); diff --git a/Resources/Views/index.leaf b/Resources/Views/index.leaf index 515521d..e526468 100644 --- a/Resources/Views/index.leaf +++ b/Resources/Views/index.leaf @@ -1,9 +1,23 @@ - Lighthouse Test Server + Lighthouse Test + + - Test +
+

Lighthouse Test

+
+
+ + + +
+
+

+

+

+
From 162c10819bd7eda6c318fc56240dc51b5118a3e4 Mon Sep 17 00:00:00 2001 From: fwcd Date: Mon, 21 Feb 2022 00:04:30 +0100 Subject: [PATCH 7/8] Render displays to test server's web interface --- Public/scripts.js | 35 +++++++++++++++++++++++++++++++++-- Resources/Views/index.leaf | 2 +- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/Public/scripts.js b/Public/scripts.js index 6af2cca..035014a 100644 --- a/Public/scripts.js +++ b/Public/scripts.js @@ -1,5 +1,34 @@ +const lighthouseRows = 14; +const lighthouseCols = 28; +const xScale = 14; +const yScale = 2 * xScale; + let connection = null; let auth = null; +let display = null; + +function updateDisplay(rgb) { + const ctx = display.getContext("2d"); + + for (let i = 0; i < (rgb.length / 3); i++) { + const r = rgb[3 * i]; + const g = rgb[3 * i + 1]; + const b = rgb[3 * i + 2]; + + const y = Math.floor(i / lighthouseCols); + const x = i % lighthouseCols; + + ctx.fillStyle = `rgb(${r},${g},${b})`; + ctx.fillRect(x * xScale, y * yScale, xScale, yScale); + } +} + +function setUpDisplay() { + display = document.getElementById("display"); + display.width = xScale * lighthouseCols; + display.height = yScale * lighthouseRows; + updateDisplay(new Uint8Array(3 * lighthouseRows * lighthouseCols)); +} function setUpConnection() { connection = new WebSocket(`${location.origin.replace(/^http/, "ws")}/websocket`); @@ -14,8 +43,9 @@ function setUpConnection() { const message = MessagePack.decode(new Uint8Array(event.data)); if (message.PAYL instanceof Uint8Array) { - const display = document.getElementById("display"); - display.innerText = message.PAYL.map(x => `${x}`).join(", "); + updateDisplay(message.PAYL); + } else { + console.log(`Something else: ${message.PAYL instanceof Uint8Array}`); } } catch (e) { console.log(`Error while decoding message from WebSocket: ${e}`); @@ -60,6 +90,7 @@ function setUpFormListener() { } window.addEventListener("load", () => { + setUpDisplay(); setUpConnection(); setUpFormListener(); }); diff --git a/Resources/Views/index.leaf b/Resources/Views/index.leaf index e526468..79081e4 100644 --- a/Resources/Views/index.leaf +++ b/Resources/Views/index.leaf @@ -16,7 +16,7 @@

-

+

From 7aa1ef3e1d487bb080b3a7ace1d46755276f0553 Mon Sep 17 00:00:00 2001 From: fwcd Date: Mon, 21 Feb 2022 00:09:41 +0100 Subject: [PATCH 8/8] Update example in README --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 494c376..91ca6b5 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ An API client for a light installation at the University of Kiel using Swift 5.5 ```swift import LighthouseClient +import LighthouseProtocol +import Dispatch func runApp() async throws { // Prepare connection @@ -31,6 +33,12 @@ func runApp() async throws { try await Task.sleep(nanoseconds: 1_000_000_000) } } + +Task { + try! await runApp() +} + +dispatchMain() ``` ## Usage