Compare commits

..

No commits in common. "21a4f4ecae16658fdce28eae46c2057f77801733" and "4274dfde4ca307489e8967f6f1c57c303d4b3a2c" have entirely different histories.

6 changed files with 22 additions and 90 deletions

1
.gitignore vendored
View File

@ -2,4 +2,3 @@
Package.resolved Package.resolved
.swiftpm .swiftpm
.build .build
Resources/config.json

View File

@ -4,19 +4,16 @@ import PackageDescription
let package = Package( let package = Package(
name: "SesameServer", name: "SesameServer",
platforms: [ platforms: [
.macOS(.v12) .macOS(.v10_15)
], ],
dependencies: [ dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
.package(url: "https://github.com/christophhagen/clairvoyant.git", from: "0.3.0"),
], ],
targets: [ targets: [
.target( .target(
name: "App", name: "App",
dependencies: [ dependencies: [
.product(name: "Vapor", package: "vapor"), .product(name: "Vapor", package: "vapor")
.product(name: "Clairvoyant", package: "Clairvoyant"),
], ],
swiftSettings: [ swiftSettings: [
// Enable better optimizations when building in Release configuration. Despite the use of // Enable better optimizations when building in Release configuration. Despite the use of

View File

@ -1,6 +0,0 @@
{
"port": 6003,
"keyFileName": "keys",
"deviceTimeout": 20,
"authenticationTokens" : [],
}

View File

@ -3,40 +3,11 @@ import Foundation
struct Config { struct Config {
/// The port where the server runs /// The port where the server runs
let port: Int static let port = 6003
/// The name of the file in the `Resources` folder containing the device authentication token /// The name of the file in the `Resources` folder containing the device authentication token
let keyFileName: String static let keyFileName = "keys"
/// The seconds to wait for a response from the device /// The seconds to wait for a response from the device
let deviceTimeout: Int64 static let deviceTimeout: Int64 = 20
/// The authentication tokens to use for monitoring of the service
let authenticationTokens: Set<String>
}
extension Config: Codable {
}
extension Config {
init(loadFrom url: URL) throws {
guard FileManager.default.fileExists(atPath: url.path) else {
fatalError("No configuration file found")
}
let data: Data
do {
data = try Data(contentsOf: url)
} catch {
print("Failed to read config data: \(error)")
throw error
}
do {
self = try JSONDecoder().decode(Config.self, from: data)
} catch {
print("Failed to decode config data: \(error)")
throw error
}
}
} }

View File

@ -18,8 +18,6 @@ final class DeviceManager {
private var isOpeningNewConnection = false private var isOpeningNewConnection = false
private let deviceTimeout: Int64
/// Indicator for device availability /// Indicator for device availability
var deviceIsConnected: Bool { var deviceIsConnected: Bool {
deviceIsAuthenticated && !(connection?.isClosed ?? true) deviceIsAuthenticated && !(connection?.isClosed ?? true)
@ -28,10 +26,9 @@ final class DeviceManager {
/// A promise to finish the request once the device responds or times out /// A promise to finish the request once the device responds or times out
private var requestInProgress: EventLoopPromise<DeviceResponse>? private var requestInProgress: EventLoopPromise<DeviceResponse>?
init(deviceKey: Data, remoteKey: Data, deviceTimeout: Int64) { init(deviceKey: Data, remoteKey: Data) {
self.deviceKey = deviceKey self.deviceKey = deviceKey
self.remoteKey = remoteKey self.remoteKey = remoteKey
self.deviceTimeout = deviceTimeout
} }
// MARK: API // MARK: API
@ -50,7 +47,7 @@ final class DeviceManager {
} }
requestInProgress = eventLoop.makePromise(of: DeviceResponse.self) requestInProgress = eventLoop.makePromise(of: DeviceResponse.self)
socket.send(message.bytes, promise: nil) socket.send(message.bytes, promise: nil)
eventLoop.scheduleTask(in: .seconds(deviceTimeout)) { [weak self] in eventLoop.scheduleTask(in: .seconds(Config.deviceTimeout)) { [weak self] in
guard let promise = self?.requestInProgress else { guard let promise = self?.requestInProgress else {
return return
} }

View File

@ -1,5 +1,4 @@
import Vapor import Vapor
import Clairvoyant
var deviceManager: DeviceManager! var deviceManager: DeviceManager!
@ -10,46 +9,11 @@ enum ServerError: Error {
// configures your application // configures your application
public func configure(_ app: Application) throws { public func configure(_ app: Application) throws {
app.http.server.configuration.port = Config.port
let storageFolder = URL(fileURLWithPath: app.directory.resourcesDirectory) let storageFolder = URL(fileURLWithPath: app.directory.resourcesDirectory)
let logFolder = storageFolder.appendingPathComponent("logs") let keyFile = storageFolder.appendingPathComponent(Config.keyFileName)
let authContent: [Data] = try String(contentsOf: keyFile)
let accessManager = AccessTokenManager([])
let monitor = MetricObserver(
logFolder: logFolder,
accessManager: accessManager,
logMetricId: "sesame.log")
MetricObserver.standard = monitor
let status = Metric<ServerStatus>("sesame.status")
status.update(.initializing)
monitor.registerRoutes(app)
let configUrl = storageFolder.appendingPathComponent("config.json")
let config = try Config(loadFrom: configUrl)
config.authenticationTokens.map { $0.data(using: .utf8)! }.forEach(accessManager.add)
app.http.server.configuration.port = config.port
let keyFile = storageFolder.appendingPathComponent(config.keyFileName)
let (deviceKey, remoteKey) = try loadKeys(at: keyFile)
deviceManager = DeviceManager(deviceKey: deviceKey, remoteKey: remoteKey, deviceTimeout: config.deviceTimeout)
try routes(app)
// Gracefully shut down by closing potentially open socket
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + .seconds(5)) {
_ = app.server.onShutdown.always { _ in
deviceManager.removeDeviceConnection()
}
}
status.update(.nominal)
}
private func loadKeys(at url: URL) throws -> (deviceKey: Data, remoteKey: Data) {
let authContent: [Data] = try String(contentsOf: url)
.trimmingCharacters(in: .whitespacesAndNewlines) .trimmingCharacters(in: .whitespacesAndNewlines)
.components(separatedBy: "\n") .components(separatedBy: "\n")
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
@ -65,5 +29,15 @@ private func loadKeys(at url: URL) throws -> (deviceKey: Data, remoteKey: Data)
guard authContent.count == 2 else { guard authContent.count == 2 else {
throw ServerError.invalidAuthenticationFileContent throw ServerError.invalidAuthenticationFileContent
} }
return (deviceKey: authContent[0], remoteKey: authContent[1]) let deviceKey = authContent[0]
let remoteKey = authContent[1]
deviceManager = DeviceManager(deviceKey: deviceKey, remoteKey: remoteKey)
try routes(app)
// Gracefully shut down by closing potentially open socket
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + .seconds(5)) {
_ = app.server.onShutdown.always { _ in
deviceManager.removeDeviceConnection()
}
}
} }