From 95b3bfc802b3f1e44a299745bafe5e360ebaa4ef Mon Sep 17 00:00:00 2001 From: Erik Little Date: Sat, 16 Sep 2017 11:55:06 -0400 Subject: [PATCH] more linux work --- Source/SocketIO/Engine/SocketEngine.swift | 122 +++++++++--------- Source/SocketIO/Engine/SocketEngineSpec.swift | 17 ++- .../Engine/SocketEngineWebsocket.swift | 93 +++++++++++++ 3 files changed, 169 insertions(+), 63 deletions(-) diff --git a/Source/SocketIO/Engine/SocketEngine.swift b/Source/SocketIO/Engine/SocketEngine.swift index 9995cb9..d043713 100644 --- a/Source/SocketIO/Engine/SocketEngine.swift +++ b/Source/SocketIO/Engine/SocketEngine.swift @@ -24,7 +24,11 @@ import Dispatch import Foundation +#if !os(Linux) import StarscreamSocketIO +#else +import WebSockets +#endif /// The class that handles the engine.io protocol and transports. /// See `SocketEnginePollable` and `SocketEngineWebsocket` for transport specific methods. @@ -60,6 +64,9 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll /// **Do not touch this directly** public var waitingForPost = false + /// The WebSocket for this engine. + public var ws: WebSocket? + /// `true` if this engine is closed. public private(set) var closed = false @@ -95,6 +102,17 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll /// If `true`, the engine is currently seeing whether it can upgrade to WebSockets. public private(set) var probing = false + /// Whether or not this engine uses secure transports + public private(set) var secure = false + + #if !os(Linux) + /// A custom security validator for Starscream. Useful for SSL pinning. + public private(set) var security: SSLSecurity? + #endif + + /// Whether or not to allow self signed certificates. + public private(set) var selfSigned = false + /// The URLSession that will be used for polling. public private(set) var session: URLSession? @@ -113,9 +131,6 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll /// If `true`, then the engine is currently in WebSockets mode. public private(set) var websocket = false - /// The WebSocket for this engine. - public private(set) var ws: WebSocket? - /// The client for this engine. public weak var client: SocketEngineClient? @@ -133,9 +148,6 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll private var pongsMissed = 0 private var pongsMissedMax = 0 private var probeWait = ProbeWaitQueue() - private var secure = false - private var security: SSLSecurity? - private var selfSigned = false // MARK: Initializers @@ -308,32 +320,6 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll return (urlPolling.url!, urlWebSocket.url!) } - private func createWebSocketAndConnect() { - ws?.delegate = nil // TODO this seems a bit defensive, is this really needed? - ws = WebSocket(url: urlWebSocketWithSid) - - if cookies != nil { - let headers = HTTPCookie.requestHeaderFields(with: cookies!) - for (key, value) in headers { - ws?.headers[key] = value - } - } - - if extraHeaders != nil { - for (headerName, value) in extraHeaders! { - ws?.headers[headerName] = value - } - } - - ws?.callbackQueue = engineQueue - ws?.enableCompression = compress - ws?.delegate = self - ws?.disableSSLCertValidation = selfSigned - ws?.security = security - - ws?.connect() - } - /// Called when an error happens during execution. Causes a disconnection. public func didError(reason: String) { DefaultSocketLogger.Logger.error("%@", type: SocketEngine.logType, args: reason) @@ -486,6 +472,44 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll } } + /// Called when a successful WebSocket connection is made. + public func handleWSConnect() { + if !forceWebsockets { + probing = true + probeWebSocket() + } else { + connected = true + probing = false + polling = false + } + } + + /// Called when the WebSocket disconnects. + public func handleWSDisconnect(error: NSError?) { + probing = false + + if closed { + client?.engineDidClose(reason: "Disconnect") + + return + } + + guard websocket else { + flushProbeWait() + + return + } + + connected = false + websocket = false + + if let reason = error?.localizedDescription { + didError(reason: reason) + } else { + client?.engineDidClose(reason: "Socket Disconnected") + } + } + /// Parses raw binary received from engine.io. /// /// - parameter data: The data to parse. @@ -601,45 +625,19 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll } } + #if !os(Linux) // MARK: Starscream delegate conformance /// Delegate method for connection. public func websocketDidConnect(socket: WebSocket) { - if !forceWebsockets { - probing = true - probeWebSocket() - } else { - connected = true - probing = false - polling = false - } + handleWSConnect() } /// Delegate method for disconnection. public func websocketDidDisconnect(socket: WebSocket, error: NSError?) { - probing = false - - if closed { - client?.engineDidClose(reason: "Disconnect") - - return - } - - guard websocket else { - flushProbeWait() - - return - } - - connected = false - websocket = false - - if let reason = error?.localizedDescription { - didError(reason: reason) - } else { - client?.engineDidClose(reason: "Socket Disconnected") - } + handleWSDisconnect(error: error) } + #endif } extension SocketEngine { diff --git a/Source/SocketIO/Engine/SocketEngineSpec.swift b/Source/SocketIO/Engine/SocketEngineSpec.swift index 89f068f..161f7c9 100644 --- a/Source/SocketIO/Engine/SocketEngineSpec.swift +++ b/Source/SocketIO/Engine/SocketEngineSpec.swift @@ -24,7 +24,11 @@ // import Foundation +#if !os(Linux) import StarscreamSocketIO +#else +import WebSockets +#endif /// Specifies a SocketEngine. @objc public protocol SocketEngineSpec { @@ -40,6 +44,9 @@ import StarscreamSocketIO /// The connect parameters sent during a connect. var connectParams: [String: Any]? { get set } + /// Whether or not to use WebSocket compression. + var compress: Bool { get } + /// An array of HTTPCookies that are sent during the connection. var cookies: [HTTPCookie]? { get } @@ -64,6 +71,14 @@ import StarscreamSocketIO /// If `true`, the engine is currently seeing whether it can upgrade to WebSockets. var probing: Bool { get } + /// Whether or not this engine uses secure transports + var secure: Bool { get } + + var security: SSLSecurity? { get } + + /// Whether or not to allow self signed certificates. + var selfSigned: Bool { get } + /// The session id for this engine. var sid: String { get } @@ -80,7 +95,7 @@ import StarscreamSocketIO var websocket: Bool { get } /// The WebSocket for this engine. - var ws: WebSocket? { get } + var ws: WebSocket? { get set } /// Creates a new engine. /// diff --git a/Source/SocketIO/Engine/SocketEngineWebsocket.swift b/Source/SocketIO/Engine/SocketEngineWebsocket.swift index 70b885c..c4eef2d 100644 --- a/Source/SocketIO/Engine/SocketEngineWebsocket.swift +++ b/Source/SocketIO/Engine/SocketEngineWebsocket.swift @@ -24,10 +24,22 @@ // import Foundation +#if !os(Linux) import StarscreamSocketIO +#else +import WebSockets +import Sockets +import TLS +#endif /// Protocol that is used to implement socket.io WebSocket support public protocol SocketEngineWebsocket : SocketEngineSpec, WebSocketDelegate { + /// Called when a successful WebSocket connection is made. + func handleWSConnect() + + /// Called when the WebSocket disconnects. + func handleWSDisconnect(error: NSError?) + /// Sends an engine.io message through the WebSocket transport. /// /// You shouldn't call this directly, instead call the `write` method on `SocketEngine`. @@ -40,6 +52,74 @@ public protocol SocketEngineWebsocket : SocketEngineSpec, WebSocketDelegate { // WebSocket methods extension SocketEngineWebsocket { + #if os(Linux) + func attachWebSocketHandlers() { + ws?.onText = {[weak self] ws, text in + guard let this = self else { return } + + this.parseEngineMessage(text) + } + + ws?.onBinary = {[weak self] ws, bin in + guard let this = self else { return } + + this.parseEngineData(Data(bytes: bin)) + } + + ws?.onClose = {[weak self] _, _, reason, clean in + guard let this = self else { return } + + this.handleWSDisconnect(error: nil) + } + } + #endif + + func createWebSocketAndConnect() { + #if !os(Linux) + ws?.delegate = nil // TODO this seems a bit defensive, is this really needed? + ws = WebSocket(url: urlWebSocketWithSid) + + if cookies != nil { + let headers = HTTPCookie.requestHeaderFields(with: cookies!) + for (key, value) in headers { + ws?.headers[key] = value + } + } + + if extraHeaders != nil { + for (headerName, value) in extraHeaders! { + ws?.headers[headerName] = value + } + } + + ws?.callbackQueue = engineQueue + ws?.enableCompression = compress + ws?.delegate = self + ws?.disableSSLCertValidation = selfSigned + ws?.security = security + + ws?.connect() + #else + let url = urlWebSocketWithSid + do { + let socket = try TCPInternetSocket(scheme: url.scheme ?? "http", + hostname: url.host ?? "localhost", + port: Port(url.port ?? 80)) + let stream = secure ? try TLS.InternetSocket(socket, TLS.Context(.client)) : socket + try WebSocket.background(to: connectURL, using: stream) {[weak self] ws in + guard let this = self else { return } + + this.ws = ws + + this.attachWebSocketHandlers() + this.handleWSConnect() + } + } catch { + DefaultSocketLogger.Logger.error("Error connecting socket", type: "SocketEngineWebsocket") + } + #endif + } + func probeWebSocket() { if ws?.isConnected ?? false { sendWebSocketMessage("probe", withType: .ping, withData: []) @@ -67,6 +147,7 @@ extension SocketEngineWebsocket { // MARK: Starscream delegate methods + #if !os(Linux) /// Delegate method for when a message is received. public func websocketDidReceiveMessage(socket: WebSocket, text: String) { parseEngineMessage(text) @@ -76,4 +157,16 @@ extension SocketEngineWebsocket { public func websocketDidReceiveData(socket: WebSocket, data: Data) { parseEngineData(data) } + #endif } + +#if os(Linux) +/// SSLSecurity does nothing on Linux. +public final class SSLSecurity { } + +extension WebSocket { + var isConnected: Bool { + return state == .open + } +} +#endif