Compare commits
No commits in common. "21a4f4ecae16658fdce28eae46c2057f77801733" and "4274dfde4ca307489e8967f6f1c57c303d4b3a2c" have entirely different histories.
21a4f4ecae
...
4274dfde4c
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,4 +2,3 @@
|
|||||||
Package.resolved
|
Package.resolved
|
||||||
.swiftpm
|
.swiftpm
|
||||||
.build
|
.build
|
||||||
Resources/config.json
|
|
||||||
|
@ -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
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"port": 6003,
|
|
||||||
"keyFileName": "keys",
|
|
||||||
"deviceTimeout": 20,
|
|
||||||
"authenticationTokens" : [],
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user