refactor engine, don't re-add engine on reconnect, other improvements

This commit is contained in:
Erik 2015-12-01 15:17:18 -05:00
parent 87e12fd284
commit 57b252fa7d
3 changed files with 90 additions and 39 deletions

View File

@ -33,6 +33,8 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate {
public private(set) var ws: WebSocket? public private(set) var ws: WebSocket?
public weak var client: SocketEngineClient? public weak var client: SocketEngineClient?
private weak var sessionDelegate: NSURLSessionDelegate?
private typealias Probe = (msg: String, type: SocketEnginePacketType, data: [NSData]) private typealias Probe = (msg: String, type: SocketEnginePacketType, data: [NSData])
private typealias ProbeWaitQueue = [Probe] private typealias ProbeWaitQueue = [Probe]
@ -45,6 +47,7 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate {
private let url: String private let url: String
private let workQueue = NSOperationQueue() private let workQueue = NSOperationQueue()
private var connectParams: [String: AnyObject]?
private var closed = false private var closed = false
private var extraHeaders: [String: String]? private var extraHeaders: [String: String]?
private var fastUpgrade = false private var fastUpgrade = false
@ -64,12 +67,11 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate {
private var probing = false private var probing = false
private var probeWait = ProbeWaitQueue() private var probeWait = ProbeWaitQueue()
private var secure = false private var secure = false
private var session: NSURLSession! private var session: NSURLSession?
private var voipEnabled = false private var voipEnabled = false
private var waitingForPoll = false private var waitingForPoll = false
private var waitingForPost = false private var waitingForPost = false
private var websocketConnected = false private var websocketConnected = false
private(set) var connected = false private(set) var connected = false
private(set) var polling = true private(set) var polling = true
private(set) var websocket = false private(set) var websocket = false
@ -81,9 +83,7 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate {
for option in options { for option in options {
switch option { switch option {
case .SessionDelegate(let delegate): case .SessionDelegate(let delegate):
session = NSURLSession(configuration: .defaultSessionConfiguration(), sessionDelegate = delegate
delegate: delegate,
delegateQueue: workQueue)
case .ForcePolling(let force): case .ForcePolling(let force):
forcePolling = force forcePolling = force
case .ForceWebsockets(let force): case .ForceWebsockets(let force):
@ -102,12 +102,6 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate {
continue continue
} }
} }
if session == nil {
session = NSURLSession(configuration: .defaultSessionConfiguration(),
delegate: nil,
delegateQueue: workQueue)
}
} }
public convenience init(client: SocketEngineClient, url: String, options: NSDictionary?) { public convenience init(client: SocketEngineClient, url: String, options: NSDictionary?) {
@ -120,6 +114,35 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate {
closed = true closed = true
stopPolling() stopPolling()
} }
private func checkAndHandleEngineError(msg: String) {
guard let stringData = msg.dataUsingEncoding(NSUTF8StringEncoding,
allowLossyConversion: false) else { return }
do {
if let dict = try NSJSONSerialization.JSONObjectWithData(stringData,
options: NSJSONReadingOptions.MutableContainers) as? NSDictionary {
guard let code = dict["code"] as? Int else { return }
guard let error = dict["message"] as? String else { return }
switch code {
case 0: // Unknown transport
logAndError(error)
case 1: // Unknown sid. clear and retry connect
sid = ""
open(connectParams)
case 2: // Bad handshake request
logAndError(error)
case 3: // Bad request
logAndError(error)
default:
logAndError(error)
}
}
} catch {
DefaultSocketLogger.Logger.error("Got message: %@", type: logType, args: msg)
}
}
private func checkIfMessageIsBase64Binary(var message: String) -> Bool { private func checkIfMessageIsBase64Binary(var message: String) -> Bool {
if message.hasPrefix("b4") { if message.hasPrefix("b4") {
@ -143,6 +166,7 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate {
pingTimer?.invalidate() pingTimer?.invalidate()
closed = true closed = true
connected = false
if websocket { if websocket {
sendWebSocketMessage("", withType: .Close) sendWebSocketMessage("", withType: .Close)
@ -341,11 +365,18 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate {
client?.engineDidClose(reason) client?.engineDidClose(reason)
} }
} }
private func logAndError(error: String) {
DefaultSocketLogger.Logger.error(error, type: logType)
client?.didError(error)
}
public func open(opts: [String: AnyObject]? = nil) { public func open(opts: [String: AnyObject]? = nil) {
connectParams = opts
if connected { if connected {
DefaultSocketLogger.Logger.error("Tried to open while connected", type: logType) DefaultSocketLogger.Logger.error("Tried to open while connected", type: logType)
client?.didError("Tried to open while connected") client?.didError("Tried to open engine while connected")
return return
} }
@ -353,7 +384,7 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate {
DefaultSocketLogger.Logger.log("Starting engine", type: logType) DefaultSocketLogger.Logger.log("Starting engine", type: logType)
DefaultSocketLogger.Logger.log("Handshaking", type: logType) DefaultSocketLogger.Logger.log("Handshaking", type: logType)
closed = false resetEngine()
(urlPolling, urlWebSocket) = createURLs(opts) (urlPolling, urlWebSocket) = createURLs(opts)
@ -388,19 +419,15 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate {
private func parseEngineMessage(var message: String, fromPolling: Bool) { private func parseEngineMessage(var message: String, fromPolling: Bool) {
DefaultSocketLogger.Logger.log("Got message: %@", type: logType, args: message) DefaultSocketLogger.Logger.log("Got message: %@", type: logType, args: message)
func handleOther(msg: String) -> SocketEnginePacketType {
if checkIfMessageIsBase64Binary(msg) {
return .Noop
} else {
DefaultSocketLogger.Logger.error("Got message: %@", type: logType, args: msg)
return .Close
}
}
let reader = SocketStringReader(message: message) let reader = SocketStringReader(message: message)
let type = SocketEnginePacketType(rawValue: Int(reader.currentCharacter) ?? -1) guard let type = SocketEnginePacketType(rawValue: Int(reader.currentCharacter) ?? -1) else {
?? handleOther(message) if !checkIfMessageIsBase64Binary(message) {
checkAndHandleEngineError(message)
}
return
}
if fromPolling && type != .Noop { if fromPolling && type != .Noop {
fixDoubleUTF8(&message) fixDoubleUTF8(&message)
@ -429,7 +456,25 @@ public final class SocketEngine: NSObject, SocketEngineSpec, WebSocketDelegate {
sendWebSocketMessage("probe", withType: .Ping) sendWebSocketMessage("probe", withType: .Ping)
} }
} }
private func resetEngine() {
closed = false
connected = false
fastUpgrade = false
polling = true
probing = false
invalidated = false
session = NSURLSession(configuration: .defaultSessionConfiguration(),
delegate: sessionDelegate,
delegateQueue: workQueue)
sid = ""
waitingForPoll = false
waitingForPost = false
websocket = false
websocketConnected = false
}
/// Send an engine message (4) /// Send an engine message (4)
public func send(msg: String, withData datas: [NSData]) { public func send(msg: String, withData datas: [NSData]) {
if probing { if probing {
@ -525,13 +570,14 @@ extension SocketEngine {
private func doRequest(req: NSMutableURLRequest, private func doRequest(req: NSMutableURLRequest,
withCallback callback: (NSData?, NSURLResponse?, NSError?) -> Void) { withCallback callback: (NSData?, NSURLResponse?, NSError?) -> Void) {
if !polling || closed || invalidated { if !polling || closed || invalidated {
DefaultSocketLogger.Logger.error("Tried to do polling request when not supposed to", type: logType)
return return
} }
DefaultSocketLogger.Logger.log("Doing polling request", type: logType) DefaultSocketLogger.Logger.log("Doing polling request", type: logType)
req.cachePolicy = .ReloadIgnoringLocalAndRemoteCacheData req.cachePolicy = .ReloadIgnoringLocalAndRemoteCacheData
session.dataTaskWithRequest(req, completionHandler: callback).resume() session?.dataTaskWithRequest(req, completionHandler: callback).resume()
} }
private func doLongPoll(req: NSMutableURLRequest) { private func doLongPoll(req: NSMutableURLRequest) {
@ -684,7 +730,7 @@ extension SocketEngine {
private func stopPolling() { private func stopPolling() {
invalidated = true invalidated = true
session.finishTasksAndInvalidate() session?.finishTasksAndInvalidate()
} }
} }

View File

@ -30,6 +30,7 @@ public final class SocketIOClient: NSObject, SocketEngineClient {
public private(set) var engine: SocketEngineSpec? public private(set) var engine: SocketEngineSpec?
public private(set) var status = SocketIOClientStatus.NotConnected public private(set) var status = SocketIOClientStatus.NotConnected
public var forceNew = false
public var nsp = "/" public var nsp = "/"
public var options: Set<SocketIOClientOption> public var options: Set<SocketIOClientOption>
public var reconnects = true public var reconnects = true
@ -88,6 +89,8 @@ public final class SocketIOClient: NSObject, SocketEngineClient {
DefaultSocketLogger.Logger = logger DefaultSocketLogger.Logger = logger
case .HandleQueue(let queue): case .HandleQueue(let queue):
handleQueue = queue handleQueue = queue
case .ForceNew(let force):
forceNew = force
default: default:
continue continue
} }
@ -109,7 +112,6 @@ public final class SocketIOClient: NSObject, SocketEngineClient {
deinit { deinit {
DefaultSocketLogger.Logger.log("Client is being deinit", type: logType) DefaultSocketLogger.Logger.log("Client is being deinit", type: logType)
DefaultSocketLogger.Logger.log = false
engine?.close() engine?.close()
} }
@ -154,21 +156,21 @@ public final class SocketIOClient: NSObject, SocketEngineClient {
assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)") assert(timeoutAfter >= 0, "Invalid timeout: \(timeoutAfter)")
guard status != .Connected else { guard status != .Connected else {
return DefaultSocketLogger.Logger.log("Tried connecting on an already connected socket",
}
if status == .Closed {
DefaultSocketLogger.Logger.log("Warning! This socket was previously closed. This might be dangerous!",
type: logType) type: logType)
}
status = SocketIOClientStatus.Connecting
addEngine().open(connectParams)
guard timeoutAfter != 0 else {
return return
} }
status = .Connecting
if engine == nil || forceNew {
addEngine().open(connectParams)
} else {
engine?.open(connectParams)
}
guard timeoutAfter != 0 else { return }
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeoutAfter) * Int64(NSEC_PER_SEC)) let time = dispatch_time(DISPATCH_TIME_NOW, Int64(timeoutAfter) * Int64(NSEC_PER_SEC))
dispatch_after(time, handleQueue) {[weak self] in dispatch_after(time, handleQueue) {[weak self] in

View File

@ -32,6 +32,7 @@ public enum SocketIOClientOption: ClientOption {
case ConnectParams([String: AnyObject]) case ConnectParams([String: AnyObject])
case Cookies([NSHTTPCookie]) case Cookies([NSHTTPCookie])
case ExtraHeaders([String: String]) case ExtraHeaders([String: String])
case ForceNew(Bool)
case ForcePolling(Bool) case ForcePolling(Bool)
case ForceWebsockets(Bool) case ForceWebsockets(Bool)
case HandleQueue(dispatch_queue_t) case HandleQueue(dispatch_queue_t)
@ -68,6 +69,8 @@ public enum SocketIOClientOption: ClientOption {
return .ReconnectAttempts(value as! Int) return .ReconnectAttempts(value as! Int)
case "reconnectWait" where value is Int: case "reconnectWait" where value is Int:
return .ReconnectWait(value as! Int) return .ReconnectWait(value as! Int)
case "forceNew" where value is Bool:
return .ForceNew(value as! Bool)
case "forcePolling" where value is Bool: case "forcePolling" where value is Bool:
return .ForcePolling(value as! Bool) return .ForcePolling(value as! Bool)
case "forceWebsockets" where value is Bool: case "forceWebsockets" where value is Bool: