From 8bebc3c1594e78f089a262ef7783d9ca3525aed0 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 3 Mar 2015 20:53:57 -0500 Subject: [PATCH 01/34] start work on adding polling --- SwiftIO/SocketEngine.swift | 190 +++++++++++++++++++++++++++++++++++ SwiftIO/SocketIOClient.swift | 22 +++- 2 files changed, 207 insertions(+), 5 deletions(-) create mode 100644 SwiftIO/SocketEngine.swift diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift new file mode 100644 index 00000000..f9198be1 --- /dev/null +++ b/SwiftIO/SocketEngine.swift @@ -0,0 +1,190 @@ +// +// SocketEngine.swift +// Socket.IO-Swift +// +// Created by Erik Little on 3/3/15. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import Foundation + +private enum PacketType: String { + case OPEN = "0" + case CLOSE = "1" + case PING = "2" + case PONG = "3" + case MESSAGE = "4" + case UPGRADE = "5" + case NOOP = "6" +} + +class SocketEngine: NSObject, SRWebSocketDelegate { + unowned let client:SocketIOClient + private var pingTimer:NSTimer? + private let pollingQueue = NSOperationQueue() + private var _polling = true + private var _websocket = false + private var websocketConnected = false + var pingInterval:Int? + var polling:Bool { + return self._polling + } + var sid = "" + var websocket:Bool { + return self._websocket + } + var ws:SRWebSocket? + + init(client:SocketIOClient) { + self.client = client + } + + func open(opts:[String: AnyObject]? = nil) { + var url:String + var urlPolling:String + var urlWebSocket:String + + if self.client.secure { + url = "\(self.client.socketURL)/socket.io/?transport=" + urlPolling = "https://" + url + "polling" + urlWebSocket = "wss://" + url + "websocket" + } else { + url = "\(self.client.socketURL)/socket.io/?transport=" + urlPolling = "http://" + url + "polling" + urlWebSocket = "ws://" + url + "websocket" + } + + let reqPolling = NSURLRequest(URL: NSURL(string: urlPolling)!) + + NSURLConnection.sendAsynchronousRequest(reqPolling, queue: self.pollingQueue) {[weak self] res, data, err in + var err:NSError? + + if self == nil || err != nil || data == nil { + println("Error") + println(err) + exit(1) + } + + let sub = data.subdataWithRange(NSMakeRange(5, data.length - 5)) + + if let json = NSJSONSerialization.JSONObjectWithData(sub, + options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { + println(json) + if let sid = json["sid"] as? String { + self?.sid = sid + + self?.ws = SRWebSocket(URL: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) + self?.ws?.delegate = self + self?.ws?.open() + + } else { + NSLog("Error handshaking") + return + } + + if let pingInterval = json["pingInterval"] as? Int { + self?.pingInterval = pingInterval / 1000 + } + } + } + } + + func handlePollingResponse(str:String) { + // TODO add polling + } + + func parseWebSocketMessage(message:AnyObject?) { + if !(message is String) { + return + } + + var strMessage = RegexMutable(message as String) + + // We should upgrade + if strMessage == "3probe" { + self.upgradeTransport() + return + } + + let type = strMessage["(\\d)"].matches()[0] + + if type != PacketType.MESSAGE.rawValue { + // TODO Handle other packets + return + } + } + + func probeWebSocket() { + if self.websocketConnected { + self.ws?.send("2probe") + } + } + + func sendPing() { + if self.websocketConnected { + self.ws?.send(PacketType.PING.rawValue) + } + } + + // Starts the ping timer + private func startPingTimer() { + if self.pingInterval == nil { + return + } + + dispatch_async(dispatch_get_main_queue()) { + self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(self.pingInterval!), target: self, + selector: Selector("sendPing"), userInfo: nil, repeats: true) + } + } + + private func upgradeTransport() { + if self.websocketConnected { + self.ws?.send(PacketType.UPGRADE.rawValue) + } + } + + // Called when a message is recieved + func webSocket(webSocket:SRWebSocket!, didReceiveMessage message:AnyObject?) { + // println(message) + + self.parseWebSocketMessage(message) + } + + // Called when the socket is opened + func webSocketDidOpen(webSocket:SRWebSocket!) { + println("socket opened") + self.startPingTimer() + self.websocketConnected = true + self.probeWebSocket() + } + + // Called when the socket is closed + func webSocket(webSocket:SRWebSocket!, didCloseWithCode code:Int, reason:String!, wasClean:Bool) { + println("socket closed") + self.pingTimer?.invalidate() + self.websocketConnected = false + } + + // Called when an error occurs. + func webSocket(webSocket:SRWebSocket!, didFailWithError error:NSError!) { + self.pingTimer?.invalidate() + self.websocketConnected = false + } +} \ No newline at end of file diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index b8350921..7cd4423f 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -25,6 +25,7 @@ import Foundation class SocketIOClient: NSObject, SRWebSocketDelegate { + let engine:SocketEngine! let socketURL:NSMutableString! let ackQueue = dispatch_queue_create("ackQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) @@ -39,7 +40,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { private var lastSocketMessage:SocketEvent? private var paramConnect = false private var pingTimer:NSTimer! - private var secure = false + private var _secure = false var closed = false var connected = false var connecting = false @@ -49,13 +50,16 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { var reconnecting = false var reconnectAttempts = -1 var reconnectWait = 10 + var secure:Bool { + return self._secure + } var sid:String? init(socketURL:String, opts:[String: AnyObject]? = nil) { var mutURL = RegexMutable(socketURL) if mutURL["https://"].matches().count != 0 { - self.secure = true + self._secure = true } mutURL = mutURL["http://"] ~= "" @@ -81,6 +85,10 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.nsp = nsp } } + + super.init() + + self.engine = SocketEngine(client: self) } // Closes the socket @@ -144,7 +152,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } private func createConnectURL() -> String { - if self.secure { + if self._secure { return "wss://\(self.socketURL)/socket.io/?transport=websocket" } else { return "ws://\(self.socketURL)/socket.io/?transport=websocket" @@ -313,7 +321,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } } - private func joinNamespace() { + func joinNamespace() { if self.nsp != nil { self.io?.send("40/\(self.nsp!)") } @@ -891,4 +899,8 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.tryReconnect(triesLeft: self.reconnectAttempts) } } -} + + func testEngine() { + self.engine.open() + } +} \ No newline at end of file From 5cc42be7091b2fbabc1ac27dff85864a2f5c5b2f Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 3 Mar 2015 21:38:11 -0500 Subject: [PATCH 02/34] start moving websocket stuff to socketengine --- SwiftIO/SocketEngine.swift | 16 ++++++---- SwiftIO/SocketIOClient.swift | 59 ++++++++++++++++++------------------ 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index f9198be1..007f5e7a 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -56,16 +56,14 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } func open(opts:[String: AnyObject]? = nil) { - var url:String + var url = "\(self.client.socketURL)/socket.io/?transport=" var urlPolling:String var urlWebSocket:String if self.client.secure { - url = "\(self.client.socketURL)/socket.io/?transport=" urlPolling = "https://" + url + "polling" urlWebSocket = "wss://" + url + "websocket" } else { - url = "\(self.client.socketURL)/socket.io/?transport=" urlPolling = "http://" + url + "polling" urlWebSocket = "ws://" + url + "websocket" } @@ -109,12 +107,15 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // TODO add polling } - func parseWebSocketMessage(message:AnyObject?) { - if !(message is String) { + func parseWebSocketMessage(var message:AnyObject?) { + if let data = message as? NSData { + // Strip off message type + self.client.parseSocketMessage(data.subdataWithRange(NSMakeRange(1, data.length - 1))) return } - var strMessage = RegexMutable(message as String) + var message = message as String + var strMessage = RegexMutable(message) // We should upgrade if strMessage == "3probe" { @@ -128,6 +129,9 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // TODO Handle other packets return } + + message.removeAtIndex(message.startIndex) + self.client.parseSocketMessage(message) } func probeWebSocket() { diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 7cd4423f..77ff582f 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -87,7 +87,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } super.init() - + self.engine = SocketEngine(client: self) } @@ -506,7 +506,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } // Parses messages recieved - private func parseSocketMessage(message:AnyObject?) { + func parseSocketMessage(message:AnyObject?) { if message == nil { return } @@ -515,41 +515,39 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if let stringMessage = message as? String { // Check for successful namepsace connect + + if stringMessage == "0" { + // connected + self.closed = false + self.connecting = false + self.reconnecting = false + self.connected = true + + if self.nsp != nil { + // Join namespace + self.joinNamespace() + return + } + + // Don't handle as internal because something crazy could happen where + // we disconnect before it's handled + self.handleEvent("connect", data: nil) + } if self.nsp != nil { - if stringMessage == "40/\(self.nsp!)" { + if stringMessage == "0/\(self.nsp!)" { self.handleEvent("connect", data: nil) return } } - /** - Begin check for socket info frame - **/ - var mutMessage = RegexMutable(stringMessage) - var setup:String! - let messageData = mutMessage["(\\d*)(\\{.*\\})?"].groups() - if messageData != nil && messageData[1] == "0" { - setup = messageData[2] - let data = setup.dataUsingEncoding(NSUTF8StringEncoding)! - var jsonError:NSError? - - if let json:AnyObject? = NSJSONSerialization.JSONObjectWithData(data, - options: nil, error: &jsonError) { - self.sid = json!["sid"] as? String - self.startPingTimer(interval: (json!["pingInterval"] as Int) / 1000) - return - } - } - /** - End check for socket info frame - **/ + var mutMessage = RegexMutable(stringMessage) /** Begin check for message **/ let messageGroups = mutMessage["(\\d*)\\/?(\\w*)?,?(\\d*)?(\\[.*\\])?"].groups() - if messageGroups[1].hasPrefix("42") { + if messageGroups[1].hasPrefix("2") { var mesNum = messageGroups[1] var ackNum:String var namespace:String? @@ -558,7 +556,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { if messageGroups[3] != "" { ackNum = messageGroups[3] } else { - let range = Range(start: mesNum.startIndex, end: advance(mesNum.startIndex, 2)) + let range = Range(start: mesNum.startIndex, end: advance(mesNum.startIndex, 1)) mesNum.replaceRange(range, with: "") ackNum = mesNum } @@ -581,6 +579,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { data = messageInternals[2] } + println(data) // It would be nice if socket.io only allowed one thing // per message, but alas, it doesn't. if let parsed:AnyObject = SocketIOClient.parseData(data) { @@ -623,7 +622,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } return } - } else if messageGroups[1].hasPrefix("43") { + } else if messageGroups[1].hasPrefix("3") { let arr = Array(messageGroups[1]) var ackNum:String let nsp = messageGroups[2] @@ -677,7 +676,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { return } - if binaryGroup[1].hasPrefix("45") { + if binaryGroup[1].hasPrefix("5") { // println(binaryGroup) var ackNum:String var event:String @@ -718,9 +717,9 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } self.lastSocketMessage = mes - } else if binaryGroup[1].hasPrefix("46") { + } else if binaryGroup[1].hasPrefix("6") { let messageType = RegexMutable(binaryGroup[1]) - let numberOfPlaceholders = (messageType["46"] ~= "") as String + let numberOfPlaceholders = (messageType["6"] ~= "") as String var ackNum:String var nsp:String From 7e0549611fdf63ab1f7a7dbc5f01dcc90f5b87dd Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 3 Mar 2015 22:13:28 -0500 Subject: [PATCH 03/34] move more things to socketengine. reconnecting broken --- SwiftIO/SocketEngine.swift | 55 +++++++++++++++++++++++-- SwiftIO/SocketEvent.swift | 27 ++++++------ SwiftIO/SocketIOClient.swift | 80 +++++++----------------------------- 3 files changed, 80 insertions(+), 82 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 007f5e7a..58bdc3d2 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -55,7 +55,14 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.client = client } - func open(opts:[String: AnyObject]? = nil) { + func close() { + if websocketConnected { + self.ws?.send(PacketType.MESSAGE.rawValue + PacketType.CLOSE.rawValue) + self.ws?.close() + } + } + + private func createURLs(params:[String: AnyObject]? = nil) -> (NSURL, NSURL) { var url = "\(self.client.socketURL)/socket.io/?transport=" var urlPolling:String var urlWebSocket:String @@ -68,7 +75,31 @@ class SocketEngine: NSObject, SRWebSocketDelegate { urlWebSocket = "ws://" + url + "websocket" } - let reqPolling = NSURLRequest(URL: NSURL(string: urlPolling)!) + if params != nil { + for (key, value) in params! { + let keyEsc = key.stringByAddingPercentEncodingWithAllowedCharacters( + NSCharacterSet.URLHostAllowedCharacterSet())! + urlPolling += "&\(keyEsc)=" + urlWebSocket += "&\(keyEsc)=" + + if value is String { + let valueEsc = (value as String).stringByAddingPercentEncodingWithAllowedCharacters( + NSCharacterSet.URLHostAllowedCharacterSet())! + urlPolling += "\(valueEsc)" + urlWebSocket += "\(valueEsc)" + } else { + urlPolling += "\(value)" + urlWebSocket += "\(value)" + } + } + } + + return (NSURL(string: urlPolling)!, NSURL(string: urlWebSocket)!) + } + + func open(opts:[String: AnyObject]? = nil) { + let (urlPolling, urlWebSocket) = self.createURLs(params: opts) + let reqPolling = NSURLRequest(URL: urlPolling) NSURLConnection.sendAsynchronousRequest(reqPolling, queue: self.pollingQueue) {[weak self] res, data, err in var err:NSError? @@ -87,7 +118,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if let sid = json["sid"] as? String { self?.sid = sid - self?.ws = SRWebSocket(URL: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) + self?.ws = SRWebSocket(URL: urlWebSocket.URLByAppendingPathComponent("&sid=\(self!.sid)")) self?.ws?.delegate = self self?.ws?.open() @@ -140,6 +171,18 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } + func send(msg:AnyObject) { + if self.websocketConnected { + if !(msg is NSData) { + self.ws?.send("\(PacketType.MESSAGE.rawValue)\(msg)") + } else { + self.ws?.send(msg) + } + } else { + // TODO handle polling + } + } + func sendPing() { if self.websocketConnected { self.ws?.send(PacketType.PING.rawValue) @@ -184,11 +227,17 @@ class SocketEngine: NSObject, SRWebSocketDelegate { println("socket closed") self.pingTimer?.invalidate() self.websocketConnected = false + + // Temp + self.client.webSocket(webSocket, didCloseWithCode: code, reason: reason, wasClean: wasClean) } // Called when an error occurs. func webSocket(webSocket:SRWebSocket!, didFailWithError error:NSError!) { self.pingTimer?.invalidate() self.websocketConnected = false + + // Temp + self.client.webSocket(webSocket, didFailWithError: error) } } \ No newline at end of file diff --git a/SwiftIO/SocketEvent.swift b/SwiftIO/SocketEvent.swift index 26d84995..7f5f1339 100644 --- a/SwiftIO/SocketEvent.swift +++ b/SwiftIO/SocketEvent.swift @@ -74,29 +74,29 @@ class SocketEvent { if !hasBinary { if nsp == nil { if ack == nil { - message = "42[\"\(event)\"" + message = "2[\"\(event)\"" } else { - message = "42\(ack!)[\"\(event)\"" + message = "2\(ack!)[\"\(event)\"" } } else { if ack == nil { - message = "42/\(nsp!),[\"\(event)\"" + message = "2/\(nsp!),[\"\(event)\"" } else { - message = "42/\(nsp!),\(ack!)[\"\(event)\"" + message = "2/\(nsp!),\(ack!)[\"\(event)\"" } } } else { if nsp == nil { if ack == nil { - message = "45\(datas)-[\"\(event)\"" + message = "5\(datas)-[\"\(event)\"" } else { - message = "45\(datas)-\(ack!)[\"\(event)\"" + message = "5\(datas)-\(ack!)[\"\(event)\"" } } else { if ack == nil { - message = "45\(datas)-/\(nsp!),[\"\(event)\"" + message = "5\(datas)-/\(nsp!),[\"\(event)\"" } else { - message = "45\(datas)-/\(nsp!),\(ack!)[\"\(event)\"" + message = "5\(datas)-/\(nsp!),\(ack!)[\"\(event)\"" } } } @@ -110,15 +110,15 @@ class SocketEvent { if ackType == 3 { if nsp == "/" { - msg = "43\(ack)[" + msg = "3\(ack)[" } else { - msg = "43/\(nsp),\(ack)[" + msg = "3/\(nsp),\(ack)[" } } else { if nsp == "/" { - msg = "46\(binary)-\(ack)[" + msg = "6\(binary)-\(ack)[" } else { - msg = "46\(binary)-/\(nsp),\(ack)[" + msg = "6\(binary)-/\(nsp),\(ack)[" } } @@ -159,6 +159,7 @@ class SocketEvent { if message != "" { message.removeAtIndex(message.endIndex.predecessor()) } + return message + "]" } @@ -251,4 +252,4 @@ class SocketEvent { return false } -} +} \ No newline at end of file diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 77ff582f..4f38b9e9 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -24,7 +24,7 @@ import Foundation -class SocketIOClient: NSObject, SRWebSocketDelegate { +class SocketIOClient: NSObject { let engine:SocketEngine! let socketURL:NSMutableString! let ackQueue = dispatch_queue_create("ackQueue".cStringUsingEncoding(NSUTF8StringEncoding), @@ -44,7 +44,6 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { var closed = false var connected = false var connecting = false - var io:SRWebSocket? var nsp:String? var reconnects = true var reconnecting = false @@ -97,49 +96,20 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { self.closed = true self.connecting = false self.connected = false - self.io?.send("41") - self.io?.close() + self.engine?.close() } // Connects to the server func connect() { - self.connectWithURL(self.createConnectURL()) + self.engine.open() } // Connect to the server using params func connectWithParams(params:[String: AnyObject]) { self.params = params self.paramConnect = true - var endpoint = self.createConnectURL() - - for (key, value) in params { - let keyEsc = key.stringByAddingPercentEncodingWithAllowedCharacters( - NSCharacterSet.URLHostAllowedCharacterSet())! - endpoint += "&\(keyEsc)=" - - if value is String { - let valueEsc = (value as String).stringByAddingPercentEncodingWithAllowedCharacters( - NSCharacterSet.URLHostAllowedCharacterSet())! - endpoint += "\(valueEsc)" - } else { - endpoint += "\(value)" - } - } - - self.connectWithURL(endpoint) - } - - private func connectWithURL(url:String) { - if self.closed { - println("Warning: This socket was previvously closed. Reopening could be dangerous. Be careful.") - } - - self.connecting = true - self.closed = false - self.io = SRWebSocket(URL: NSURL(string: url)) - self.io?.delegate = self - self.io?.open() + self.engine.open(opts: params) } // Creates a binary message, ready for sending @@ -151,14 +121,6 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { return mutData } - private func createConnectURL() -> String { - if self._secure { - return "wss://\(self.socketURL)/socket.io/?transport=websocket" - } else { - return "ws://\(self.socketURL)/socket.io/?transport=websocket" - } - } - // Sends a message with multiple args // If a message contains binary we have to send those // seperately. @@ -215,9 +177,9 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { hasBinary: true, withDatas: emitDatas.count, toNamespace: self.nsp, wantsAck: self.currentAck) } - self.io?.send(str) + self.engine?.send(str) for data in emitDatas { - self.io?.send(data) + self.engine?.send(data) } } else { if !ack { @@ -228,7 +190,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { withDatas: 0, toNamespace: self.nsp, wantsAck: self.currentAck) } - self.io?.send(str) + self.engine?.send(str) } } @@ -251,7 +213,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { withAckType: 3, withNsp: self!.nsp!) } - self?.io?.send(str) + self?.engine?.send(str) } else { if self?.nsp == nil { str = SocketEvent.createAck(ack, withArgs: items, @@ -261,9 +223,9 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { withAckType: 6, withNsp: self!.nsp!, withBinary: emitDatas.count) } - self?.io?.send(str) + self?.engine?.send(str) for data in emitDatas { - self?.io?.send(data) + self?.engine?.send(data) } } } @@ -321,9 +283,10 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } } + // Should be removed and moved to SocketEngine func joinNamespace() { if self.nsp != nil { - self.io?.send("40/\(self.nsp!)") + self.engine?.send("0/\(self.nsp!)") } } @@ -579,7 +542,6 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { data = messageInternals[2] } - println(data) // It would be nice if socket.io only allowed one thing // per message, but alas, it doesn't. if let parsed:AnyObject = SocketIOClient.parseData(data) { @@ -632,7 +594,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } if nsp == "" { - ackNum = String(arr[2...arr.count-1]) + ackNum = String(arr[1...arr.count-1]) } else { ackNum = messageGroups[3] } @@ -694,7 +656,7 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { ackNum = "" } - numberOfPlaceholders = (messageType["45"] ~= "") as String + numberOfPlaceholders = (messageType["5"] ~= "") as String event = (RegexMutable(binaryGroup[4])["\""] ~= "") as String mutMessageObject = RegexMutable(binaryGroup[5]) @@ -787,20 +749,6 @@ class SocketIOClient: NSObject, SRWebSocketDelegate { } } - func sendPing() { - if self.connected { - self.io?.send("2") - } - } - - // Starts the ping timer - private func startPingTimer(#interval:Int) { - dispatch_async(dispatch_get_main_queue()) { - self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(interval), target: self, - selector: Selector("sendPing"), userInfo: nil, repeats: true) - } - } - // We lost connection and should attempt to reestablish private func tryReconnect(var #triesLeft:Int) { if triesLeft != -1 && triesLeft <= 0 { From 16fd0436328003a73d266c7cdc6fd313cbd1e10a Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 3 Mar 2015 22:39:37 -0500 Subject: [PATCH 04/34] fix reconnect --- SwiftIO/SocketEngine.swift | 11 +++++++---- SwiftIO/SocketIOClient.swift | 6 +----- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 58bdc3d2..a3c3a90b 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -103,18 +103,21 @@ class SocketEngine: NSObject, SRWebSocketDelegate { NSURLConnection.sendAsynchronousRequest(reqPolling, queue: self.pollingQueue) {[weak self] res, data, err in var err:NSError? - - if self == nil || err != nil || data == nil { + if self == nil { + return + } else if err != nil || data == nil { println("Error") println(err) - exit(1) + if !self!.client.reconnecting { + self?.client.tryReconnect(triesLeft: self!.client.reconnectAttempts) + } + return } let sub = data.subdataWithRange(NSMakeRange(5, data.length - 5)) if let json = NSJSONSerialization.JSONObjectWithData(sub, options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { - println(json) if let sid = json["sid"] as? String { self?.sid = sid diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 4f38b9e9..ff3f622e 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -750,7 +750,7 @@ class SocketIOClient: NSObject { } // We lost connection and should attempt to reestablish - private func tryReconnect(var #triesLeft:Int) { + func tryReconnect(var #triesLeft:Int) { if triesLeft != -1 && triesLeft <= 0 { self.connecting = false self.reconnects = false @@ -846,8 +846,4 @@ class SocketIOClient: NSObject { self.tryReconnect(triesLeft: self.reconnectAttempts) } } - - func testEngine() { - self.engine.open() - } } \ No newline at end of file From 6e4aa4eee40c16c9540d40295edc529e9cb61359 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 09:12:51 -0500 Subject: [PATCH 05/34] work on polling heartbeat --- SwiftIO/SocketEngine.swift | 148 +++++++++++++++++++++++++++++-------- 1 file changed, 117 insertions(+), 31 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index a3c3a90b..ecd6f770 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -36,8 +36,9 @@ private enum PacketType: String { class SocketEngine: NSObject, SRWebSocketDelegate { unowned let client:SocketIOClient - private var pingTimer:NSTimer? private let pollingQueue = NSOperationQueue() + private var pingTimer:NSTimer? + private var pollingTimer:NSTimer? private var _polling = true private var _websocket = false private var websocketConnected = false @@ -46,6 +47,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return self._polling } var sid = "" + var urlPolling:String? + var urlWebSocket:String? var websocket:Bool { return self._websocket } @@ -56,16 +59,20 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } func close() { + self.pingTimer?.invalidate() + self.pollingTimer?.invalidate() + if websocketConnected { self.ws?.send(PacketType.MESSAGE.rawValue + PacketType.CLOSE.rawValue) self.ws?.close() } } - private func createURLs(params:[String: AnyObject]? = nil) -> (NSURL, NSURL) { + private func createURLs(params:[String: AnyObject]? = nil) -> (String, String) { var url = "\(self.client.socketURL)/socket.io/?transport=" var urlPolling:String var urlWebSocket:String + let time = Int(NSDate().timeIntervalSince1970 * 1000) if self.client.secure { urlPolling = "https://" + url + "polling" @@ -94,46 +101,93 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - return (NSURL(string: urlPolling)!, NSURL(string: urlWebSocket)!) + return (urlPolling, urlWebSocket) } - func open(opts:[String: AnyObject]? = nil) { - let (urlPolling, urlWebSocket) = self.createURLs(params: opts) - let reqPolling = NSURLRequest(URL: urlPolling) + func doPoll() { + if self.urlPolling == nil || self.websocket { + return + } - NSURLConnection.sendAsynchronousRequest(reqPolling, queue: self.pollingQueue) {[weak self] res, data, err in - var err:NSError? + let time = Int(NSDate().timeIntervalSince1970) + let req = NSURLRequest(URL: NSURL(string: self.urlPolling! + "&t=\(time)-0&b64=1" + "&sid=\(self.sid)")!) + + NSURLConnection.sendAsynchronousRequest(req, queue: self.pollingQueue) {[weak self] res, data, err in if self == nil { return - } else if err != nil || data == nil { - println("Error") + } + + if err != nil { println(err) - if !self!.client.reconnecting { - self?.client.tryReconnect(triesLeft: self!.client.reconnectAttempts) - } return } - let sub = data.subdataWithRange(NSMakeRange(5, data.length - 5)) + let str = NSString(data: data, encoding: NSUTF8StringEncoding) - if let json = NSJSONSerialization.JSONObjectWithData(sub, - options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { - if let sid = json["sid"] as? String { - self?.sid = sid + println(str) + } + } + + func open(opts:[String: AnyObject]? = nil) { + let (urlPolling, urlWebSocket) = self.createURLs(params: opts) + + self.urlPolling = urlPolling + self.urlWebSocket = urlWebSocket + let time = Int(NSDate().timeIntervalSince1970) + let reqPolling = NSURLRequest(URL: NSURL(string: urlPolling + "&t=\(time)-0&b64=1")!) + + NSURLConnection.sendAsynchronousRequest(reqPolling, + queue: self.pollingQueue) {[weak self] res, data, err in + var err:NSError? + if self == nil { + return + } else if err != nil || data == nil { + println("Error") + println(err) + if !self!.client.reconnecting { + self?.client.tryReconnect(triesLeft: self!.client.reconnectAttempts) + } + return + } + + if let dataString = NSString(data: data, encoding: NSUTF8StringEncoding) { + var mutString = RegexMutable(dataString) + + let parsed = mutString["(\\d*):(\\d)(\\{.*\\})?"].groups() + + if parsed.count == 4 { + let length = parsed[1] + let type = parsed[2] + let jsonData = parsed[3].dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) - self?.ws = SRWebSocket(URL: urlWebSocket.URLByAppendingPathComponent("&sid=\(self!.sid)")) - self?.ws?.delegate = self - self?.ws?.open() + if type != "0" { + NSLog("Error handshaking") + return + } - } else { - NSLog("Error handshaking") - return + if let json = NSJSONSerialization.JSONObjectWithData(jsonData!, + options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { + if let sid = json["sid"] as? String { + self?.sid = sid + + self?.ws = SRWebSocket(URL: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) + self?.ws?.delegate = self + //self?.ws?.open() + + } else { + NSLog("Error handshaking") + return + } + + if let pingInterval = json["pingInterval"] as? Int { + self?.pingInterval = pingInterval / 1000 + } + } } - if let pingInterval = json["pingInterval"] as? Int { - self?.pingInterval = pingInterval / 1000 - } - } + self?.doPoll() + self?.startPingTimer() + } } } @@ -141,7 +195,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // TODO add polling } - func parseWebSocketMessage(var message:AnyObject?) { + func parseWebSocketMessage(message:AnyObject?) { if let data = message as? NSData { // Strip off message type self.client.parseSocketMessage(data.subdataWithRange(NSMakeRange(1, data.length - 1))) @@ -154,6 +208,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // We should upgrade if strMessage == "3probe" { self.upgradeTransport() + self.pollingTimer?.invalidate() return } @@ -182,22 +237,51 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.ws?.send(msg) } } else { - // TODO handle polling + self.sendPollMessage(msg) } } func sendPing() { - if self.websocketConnected { + if self.websocket { self.ws?.send(PacketType.PING.rawValue) + } else { + let time = Int(NSDate().timeIntervalSince1970) + var req = NSMutableURLRequest(URL: NSURL(string: self.urlPolling! + "&t=\(time)-0&b64=1" + "&sid=\(self.sid)")!) + let postStr = "1:\(PacketType.PING.rawValue)" + let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! + let postLength = "\(postData.length)" + + req.HTTPMethod = "POST" + req.setValue(postLength, forHTTPHeaderField: "Content-Length") + req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") + req.HTTPBody = postData + + NSURLConnection.sendAsynchronousRequest(req, queue: self.pollingQueue) {[weak self] res, data, err in + if err != nil { + println(err) + self?.pingTimer?.invalidate() + if !self!.client.reconnecting { + self?.client.tryReconnect(triesLeft: self!.client.reconnectAttempts) + } + return + } + + self?.doPoll() + } } } + func sendPollMessage(msg:AnyObject) { + + } + // Starts the ping timer private func startPingTimer() { if self.pingInterval == nil { return } + self.pingTimer?.invalidate() dispatch_async(dispatch_get_main_queue()) { self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(self.pingInterval!), target: self, selector: Selector("sendPing"), userInfo: nil, repeats: true) @@ -206,6 +290,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private func upgradeTransport() { if self.websocketConnected { + self._websocket = true + self._polling = false self.ws?.send(PacketType.UPGRADE.rawValue) } } From 7f22dc8f422f905b7d4cef77bc6b41016cee176f Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 10:04:26 -0500 Subject: [PATCH 06/34] send connect event on polling connect --- SwiftIO/SocketEngine.swift | 46 +++++++++++++++++++++--------------- SwiftIO/SocketIOClient.swift | 10 ++++---- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index ecd6f770..1efdfbf5 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -72,7 +72,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { var url = "\(self.client.socketURL)/socket.io/?transport=" var urlPolling:String var urlWebSocket:String - let time = Int(NSDate().timeIntervalSince1970 * 1000) if self.client.secure { urlPolling = "https://" + url + "polling" @@ -115,9 +114,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { NSURLConnection.sendAsynchronousRequest(req, queue: self.pollingQueue) {[weak self] res, data, err in if self == nil { return - } - - if err != nil { + } else if err != nil { println(err) return } @@ -125,6 +122,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { let str = NSString(data: data, encoding: NSUTF8StringEncoding) println(str) + + // TODO parse packets } } @@ -169,10 +168,12 @@ class SocketEngine: NSObject, SRWebSocketDelegate { options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { if let sid = json["sid"] as? String { self?.sid = sid - + self?.client.didConnect() + self?.client.handleEvent("connect", data: nil, isInternalMessage: false) + self?.ws = SRWebSocket(URL: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) self?.ws?.delegate = self - //self?.ws?.open() + self?.ws?.open() } else { NSLog("Error handshaking") @@ -208,7 +209,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // We should upgrade if strMessage == "3probe" { self.upgradeTransport() - self.pollingTimer?.invalidate() return } @@ -246,7 +246,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.ws?.send(PacketType.PING.rawValue) } else { let time = Int(NSDate().timeIntervalSince1970) - var req = NSMutableURLRequest(URL: NSURL(string: self.urlPolling! + "&t=\(time)-0&b64=1" + "&sid=\(self.sid)")!) + var req = NSMutableURLRequest(URL: NSURL(string: + self.urlPolling! + "&t=\(time)-0&b64=1" + "&sid=\(self.sid)")!) let postStr = "1:\(PacketType.PING.rawValue)" let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! let postLength = "\(postData.length)" @@ -256,17 +257,20 @@ class SocketEngine: NSObject, SRWebSocketDelegate { req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") req.HTTPBody = postData - NSURLConnection.sendAsynchronousRequest(req, queue: self.pollingQueue) {[weak self] res, data, err in - if err != nil { - println(err) - self?.pingTimer?.invalidate() - if !self!.client.reconnecting { - self?.client.tryReconnect(triesLeft: self!.client.reconnectAttempts) + NSURLConnection.sendAsynchronousRequest(req, + queue: self.pollingQueue) {[weak self] res, data, err in + if self == nil { + return + } else if err != nil { + println(err) + self?.pingTimer?.invalidate() + if !self!.client.reconnecting { + self?.client.tryReconnect(triesLeft: self!.client.reconnectAttempts) + } + return } - return - } - - self?.doPoll() + + self?.doPoll() } } } @@ -292,6 +296,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if self.websocketConnected { self._websocket = true self._polling = false + self.pollingTimer?.invalidate() self.ws?.send(PacketType.UPGRADE.rawValue) } } @@ -306,7 +311,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // Called when the socket is opened func webSocketDidOpen(webSocket:SRWebSocket!) { println("socket opened") - self.startPingTimer() self.websocketConnected = true self.probeWebSocket() } @@ -316,6 +320,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { println("socket closed") self.pingTimer?.invalidate() self.websocketConnected = false + self._websocket = false + self._polling = true // Temp self.client.webSocket(webSocket, didCloseWithCode: code, reason: reason, wasClean: wasClean) @@ -325,6 +331,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { func webSocket(webSocket:SRWebSocket!, didFailWithError error:NSError!) { self.pingTimer?.invalidate() self.websocketConnected = false + self._websocket = false + self._polling = true // Temp self.client.webSocket(webSocket, didFailWithError: error) diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index ff3f622e..3e54dcbd 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -39,7 +39,6 @@ class SocketIOClient: NSObject { private var handlers = [SocketEventHandler]() private var lastSocketMessage:SocketEvent? private var paramConnect = false - private var pingTimer:NSTimer! private var _secure = false var closed = false var connected = false @@ -92,7 +91,6 @@ class SocketIOClient: NSObject { // Closes the socket func close() { - self.pingTimer?.invalidate() self.closed = true self.connecting = false self.connected = false @@ -121,6 +119,12 @@ class SocketIOClient: NSObject { return mutData } + func didConnect() { + self.connected = true + self.connecting = false + self.reconnecting = false + } + // Sends a message with multiple args // If a message contains binary we have to send those // seperately. @@ -821,7 +825,6 @@ class SocketIOClient: NSObject { // Called when the socket is closed func webSocket(webSocket:SRWebSocket!, didCloseWithCode code:Int, reason:String!, wasClean:Bool) { - self.pingTimer?.invalidate() self.connected = false self.connecting = false if self.closed || !self.reconnects { @@ -835,7 +838,6 @@ class SocketIOClient: NSObject { // Called when an error occurs. func webSocket(webSocket:SRWebSocket!, didFailWithError error:NSError!) { - self.pingTimer?.invalidate() self.connected = false self.connecting = false self.handleEvent("error", data: error.localizedDescription, isInternalMessage: true) From 574e9d5f7eae16a5ea252c04aae5c066d7a8c10f Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 13:27:55 -0500 Subject: [PATCH 07/34] start work on parsing polling messages --- SwiftIO/SocketEngine.swift | 129 ++++++++++++++++++++++++++++++------- 1 file changed, 107 insertions(+), 22 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 1efdfbf5..58a3d812 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -24,6 +24,12 @@ import Foundation +extension String { + private var length:Int { + return Array(self).count + } +} + private enum PacketType: String { case OPEN = "0" case CLOSE = "1" @@ -40,6 +46,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private var pingTimer:NSTimer? private var pollingTimer:NSTimer? private var _polling = true + private var wait = false private var _websocket = false private var websocketConnected = false var pingInterval:Int? @@ -62,9 +69,11 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.pingTimer?.invalidate() self.pollingTimer?.invalidate() - if websocketConnected { + if self.websocket { self.ws?.send(PacketType.MESSAGE.rawValue + PacketType.CLOSE.rawValue) self.ws?.close() + } else { + // TODO handling polling } } @@ -104,26 +113,45 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } func doPoll() { - if self.urlPolling == nil || self.websocket { + if self.urlPolling == nil || self.websocket || self.wait { return } let time = Int(NSDate().timeIntervalSince1970) let req = NSURLRequest(URL: NSURL(string: self.urlPolling! + "&t=\(time)-0&b64=1" + "&sid=\(self.sid)")!) + self.wait = true NSURLConnection.sendAsynchronousRequest(req, queue: self.pollingQueue) {[weak self] res, data, err in if self == nil { return } else if err != nil { println(err) + self?.handlePollingFailed() return } - let str = NSString(data: data, encoding: NSUTF8StringEncoding) - - println(str) - - // TODO parse packets + if let str = NSString(data: data, encoding: NSUTF8StringEncoding) { + // println(str) + var mut = RegexMutable(str) + + let groups = mut["(\\d):(.*)"].groups() + if groups[1] == "" || groups[2] == "" { + return + } + + let type = groups[1] + var mutPart = RegexMutable(groups[0]) + + if type != "2" { + return + } + + mutPart["^2:40"] ~= "" + + self?.parsePollingMessage(mutPart) + self?.wait = false + self?.doPoll() + } } } @@ -141,12 +169,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if self == nil { return } else if err != nil || data == nil { - println("Error") println(err) - if !self!.client.reconnecting { - self?.client.tryReconnect(triesLeft: self!.client.reconnectAttempts) - } + self?.handlePollingFailed() return + } if let dataString = NSString(data: data, encoding: NSUTF8StringEncoding) { @@ -170,10 +196,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.sid = sid self?.client.didConnect() self?.client.handleEvent("connect", data: nil, isInternalMessage: false) - + self?.ws = SRWebSocket(URL: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) self?.ws?.delegate = self - self?.ws?.open() + //self?.ws?.open() } else { NSLog("Error handshaking") @@ -192,11 +218,71 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - func handlePollingResponse(str:String) { - // TODO add polling + // A poll failed, try and reconnect + private func handlePollingFailed() { + if !self.client.reconnecting { + self.pingTimer?.invalidate() + self.client.tryReconnect(triesLeft: self.client.reconnectAttempts) + } } - func parseWebSocketMessage(message:AnyObject?) { + // Translatation of engine.io-parser#decodePayload + func parsePollingMessage(str:String) { + if str.length == 1 { + return + } + + // println(str) + + var length = "" + var n = 0 + var msg = "" + + func testLength(length:String, inout n:Int) -> Bool { + if let num = length.toInt() { + n = num + if num != n { + return true + } + } + + return false + } + + for var i = 0, l = str.length; i < l; i++ { + let strArray = Array(str) + let chr = String(strArray[i]) + + if chr != ":" { + length += chr + } else { + if testLength(length, &n) || length == "" { + println("parsing error at testlength") + return + } + + msg = String(strArray[i+1...i+n]) + + if let lengthInt = length.toInt() { + if lengthInt != msg.length { + println("parsing error") + return + } + } + + if msg.length != 0 { + self.parseEngineMessage(msg) + } + + i += n + length = "" + } + } + } + + func parseEngineMessage(message:AnyObject?) { + // println(message) + if let data = message as? NSData { // Strip off message type self.client.parseSocketMessage(data.subdataWithRange(NSMakeRange(1, data.length - 1))) @@ -212,13 +298,15 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } - let type = strMessage["(\\d)"].matches()[0] + let type = strMessage["^(\\d)"].groups()?[1] if type != PacketType.MESSAGE.rawValue { // TODO Handle other packets + println(message) return } + // Remove message type message.removeAtIndex(message.startIndex) self.client.parseSocketMessage(message) } @@ -263,10 +351,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } else if err != nil { println(err) - self?.pingTimer?.invalidate() - if !self!.client.reconnecting { - self?.client.tryReconnect(triesLeft: self!.client.reconnectAttempts) - } + self?.handlePollingFailed() return } @@ -305,7 +390,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { func webSocket(webSocket:SRWebSocket!, didReceiveMessage message:AnyObject?) { // println(message) - self.parseWebSocketMessage(message) + self.parseEngineMessage(message) } // Called when the socket is opened From 3088bd88e6977d01c72c66283a83101c93af82a6 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 14:06:20 -0500 Subject: [PATCH 08/34] handle binary --- SwiftIO/SocketEngine.swift | 40 +++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 58a3d812..4a261450 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -130,28 +130,16 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } + // println(data) + if let str = NSString(data: data, encoding: NSUTF8StringEncoding) { // println(str) - var mut = RegexMutable(str) - - let groups = mut["(\\d):(.*)"].groups() - if groups[1] == "" || groups[2] == "" { - return - } - - let type = groups[1] - var mutPart = RegexMutable(groups[0]) - - if type != "2" { - return - } - mutPart["^2:40"] ~= "" - - self?.parsePollingMessage(mutPart) - self?.wait = false - self?.doPoll() + self?.parsePollingMessage(str) } + + self?.wait = false + self?.doPoll() } } @@ -241,9 +229,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { func testLength(length:String, inout n:Int) -> Bool { if let num = length.toInt() { n = num - if num != n { - return true - } + } else { + return true } return false @@ -302,7 +289,16 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if type != PacketType.MESSAGE.rawValue { // TODO Handle other packets - println(message) + if message.hasPrefix("b4") { + // binary in base64 string + message.removeRange(Range(start: message.startIndex, + end: advance(message.startIndex, 2))) + } + + if let data = NSData(base64EncodedString: message, + options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { + self.client.parseSocketMessage(data) + } return } From 2e80b9405db04ccdd9286459016be3ee3fe5ec6c Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 15:13:41 -0500 Subject: [PATCH 09/34] Fixes. Sending doesn't work on polling, namespaces broken --- SwiftIO/SocketEngine.swift | 93 +++++++++++++++++++----------------- SwiftIO/SocketIOClient.swift | 32 ++----------- 2 files changed, 53 insertions(+), 72 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 4a261450..b0afd410 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -44,7 +44,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { unowned let client:SocketIOClient private let pollingQueue = NSOperationQueue() private var pingTimer:NSTimer? - private var pollingTimer:NSTimer? private var _polling = true private var wait = false private var _websocket = false @@ -67,7 +66,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { func close() { self.pingTimer?.invalidate() - self.pollingTimer?.invalidate() if self.websocket { self.ws?.send(PacketType.MESSAGE.rawValue + PacketType.CLOSE.rawValue) @@ -165,39 +163,46 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if let dataString = NSString(data: data, encoding: NSUTF8StringEncoding) { var mutString = RegexMutable(dataString) - let parsed = mutString["(\\d*):(\\d)(\\{.*\\})?"].groups() - if parsed.count == 4 { - let length = parsed[1] - let type = parsed[2] - let jsonData = parsed[3].dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) - - if type != "0" { - NSLog("Error handshaking") - return - } - - if let json = NSJSONSerialization.JSONObjectWithData(jsonData!, - options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { - if let sid = json["sid"] as? String { - self?.sid = sid - self?.client.didConnect() - self?.client.handleEvent("connect", data: nil, isInternalMessage: false) - - self?.ws = SRWebSocket(URL: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) - self?.ws?.delegate = self - //self?.ws?.open() - - } else { - NSLog("Error handshaking") - return - } + if parsed.count != 4 { + return + } + + let length = parsed[1] + let type = parsed[2] + let jsonData = parsed[3].dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) + + if type != "0" { + NSLog("Error handshaking") + return + } + + if let json = NSJSONSerialization.JSONObjectWithData(jsonData!, + options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { + if let sid = json["sid"] as? String { + // println(json) + self?.sid = sid - if let pingInterval = json["pingInterval"] as? Int { - self?.pingInterval = pingInterval / 1000 + if let up = json["upgrades"] as? [String] { + for available in up { + if available == "websocket" { + self?.ws = SRWebSocket(URL: + NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) + self?.ws?.delegate = self + self?.ws?.open() + } + } } - } + + } else { + NSLog("Error handshaking") + return + } + + if let pingInterval = json["pingInterval"] as? Int { + self?.pingInterval = pingInterval / 1000 + } } self?.doPoll() @@ -293,12 +298,17 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // binary in base64 string message.removeRange(Range(start: message.startIndex, end: advance(message.startIndex, 2))) + + if let data = NSData(base64EncodedString: message, + options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { + self.client.parseSocketMessage(data) + } + + return } - if let data = NSData(base64EncodedString: message, - options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { - self.client.parseSocketMessage(data) - } + println("Got something idk what to do with") + println(message) return } @@ -314,7 +324,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } func send(msg:AnyObject) { - if self.websocketConnected { + if self.websocket { if !(msg is NSData) { self.ws?.send("\(PacketType.MESSAGE.rawValue)\(msg)") } else { @@ -377,7 +387,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if self.websocketConnected { self._websocket = true self._polling = false - self.pollingTimer?.invalidate() self.ws?.send(PacketType.UPGRADE.rawValue) } } @@ -391,31 +400,25 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // Called when the socket is opened func webSocketDidOpen(webSocket:SRWebSocket!) { - println("socket opened") self.websocketConnected = true self.probeWebSocket() } // Called when the socket is closed func webSocket(webSocket:SRWebSocket!, didCloseWithCode code:Int, reason:String!, wasClean:Bool) { - println("socket closed") - self.pingTimer?.invalidate() self.websocketConnected = false self._websocket = false self._polling = true - // Temp - self.client.webSocket(webSocket, didCloseWithCode: code, reason: reason, wasClean: wasClean) + self.client.webSocketDidCloseWithCode(code, reason: reason, wasClean: wasClean) } // Called when an error occurs. func webSocket(webSocket:SRWebSocket!, didFailWithError error:NSError!) { - self.pingTimer?.invalidate() self.websocketConnected = false self._websocket = false self._polling = true - // Temp - self.client.webSocket(webSocket, didFailWithError: error) + self.client.webSocketDidFailWithError(error) } } \ No newline at end of file diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 3e54dcbd..3e18d755 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -24,7 +24,7 @@ import Foundation -class SocketIOClient: NSObject { +class SocketIOClient { let engine:SocketEngine! let socketURL:NSMutableString! let ackQueue = dispatch_queue_create("ackQueue".cStringUsingEncoding(NSUTF8StringEncoding), @@ -84,8 +84,6 @@ class SocketIOClient: NSObject { } } - super.init() - self.engine = SocketEngine(client: self) } @@ -120,6 +118,7 @@ class SocketIOClient: NSObject { } func didConnect() { + self.closed = false self.connected = true self.connecting = false self.reconnecting = false @@ -485,10 +484,7 @@ class SocketIOClient: NSObject { if stringMessage == "0" { // connected - self.closed = false - self.connecting = false - self.reconnecting = false - self.connected = true + self.didConnect() if self.nsp != nil { // Join namespace @@ -805,26 +801,8 @@ class SocketIOClient: NSObject { } } - // Called when the socket is opened - func webSocketDidOpen(webSocket:SRWebSocket!) { - self.closed = false - self.connecting = false - self.reconnecting = false - self.connected = true - - if self.nsp != nil { - // Join namespace - self.joinNamespace() - return - } - - // Don't handle as internal because something crazy could happen where - // we disconnect before it's handled - self.handleEvent("connect", data: nil) - } - // Called when the socket is closed - func webSocket(webSocket:SRWebSocket!, didCloseWithCode code:Int, reason:String!, wasClean:Bool) { + func webSocketDidCloseWithCode(code:Int, reason:String!, wasClean:Bool) { self.connected = false self.connecting = false if self.closed || !self.reconnects { @@ -837,7 +815,7 @@ class SocketIOClient: NSObject { } // Called when an error occurs. - func webSocket(webSocket:SRWebSocket!, didFailWithError error:NSError!) { + func webSocketDidFailWithError(error:NSError!) { self.connected = false self.connecting = false self.handleEvent("error", data: error.localizedDescription, isInternalMessage: true) From 2205cfb7d9837e4c30d0761f30137a22480c1843 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 15:52:18 -0500 Subject: [PATCH 10/34] fix EXE_BAD_ACCESS when compiling with -O with swift 1.1 --- SwiftIO/SocketEngine.swift | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index b0afd410..9e6f59e0 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -24,6 +24,10 @@ import Foundation +// This is used because in Swift 1.1, turning on -O causes a +// memory access violation in SocketEngine#parseEngineMessage +private var fixSwift:AnyObject? + extension String { private var length:Int { return Array(self).count @@ -194,7 +198,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } } - } else { NSLog("Error handshaking") return @@ -220,7 +223,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } // Translatation of engine.io-parser#decodePayload - func parsePollingMessage(str:String) { + private func parsePollingMessage(str:String) { if str.length == 1 { return } @@ -263,7 +266,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } if msg.length != 0 { - self.parseEngineMessage(msg) + fixSwift = msg + self.parseEngineMessage(fixSwift) } i += n @@ -272,7 +276,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - func parseEngineMessage(message:AnyObject?) { + private func parseEngineMessage(message:AnyObject?) { // println(message) if let data = message as? NSData { @@ -281,8 +285,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } - var message = message as String - var strMessage = RegexMutable(message) + var messageString = message as String + var strMessage = RegexMutable(messageString) // We should upgrade if strMessage == "3probe" { @@ -294,12 +298,12 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if type != PacketType.MESSAGE.rawValue { // TODO Handle other packets - if message.hasPrefix("b4") { + if messageString.hasPrefix("b4") { // binary in base64 string - message.removeRange(Range(start: message.startIndex, - end: advance(message.startIndex, 2))) + messageString.removeRange(Range(start: messageString.startIndex, + end: advance(messageString.startIndex, 2))) - if let data = NSData(base64EncodedString: message, + if let data = NSData(base64EncodedString: messageString, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { self.client.parseSocketMessage(data) } @@ -308,13 +312,12 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } println("Got something idk what to do with") - println(message) - return + println(messageString) } // Remove message type - message.removeAtIndex(message.startIndex) - self.client.parseSocketMessage(message) + messageString.removeAtIndex(messageString.startIndex) + self.client.parseSocketMessage(messageString) } func probeWebSocket() { From 4925c00c1228f3efa291f47f7a06fca4ae5ecb9d Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 18:02:42 -0500 Subject: [PATCH 11/34] work on xhr polling sending --- SwiftIO/SocketEngine.swift | 118 +++++++++++++++++++++++++---------- SwiftIO/SocketIOClient.swift | 26 ++++---- 2 files changed, 98 insertions(+), 46 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 9e6f59e0..0237c9c2 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -30,7 +30,7 @@ private var fixSwift:AnyObject? extension String { private var length:Int { - return Array(self).count + return countElements(self) } } @@ -79,6 +79,22 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } + private func createBinaryDataForSend(data:NSData) -> (NSData?, String?) { + if self.websocket { + var byteArray = [UInt8](count: 1, repeatedValue: 0x0) + byteArray[0] = 4 + var mutData = NSMutableData(bytes: &byteArray, length: 1) + mutData.appendData(data) + return (mutData, nil) + } else { + var str = "b4" + str += data.base64EncodedStringWithOptions( + NSDataBase64EncodingOptions.Encoding64CharacterLineLength) + + return (nil, str) + } + } + private func createURLs(params:[String: AnyObject]? = nil) -> (String, String) { var url = "\(self.client.socketURL)/socket.io/?transport=" var urlPolling:String @@ -120,28 +136,30 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } let time = Int(NSDate().timeIntervalSince1970) - let req = NSURLRequest(URL: NSURL(string: self.urlPolling! + "&t=\(time)-0&b64=1" + "&sid=\(self.sid)")!) + let req = NSURLRequest(URL: + NSURL(string: self.urlPolling! + "&t=\(time)-0&b64=1" + "&sid=\(self.sid)")!) self.wait = true - NSURLConnection.sendAsynchronousRequest(req, queue: self.pollingQueue) {[weak self] res, data, err in - if self == nil { - return - } else if err != nil { - println(err) - self?.handlePollingFailed() - return - } - - // println(data) - - if let str = NSString(data: data, encoding: NSUTF8StringEncoding) { - // println(str) + NSURLConnection.sendAsynchronousRequest(req, + queue: self.pollingQueue) {[weak self] res, data, err in + if self == nil { + return + } else if err != nil { + // println(err) + self?.handlePollingFailed() + return + } - self?.parsePollingMessage(str) - } - - self?.wait = false - self?.doPoll() + // println(data) + + if let str = NSString(data: data, encoding: NSUTF8StringEncoding) { + // println(str) + + self?.parsePollingMessage(str) + } + + self?.wait = false + self?.doPoll() } } @@ -159,7 +177,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if self == nil { return } else if err != nil || data == nil { - println(err) + // println(err) self?.handlePollingFailed() return @@ -188,16 +206,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // println(json) self?.sid = sid - if let up = json["upgrades"] as? [String] { - for available in up { - if available == "websocket" { - self?.ws = SRWebSocket(URL: - NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) - self?.ws?.delegate = self - self?.ws?.open() - } - } - } + self?.ws = SRWebSocket(URL: + NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) + self?.ws?.delegate = self + // self?.ws?.open() } else { NSLog("Error handshaking") return @@ -218,6 +230,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private func handlePollingFailed() { if !self.client.reconnecting { self.pingTimer?.invalidate() + self.wait = false + self.client.handleEvent("reconnect", data: "XHR polling timeout", isInternalMessage: true) self.client.tryReconnect(triesLeft: self.client.reconnectAttempts) } } @@ -253,7 +267,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } else { if testLength(length, &n) || length == "" { println("parsing error at testlength") - return + exit(1) } msg = String(strArray[i+1...i+n]) @@ -261,7 +275,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if let lengthInt = length.toInt() { if lengthInt != msg.length { println("parsing error") - return + exit(1) } } @@ -331,7 +345,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if !(msg is NSData) { self.ws?.send("\(PacketType.MESSAGE.rawValue)\(msg)") } else { - self.ws?.send(msg) + let (data, nilString) = self.createBinaryDataForSend(msg as NSData) + self.ws?.send(data!) } } else { self.sendPollMessage(msg) @@ -370,7 +385,42 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } func sendPollMessage(msg:AnyObject) { + var postData:NSData + let time = Int(NSDate().timeIntervalSince1970) + var req = NSMutableURLRequest(URL: + NSURL(string:self.urlPolling! + "&t=\(time)&b64=1" + "&sid=\(self.sid)")!) + + req.HTTPMethod = "POST" + req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") + + if msg is NSData { + println("sending poll binary") + let (nilData, data) = self.createBinaryDataForSend(msg as NSData) + + let dataLen = countElements(data!) + postData = "\(dataLen):\(data!)".dataUsingEncoding(NSUTF8StringEncoding, + allowLossyConversion: false)! + } else if !(msg is String) { + return + } else { + // println(msg) + let strMsg = "\(PacketType.MESSAGE.rawValue)\(msg as String)" + // println(strMsg) + + let postCount = countElements(strMsg) + postData = ("\(postCount):\(strMsg)").dataUsingEncoding(NSUTF8StringEncoding, + allowLossyConversion: false)! + } + + req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") + req.HTTPBody = postData + NSURLConnection.sendAsynchronousRequest(req, queue: self.pollingQueue) {[weak self] res, data, err in + if err != nil { + println(err) + return + } + } } // Starts the ping timer diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 3e18d755..43f6f26a 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -109,13 +109,13 @@ class SocketIOClient { } // Creates a binary message, ready for sending - private class func createBinaryDataForSend(data:NSData) -> NSData { - var byteArray = [UInt8](count: 1, repeatedValue: 0x0) - byteArray[0] = 4 - var mutData = NSMutableData(bytes: &byteArray, length: 1) - mutData.appendData(data) - return mutData - } +// private class func createBinaryDataForSend(data:NSData) -> NSData { +// var byteArray = [UInt8](count: 1, repeatedValue: 0x0) +// byteArray[0] = 4 +// var mutData = NSMutableData(bytes: &byteArray, length: 1) +// mutData.appendData(data) +// return mutData +// } func didConnect() { self.closed = false @@ -317,7 +317,7 @@ class SocketIOClient { for g in 0.. Date: Wed, 4 Mar 2015 18:04:45 -0500 Subject: [PATCH 12/34] remove old method --- SwiftIO/SocketIOClient.swift | 9 --------- 1 file changed, 9 deletions(-) diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 43f6f26a..54d48e59 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -108,15 +108,6 @@ class SocketIOClient { self.engine.open(opts: params) } - // Creates a binary message, ready for sending -// private class func createBinaryDataForSend(data:NSData) -> NSData { -// var byteArray = [UInt8](count: 1, repeatedValue: 0x0) -// byteArray[0] = 4 -// var mutData = NSMutableData(bytes: &byteArray, length: 1) -// mutData.appendData(data) -// return mutData -// } - func didConnect() { self.closed = false self.connected = true From 346edc7b53c93eb279be3d7ed4cad01193173ceb Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 19:52:20 -0500 Subject: [PATCH 13/34] Better handle moving between polling and websockets. Sending binary on polling still bork --- SwiftIO/SocketEngine.swift | 58 +++++++++++++++++++++++++++--------- SwiftIO/SocketIOClient.swift | 10 ++----- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 0237c9c2..02aa01ce 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -34,6 +34,8 @@ extension String { } } +private typealias ProbeQueue = [() -> Void] + private enum PacketType: String { case OPEN = "0" case CLOSE = "1" @@ -46,9 +48,11 @@ private enum PacketType: String { class SocketEngine: NSObject, SRWebSocketDelegate { unowned let client:SocketIOClient - private let pollingQueue = NSOperationQueue() + private let workQueue = NSOperationQueue() private var pingTimer:NSTimer? private var _polling = true + private var probing = false + private var probeWait = ProbeQueue() private var wait = false private var _websocket = false private var websocketConnected = false @@ -141,7 +145,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.wait = true NSURLConnection.sendAsynchronousRequest(req, - queue: self.pollingQueue) {[weak self] res, data, err in + queue: self.workQueue) {[weak self] res, data, err in if self == nil { return } else if err != nil { @@ -172,7 +176,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { let reqPolling = NSURLRequest(URL: NSURL(string: urlPolling + "&t=\(time)-0&b64=1")!) NSURLConnection.sendAsynchronousRequest(reqPolling, - queue: self.pollingQueue) {[weak self] res, data, err in + queue: self.workQueue) {[weak self] res, data, err in var err:NSError? if self == nil { return @@ -209,7 +213,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.ws = SRWebSocket(URL: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) self?.ws?.delegate = self - // self?.ws?.open() + self?.ws?.open() } else { NSLog("Error handshaking") return @@ -340,16 +344,31 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - func send(msg:AnyObject) { - if self.websocket { - if !(msg is NSData) { - self.ws?.send("\(PacketType.MESSAGE.rawValue)\(msg)") - } else { - let (data, nilString) = self.createBinaryDataForSend(msg as NSData) - self.ws?.send(data!) + func send(msg:AnyObject, datas:[NSData]? = nil) { + let _send = {[weak self] (msg:AnyObject, datas:[NSData]?) -> () -> Void in + return { + if self == nil { + return + } + + if self!.websocket { + self?.ws?.send("\(PacketType.MESSAGE.rawValue)\(msg)") + if datas != nil { + for data in datas! { + let (data, nilString) = self!.createBinaryDataForSend(data) + self?.ws?.send(data!) + } + } + } else { + self?.sendPollMessage(msg) + } } + } + + if self.probing { + self.probeWait.append(_send(msg, datas)) } else { - self.sendPollMessage(msg) + _send(msg, datas)() } } @@ -370,7 +389,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { req.HTTPBody = postData NSURLConnection.sendAsynchronousRequest(req, - queue: self.pollingQueue) {[weak self] res, data, err in + queue: self.workQueue) {[weak self] res, data, err in if self == nil { return } else if err != nil { @@ -385,6 +404,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } func sendPollMessage(msg:AnyObject) { + // println("Sending: \(msg)") + var postData:NSData let time = Int(NSDate().timeIntervalSince1970) var req = NSMutableURLRequest(URL: @@ -415,7 +436,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") req.HTTPBody = postData - NSURLConnection.sendAsynchronousRequest(req, queue: self.pollingQueue) {[weak self] res, data, err in + NSURLConnection.sendAsynchronousRequest(req, queue: self.workQueue) {[weak self] res, data, err in if err != nil { println(err) return @@ -438,9 +459,15 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private func upgradeTransport() { if self.websocketConnected { + self.probing = false self._websocket = true self._polling = false self.ws?.send(PacketType.UPGRADE.rawValue) + for sender in self.probeWait { + sender() + } + + self.probeWait.removeAll(keepCapacity: false) } } @@ -454,12 +481,14 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // Called when the socket is opened func webSocketDidOpen(webSocket:SRWebSocket!) { self.websocketConnected = true + self.probing = true self.probeWebSocket() } // Called when the socket is closed func webSocket(webSocket:SRWebSocket!, didCloseWithCode code:Int, reason:String!, wasClean:Bool) { self.websocketConnected = false + self.probing = false self._websocket = false self._polling = true @@ -471,6 +500,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.websocketConnected = false self._websocket = false self._polling = true + self.probing = false self.client.webSocketDidFailWithError(error) } diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 54d48e59..a1e43db6 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -171,10 +171,7 @@ class SocketIOClient { hasBinary: true, withDatas: emitDatas.count, toNamespace: self.nsp, wantsAck: self.currentAck) } - self.engine?.send(str) - for data in emitDatas { - self.engine?.send(data) - } + self.engine?.send(str, datas: emitDatas) } else { if !ack { str = SocketEvent.createMessageForEvent(event, withArgs: items, hasBinary: false, @@ -217,10 +214,7 @@ class SocketIOClient { withAckType: 6, withNsp: self!.nsp!, withBinary: emitDatas.count) } - self?.engine?.send(str) - for data in emitDatas { - self?.engine?.send(data) - } + self?.engine?.send(str, datas: emitDatas) } } } From b295561dcc2d013912e3594c81e4da22467f38b9 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 20:22:39 -0500 Subject: [PATCH 14/34] non-namespace polling seems to work, needs more testing --- SwiftIO/SocketEngine.swift | 64 ++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 02aa01ce..0428de4f 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -167,6 +167,14 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } + private func flushProbeWait() { + for waiter in self.probeWait { + waiter() + } + + self.probeWait.removeAll(keepCapacity: false) + } + func open(opts:[String: AnyObject]? = nil) { let (urlPolling, urlWebSocket) = self.createURLs(params: opts) @@ -360,7 +368,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } } else { - self?.sendPollMessage(msg) + self?.sendPollMessage(msg, datas: datas) } } } @@ -403,10 +411,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - func sendPollMessage(msg:AnyObject) { + func sendPollMessage(msg:AnyObject, datas:[NSData]?) { // println("Sending: \(msg)") - var postData:NSData + var bDatas:[String]? let time = Int(NSDate().timeIntervalSince1970) var req = NSMutableURLRequest(URL: NSURL(string:self.urlPolling! + "&t=\(time)&b64=1" + "&sid=\(self.sid)")!) @@ -414,25 +422,31 @@ class SocketEngine: NSObject, SRWebSocketDelegate { req.HTTPMethod = "POST" req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") - if msg is NSData { - println("sending poll binary") - let (nilData, data) = self.createBinaryDataForSend(msg as NSData) - - let dataLen = countElements(data!) - postData = "\(dataLen):\(data!)".dataUsingEncoding(NSUTF8StringEncoding, - allowLossyConversion: false)! - } else if !(msg is String) { - return - } else { - // println(msg) - let strMsg = "\(PacketType.MESSAGE.rawValue)\(msg as String)" - // println(strMsg) - - let postCount = countElements(strMsg) - postData = ("\(postCount):\(strMsg)").dataUsingEncoding(NSUTF8StringEncoding, - allowLossyConversion: false)! + if datas != nil { + bDatas = [String]() + for data in datas! { + let (nilData, b64Data) = self.createBinaryDataForSend(data) + let dataLen = countElements(b64Data!) + + bDatas!.append("\(dataLen):\(b64Data!)") + } + } + + let strMsg = "\(PacketType.MESSAGE.rawValue)\(msg as String)" + + let postCount = countElements(strMsg) + var postStr = "\(postCount):\(strMsg)" + + if bDatas != nil { + for data in bDatas! { + postStr += data + } } + postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, + allowLossyConversion: false)! + + req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") req.HTTPBody = postData @@ -463,11 +477,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self._websocket = true self._polling = false self.ws?.send(PacketType.UPGRADE.rawValue) - for sender in self.probeWait { - sender() - } - - self.probeWait.removeAll(keepCapacity: false) + self.flushProbeWait() } } @@ -491,6 +501,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.probing = false self._websocket = false self._polling = true + self.flushProbeWait() self.client.webSocketDidCloseWithCode(code, reason: reason, wasClean: wasClean) } @@ -501,7 +512,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self._websocket = false self._polling = true self.probing = false + self.flushProbeWait() - self.client.webSocketDidFailWithError(error) + // self.client.webSocketDidFailWithError(error) } } \ No newline at end of file From 1f1ba605743a93f4e7d9c85cf1c39c4a07cd7c89 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 21:20:52 -0500 Subject: [PATCH 15/34] fix namespaces --- SwiftIO/SocketAckHandler.swift | 2 +- SwiftIO/SocketEngine.swift | 8 ++++---- SwiftIO/SocketIOClient.swift | 35 ++++++++++++++++++++++------------ 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/SwiftIO/SocketAckHandler.swift b/SwiftIO/SocketAckHandler.swift index 5a9d9096..17a42364 100644 --- a/SwiftIO/SocketAckHandler.swift +++ b/SwiftIO/SocketAckHandler.swift @@ -24,7 +24,7 @@ import Foundation -typealias AckCallback = (AnyObject?) -> Void +typealias AckCallback = (NSArray?) -> Void class SocketAckHandler { let ackNum:Int! diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 0428de4f..27fe4d45 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -221,7 +221,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.ws = SRWebSocket(URL: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) self?.ws?.delegate = self - self?.ws?.open() + // self?.ws?.open() } else { NSLog("Error handshaking") return @@ -278,8 +278,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { length += chr } else { if testLength(length, &n) || length == "" { - println("parsing error at testlength") - exit(1) + self.handlePollingFailed() + return } msg = String(strArray[i+1...i+n]) @@ -287,7 +287,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if let lengthInt = length.toInt() { if lengthInt != msg.length { println("parsing error") - exit(1) + return } } diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index a1e43db6..6b65e41b 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -225,7 +225,14 @@ class SocketIOClient { if handler.ackNum != ack { return true } else { - handler.callback?(data) + if data is NSArray { + handler.callback?(data as? NSArray) + } else if data != nil { + handler.callback?([data!]) + } else { + handler.callback?(nil) + } + return false } } @@ -465,23 +472,23 @@ class SocketIOClient { if let stringMessage = message as? String { // Check for successful namepsace connect + if self.nsp != nil { + if stringMessage == "0/\(self.nsp!)" { + self.didConnect() + self.handleEvent("connect", data: nil) + return + } + } if stringMessage == "0" { - // connected - self.didConnect() - if self.nsp != nil { // Join namespace self.joinNamespace() return - } - - // Don't handle as internal because something crazy could happen where - // we disconnect before it's handled - self.handleEvent("connect", data: nil) - } - if self.nsp != nil { - if stringMessage == "0/\(self.nsp!)" { + } else { + // Don't handle as internal because something crazy could happen where + // we disconnect before it's handled + self.didConnect() self.handleEvent("connect", data: nil) return } @@ -705,10 +712,12 @@ class SocketIOClient { let filledInArgs:AnyObject = self.lastSocketMessage!.fillInPlaceholders(args) if self.lastSocketMessage!.justAck! { + // Should handle ack self.handleAck(self.lastSocketMessage!.ack!, data: filledInArgs) return } + // Should do event if self.lastSocketMessage!.ack != nil { self.handleEvent(event, data: filledInArgs, isInternalMessage: false, wantsAck: self.lastSocketMessage!.ack!, withAckType: 6) @@ -718,11 +727,13 @@ class SocketIOClient { } else { let filledInArgs:AnyObject = self.lastSocketMessage!.fillInPlaceholders() + // Should handle ack if self.lastSocketMessage!.justAck! { self.handleAck(self.lastSocketMessage!.ack!, data: filledInArgs) return } + // Should handle ack if self.lastSocketMessage!.ack != nil { self.handleEvent(event, data: filledInArgs, isInternalMessage: false, wantsAck: self.lastSocketMessage!.ack!, withAckType: 6) From 63aed1ce91f97c610bb26c4815a8385b468edec1 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 21:50:23 -0500 Subject: [PATCH 16/34] tweaks --- README.md | 2 +- SwiftIO/SocketEngine.swift | 77 ++++++++++++++++++++------------------ 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index ce733c7b..0c48ca55 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ socket.on("ackEvent") {data, ack in } socket.emitWithAck("ackTest", "test").onAck {data in - println(data) + println(data?[0]) } ack?("Got your event", "dude") diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 27fe4d45..5feae6bd 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -49,6 +49,8 @@ private enum PacketType: String { class SocketEngine: NSObject, SRWebSocketDelegate { unowned let client:SocketIOClient private let workQueue = NSOperationQueue() + private let handleQueue = dispatch_queue_create( + "handleQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) private var pingTimer:NSTimer? private var _polling = true private var probing = false @@ -134,7 +136,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return (urlPolling, urlWebSocket) } - func doPoll() { + private func doPoll() { if self.urlPolling == nil || self.websocket || self.wait { return } @@ -221,7 +223,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.ws = SRWebSocket(URL: NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) self?.ws?.delegate = self - // self?.ws?.open() + self?.ws?.open() } else { NSLog("Error handshaking") return @@ -304,46 +306,47 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private func parseEngineMessage(message:AnyObject?) { // println(message) - - if let data = message as? NSData { - // Strip off message type - self.client.parseSocketMessage(data.subdataWithRange(NSMakeRange(1, data.length - 1))) - return - } - - var messageString = message as String - var strMessage = RegexMutable(messageString) - - // We should upgrade - if strMessage == "3probe" { - self.upgradeTransport() - return - } - - let type = strMessage["^(\\d)"].groups()?[1] - - if type != PacketType.MESSAGE.rawValue { - // TODO Handle other packets - if messageString.hasPrefix("b4") { - // binary in base64 string - messageString.removeRange(Range(start: messageString.startIndex, - end: advance(messageString.startIndex, 2))) - - if let data = NSData(base64EncodedString: messageString, - options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { - self.client.parseSocketMessage(data) + dispatch_async(self.handleQueue) {[weak self] in + if let data = message as? NSData { + // Strip off message type + self?.client.parseSocketMessage(data.subdataWithRange(NSMakeRange(1, data.length - 1))) + return + } + + var messageString = message as String + var strMessage = RegexMutable(messageString) + + // We should upgrade + if strMessage == "3probe" { + self?.upgradeTransport() + return + } + + let type = strMessage["^(\\d)"].groups()?[1] + + if type != PacketType.MESSAGE.rawValue { + // TODO Handle other packets + if messageString.hasPrefix("b4") { + // binary in base64 string + messageString.removeRange(Range(start: messageString.startIndex, + end: advance(messageString.startIndex, 2))) + + if let data = NSData(base64EncodedString: messageString, + options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { + self?.client.parseSocketMessage(data) + } + + return } - return + // println("Got something idk what to do with") + // println(messageString) } - println("Got something idk what to do with") - println(messageString) + // Remove message type + messageString.removeAtIndex(messageString.startIndex) + self?.client.parseSocketMessage(messageString) } - - // Remove message type - messageString.removeAtIndex(messageString.startIndex) - self.client.parseSocketMessage(messageString) } func probeWebSocket() { From 68a0f8d9acf82c0ebee3207b39bbe4a3a470cff5 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 22:53:57 -0500 Subject: [PATCH 17/34] gotta go fast --- SwiftIO/SocketEngine.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 5feae6bd..5c84e85c 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -272,7 +272,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return false } - for var i = 0, l = str.length; i < l; i++ { + for var i = 0, l = str.length; i < l; i = i &+ 1 { let strArray = Array(str) let chr = String(strArray[i]) @@ -284,7 +284,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } - msg = String(strArray[i+1...i+n]) + msg = String(strArray[i&+1...i&+n]) if let lengthInt = length.toInt() { if lengthInt != msg.length { From 1a63088a5e45606e931b178c47831fb58f5fa2cd Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 23:02:32 -0500 Subject: [PATCH 18/34] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0c48ca55..766a5df3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Socket.IO-Client-Swift ====================== -Socket.IO-client for Swift. Supports ws/wss connections and binary. For socket.io 1.0+ and Swift 1.1. +Socket.IO-client for Swift. Supports ws/wss/polling connections and binary. For socket.io 1.0+ and Swift 1.1. For Swift 1.2 use the 1.2 branch. From 543a3c8f91067f1f777b4621422dd7774afaaf91 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 23:15:11 -0500 Subject: [PATCH 19/34] add toggle for polling only --- SwiftIO/SocketEngine.swift | 14 +++++++++----- SwiftIO/SocketIOClient.swift | 7 ++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 5c84e85c..b6cf850a 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -51,6 +51,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private let workQueue = NSOperationQueue() private let handleQueue = dispatch_queue_create( "handleQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) + private var forcePolling = false private var pingTimer:NSTimer? private var _polling = true private var probing = false @@ -70,8 +71,9 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } var ws:SRWebSocket? - init(client:SocketIOClient) { + init(client:SocketIOClient, forcePolling:Bool = false) { self.client = client + self.forcePolling = forcePolling } func close() { @@ -220,10 +222,12 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // println(json) self?.sid = sid - self?.ws = SRWebSocket(URL: - NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) - self?.ws?.delegate = self - self?.ws?.open() + if !self!.forcePolling { + self?.ws = SRWebSocket(URL: + NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) + self?.ws?.delegate = self + self?.ws?.open() + } } else { NSLog("Error handshaking") return diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 6b65e41b..72170f3f 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -36,6 +36,7 @@ class SocketIOClient { private lazy var params:[String: AnyObject] = [String: AnyObject]() private var ackHandlers = [SocketAckHandler]() private var currentAck = -1 + private var forcePolling = false private var handlers = [SocketEventHandler]() private var lastSocketMessage:SocketEvent? private var paramConnect = false @@ -82,9 +83,13 @@ class SocketIOClient { if let nsp = opts!["nsp"] as? String { self.nsp = nsp } + + if let polling = opts!["forcePolling"] as? Bool { + self.forcePolling = polling + } } - self.engine = SocketEngine(client: self) + self.engine = SocketEngine(client: self, forcePolling: self.forcePolling) } // Closes the socket From 8312d28196c8fbfe3c2b1917c28b145b2d9e7de7 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 23:16:09 -0500 Subject: [PATCH 20/34] update READEME.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 766a5df3..eaf9169b 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ API === Constructor ----------- -`init(socketURL: String, opts:[String: AnyObject]? = nil)` - Constructs a new client for the given URL. opts can be omitted (will use default values.) +`init(socketURL: String, opts:[String: AnyObject]? = nil)` - Constructs a new client for the given URL. opts can be omitted (will use default values. See example) Methods ------- 1. `socket.on(name:String, callback:((data:NSArray?, ack:AckEmitter?) -> Void))` - Adds a handler for an event. Items are passed by an array. `ack` can be used to send an ack when one is requested. See example. @@ -41,7 +41,8 @@ let socket = SocketIOClient(socketURL: "https://localhost:8080", opts: [ "reconnects": true, // default true "reconnectAttempts": 5, // default -1 (infinite tries) "reconnectWait": 5, // default 10 - "nsp": "swift" // connects to the specified namespace. Default is / + "nsp": "swift", // connects to the specified namespace. Default is / + "forcePolling": true // if true, the socket will only use XHR polling, default is false (polling/WebSockets) ]) // Socket Events From a1e3232db0cba2c317cf6d7c93f1212bb1878975 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 23:16:09 -0500 Subject: [PATCH 21/34] update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 766a5df3..eaf9169b 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ API === Constructor ----------- -`init(socketURL: String, opts:[String: AnyObject]? = nil)` - Constructs a new client for the given URL. opts can be omitted (will use default values.) +`init(socketURL: String, opts:[String: AnyObject]? = nil)` - Constructs a new client for the given URL. opts can be omitted (will use default values. See example) Methods ------- 1. `socket.on(name:String, callback:((data:NSArray?, ack:AckEmitter?) -> Void))` - Adds a handler for an event. Items are passed by an array. `ack` can be used to send an ack when one is requested. See example. @@ -41,7 +41,8 @@ let socket = SocketIOClient(socketURL: "https://localhost:8080", opts: [ "reconnects": true, // default true "reconnectAttempts": 5, // default -1 (infinite tries) "reconnectWait": 5, // default 10 - "nsp": "swift" // connects to the specified namespace. Default is / + "nsp": "swift", // connects to the specified namespace. Default is / + "forcePolling": true // if true, the socket will only use XHR polling, default is false (polling/WebSockets) ]) // Socket Events From 829992fcef62ab4e5246cc624a11ec7c53d47d5d Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 4 Mar 2015 23:39:11 -0500 Subject: [PATCH 22/34] send disconnect on polling close --- SwiftIO/SocketEngine.swift | 10 +++++----- SwiftIO/SocketIOClient.swift | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index b6cf850a..321af2bb 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -79,11 +79,11 @@ class SocketEngine: NSObject, SRWebSocketDelegate { func close() { self.pingTimer?.invalidate() - if self.websocket { - self.ws?.send(PacketType.MESSAGE.rawValue + PacketType.CLOSE.rawValue) - self.ws?.close() - } else { - // TODO handling polling + self.send(PacketType.CLOSE.rawValue) + self.ws?.close() + + if self.polling { + self.client.handleEvent("disconnect", data: "close", isInternalMessage: true) } } diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 72170f3f..90b494d5 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -97,6 +97,7 @@ class SocketIOClient { self.closed = true self.connecting = false self.connected = false + self.reconnecting = false self.engine?.close() } From 296802fddbe6a7e31ac70b84a86b79631fd67cc4 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 5 Mar 2015 11:06:11 -0500 Subject: [PATCH 23/34] better handle closes --- SwiftIO/SocketEngine.swift | 62 +++++++++++++++++++++++------------- SwiftIO/SocketIOClient.swift | 8 +++++ 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 321af2bb..45568487 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -76,11 +76,15 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.forcePolling = forcePolling } - func close() { + func close(forced:Bool = false) { self.pingTimer?.invalidate() - self.send(PacketType.CLOSE.rawValue) - self.ws?.close() + if !forced { + self.send(PacketType.CLOSE.rawValue) + self.ws?.close() + } else { + self.client.didForceClose() + } if self.polling { self.client.handleEvent("disconnect", data: "close", isInternalMessage: true) @@ -343,6 +347,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } + if messageString == PacketType.CLOSE.rawValue { + self?.close(forced: true) + return + } // println("Got something idk what to do with") // println(messageString) } @@ -353,27 +361,21 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - func probeWebSocket() { + private func probeWebSocket() { if self.websocketConnected { self.ws?.send("2probe") } } - func send(msg:AnyObject, datas:[NSData]? = nil) { - let _send = {[weak self] (msg:AnyObject, datas:[NSData]?) -> () -> Void in + func send(msg:String, datas:[NSData]? = nil) { + let _send = {[weak self] (msg:String, datas:[NSData]?) -> () -> Void in return { if self == nil { return } if self!.websocket { - self?.ws?.send("\(PacketType.MESSAGE.rawValue)\(msg)") - if datas != nil { - for data in datas! { - let (data, nilString) = self!.createBinaryDataForSend(data) - self?.ws?.send(data!) - } - } + self?.sendWebSocketMessage(msg, datas: datas) } else { self?.sendPollMessage(msg, datas: datas) } @@ -393,7 +395,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } else { let time = Int(NSDate().timeIntervalSince1970) var req = NSMutableURLRequest(URL: NSURL(string: - self.urlPolling! + "&t=\(time)-0&b64=1" + "&sid=\(self.sid)")!) + self.urlPolling! + "&sid=\(self.sid)")!) let postStr = "1:\(PacketType.PING.rawValue)" let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! let postLength = "\(postData.length)" @@ -418,13 +420,12 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - func sendPollMessage(msg:AnyObject, datas:[NSData]?) { + func sendPollMessage(msg:String, datas:[NSData]?) { // println("Sending: \(msg)") var postData:NSData var bDatas:[String]? - let time = Int(NSDate().timeIntervalSince1970) var req = NSMutableURLRequest(URL: - NSURL(string:self.urlPolling! + "&t=\(time)&b64=1" + "&sid=\(self.sid)")!) + NSURL(string:self.urlPolling! + "&sid=\(self.sid)")!) req.HTTPMethod = "POST" req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") @@ -439,7 +440,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - let strMsg = "\(PacketType.MESSAGE.rawValue)\(msg as String)" + let strMsg = "\(PacketType.MESSAGE.rawValue)\(msg)" let postCount = countElements(strMsg) var postStr = "\(postCount):\(strMsg)" @@ -459,12 +460,26 @@ class SocketEngine: NSObject, SRWebSocketDelegate { NSURLConnection.sendAsynchronousRequest(req, queue: self.workQueue) {[weak self] res, data, err in if err != nil { - println(err) + // println(err) + self?.handlePollingFailed() return } } } + func sendWebSocketMessage(str:String, datas:[NSData]?) { + self.ws?.send("\(PacketType.MESSAGE.rawValue)\(str)") + + if datas != nil { + for data in datas! { + let (data, nilString) = self.createBinaryDataForSend(data) + if data != nil { + self.ws?.send(data!) + } + } + } + } + // Starts the ping timer private func startPingTimer() { if self.pingInterval == nil { @@ -491,7 +506,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // Called when a message is recieved func webSocket(webSocket:SRWebSocket!, didReceiveMessage message:AnyObject?) { // println(message) - self.parseEngineMessage(message) } @@ -506,11 +520,13 @@ class SocketEngine: NSObject, SRWebSocketDelegate { func webSocket(webSocket:SRWebSocket!, didCloseWithCode code:Int, reason:String!, wasClean:Bool) { self.websocketConnected = false self.probing = false - self._websocket = false - self._polling = true self.flushProbeWait() - self.client.webSocketDidCloseWithCode(code, reason: reason, wasClean: wasClean) + if self.websocket { + self._websocket = false + self._polling = true + self.client.webSocketDidCloseWithCode(code, reason: reason, wasClean: wasClean) + } } // Called when an error occurs. diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 90b494d5..8108c1a8 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -121,6 +121,14 @@ class SocketIOClient { self.reconnecting = false } + // Server wants us to die + func didForceClose() { + self.closed = true + self.connecting = false + self.connected = false + self.reconnecting = false + } + // Sends a message with multiple args // If a message contains binary we have to send those // seperately. From 5ed2688c9e657d3fde79dc0dce0f85370bdb20dd Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 5 Mar 2015 12:55:09 -0500 Subject: [PATCH 24/34] better handle when the server closes the connection (polling) --- SwiftIO/SocketEngine.swift | 38 ++++++++++++++++++++++++++--------- SwiftIO/SocketIOClient.swift | 39 +++++++++++++++--------------------- 2 files changed, 45 insertions(+), 32 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 45568487..393cf22b 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -85,10 +85,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } else { self.client.didForceClose() } - - if self.polling { - self.client.handleEvent("disconnect", data: "close", isInternalMessage: true) - } } private func createBinaryDataForSend(data:NSData) -> (NSData?, String?) { @@ -147,9 +143,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } - let time = Int(NSDate().timeIntervalSince1970) let req = NSURLRequest(URL: - NSURL(string: self.urlPolling! + "&t=\(time)-0&b64=1" + "&sid=\(self.sid)")!) + NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) self.wait = true NSURLConnection.sendAsynchronousRequest(req, @@ -248,13 +243,38 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - // A poll failed, try and reconnect + // A poll failed, tell the client about it + // We check to see if we were closed by the server first private func handlePollingFailed() { if !self.client.reconnecting { self.pingTimer?.invalidate() self.wait = false - self.client.handleEvent("reconnect", data: "XHR polling timeout", isInternalMessage: true) - self.client.tryReconnect(triesLeft: self.client.reconnectAttempts) + + let forced = {() -> Bool in + var err:NSError? + let url = NSURL(string: self.urlPolling! + "&sid=\(self.sid)")! + let req = NSURLRequest(URL: url, cachePolicy: + NSURLRequestCachePolicy.UseProtocolCachePolicy, timeoutInterval: 4) + var resp:NSURLResponse? + let data = NSURLConnection.sendSynchronousRequest(req, returningResponse: &resp, error: &err) + + if data == nil || resp == nil || err != nil { + return false + } else if let str = NSString(data: data!, encoding: NSUTF8StringEncoding) { + if str == "1:61:1" { + return true + } else { + return false + } + } else { + return false + }}() + + if forced { + self.close(forced: true) + } else { + self.client.pollingDidFail() + } } } diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 8108c1a8..9dcc88ba 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -119,14 +119,17 @@ class SocketIOClient { self.connected = true self.connecting = false self.reconnecting = false + self.handleEvent("connect", data: nil, isInternalMessage: false) } // Server wants us to die func didForceClose() { self.closed = true - self.connecting = false self.connected = false + self.reconnects = false + self.connecting = false self.reconnecting = false + self.handleEvent("disconnect", data: "closed", isInternalMessage: true) } // Sends a message with multiple args @@ -489,7 +492,6 @@ class SocketIOClient { if self.nsp != nil { if stringMessage == "0/\(self.nsp!)" { self.didConnect() - self.handleEvent("connect", data: nil) return } } @@ -503,7 +505,6 @@ class SocketIOClient { // Don't handle as internal because something crazy could happen where // we disconnect before it's handled self.didConnect() - self.handleEvent("connect", data: nil) return } } @@ -758,16 +759,20 @@ class SocketIOClient { } } + // Something happened while polling + func pollingDidFail() { + if !self.reconnecting { + self.handleEvent("reconnect", data: "XHR polling error", isInternalMessage: true) + self.tryReconnect(triesLeft: self.reconnectAttempts) + } + } + // We lost connection and should attempt to reestablish func tryReconnect(var #triesLeft:Int) { self.connected = false if triesLeft != -1 && triesLeft <= 0 { - self.connected = false - self.connecting = false - self.reconnects = false - self.reconnecting = false - self.handleEvent("disconnect", data: "Failed to reconnect", isInternalMessage: true) + self.didForceClose() return } else if self.connected { self.connecting = false @@ -776,7 +781,7 @@ class SocketIOClient { } // println("Trying to reconnect #\(reconnectAttempts - triesLeft)") - self.handleEvent("reconnectAttempt", data: triesLeft, isInternalMessage: true) + self.handleEvent("reconnectAttempt", data: triesLeft - 1, isInternalMessage: true) let waitTime = UInt64(self.reconnectWait) * NSEC_PER_SEC let time = dispatch_time(DISPATCH_TIME_NOW, Int64(waitTime)) @@ -802,27 +807,15 @@ class SocketIOClient { } } - // Called when a message is recieved - func webSocket(webSocket:SRWebSocket!, didReceiveMessage message:AnyObject?) { - dispatch_async(self.handleQueue) {[weak self] in - if self == nil { - return - } - - self?.parseSocketMessage(message) - } - } - // Called when the socket is closed func webSocketDidCloseWithCode(code:Int, reason:String!, wasClean:Bool) { self.connected = false self.connecting = false if self.closed || !self.reconnects { - self.handleEvent("disconnect", data: reason, isInternalMessage: true) + self.didForceClose() } else { self.handleEvent("reconnect", data: reason, isInternalMessage: true) self.tryReconnect(triesLeft: self.reconnectAttempts) - } } @@ -832,7 +825,7 @@ class SocketIOClient { self.connecting = false self.handleEvent("error", data: error.localizedDescription, isInternalMessage: true) if self.closed || !self.reconnects { - self.handleEvent("disconnect", data: error.localizedDescription, isInternalMessage: true) + self.didForceClose() } else if !self.reconnecting { self.handleEvent("reconnect", data: error.localizedDescription, isInternalMessage: true) self.tryReconnect(triesLeft: self.reconnectAttempts) From c85a1def873a8755d33c720ef6d6ed41b4e3fc04 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 5 Mar 2015 13:45:25 -0500 Subject: [PATCH 25/34] tell client about failed ws --- SwiftIO/SocketEngine.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 393cf22b..c018c1b4 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -430,7 +430,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if self == nil { return } else if err != nil { - println(err) + // println(err) self?.handlePollingFailed() return } @@ -543,6 +543,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.flushProbeWait() if self.websocket { + self.pingTimer?.invalidate() self._websocket = false self._polling = true self.client.webSocketDidCloseWithCode(code, reason: reason, wasClean: wasClean) @@ -557,6 +558,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.probing = false self.flushProbeWait() - // self.client.webSocketDidFailWithError(error) + self.client.webSocketDidFailWithError(error) } } \ No newline at end of file From d35934c79d8b07e9dac29f712f1d2003591d6307 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 5 Mar 2015 14:37:00 -0500 Subject: [PATCH 26/34] handle binary data better --- SwiftIO/SocketEngine.swift | 47 +++++++++--------------------------- SwiftIO/SocketIOClient.swift | 40 +++++++++++++++--------------- 2 files changed, 32 insertions(+), 55 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index c018c1b4..a3066619 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -76,15 +76,9 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.forcePolling = forcePolling } - func close(forced:Bool = false) { + func close() { self.pingTimer?.invalidate() - - if !forced { - self.send(PacketType.CLOSE.rawValue) - self.ws?.close() - } else { - self.client.didForceClose() - } + self.send(PacketType.CLOSE.rawValue) } private func createBinaryDataForSend(data:NSData) -> (NSData?, String?) { @@ -250,31 +244,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.pingTimer?.invalidate() self.wait = false - let forced = {() -> Bool in - var err:NSError? - let url = NSURL(string: self.urlPolling! + "&sid=\(self.sid)")! - let req = NSURLRequest(URL: url, cachePolicy: - NSURLRequestCachePolicy.UseProtocolCachePolicy, timeoutInterval: 4) - var resp:NSURLResponse? - let data = NSURLConnection.sendSynchronousRequest(req, returningResponse: &resp, error: &err) - - if data == nil || resp == nil || err != nil { - return false - } else if let str = NSString(data: data!, encoding: NSUTF8StringEncoding) { - if str == "1:61:1" { - return true - } else { - return false - } - } else { - return false - }}() - - if forced { - self.close(forced: true) - } else { - self.client.pollingDidFail() - } + self.client.pollingDidFail() } } @@ -361,6 +331,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if let data = NSData(base64EncodedString: messageString, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { + // println("sending \(data)") self?.client.parseSocketMessage(data) } @@ -368,7 +339,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } if messageString == PacketType.CLOSE.rawValue { - self?.close(forced: true) + // do nothing return } // println("Got something idk what to do with") @@ -377,6 +348,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // Remove message type messageString.removeAtIndex(messageString.startIndex) + // println("sending \(messageString)") + self?.client.parseSocketMessage(messageString) } } @@ -553,11 +526,13 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // Called when an error occurs. func webSocket(webSocket:SRWebSocket!, didFailWithError error:NSError!) { self.websocketConnected = false - self._websocket = false self._polling = true self.probing = false self.flushProbeWait() - self.client.webSocketDidFailWithError(error) + if self.websocket { + self.pingTimer?.invalidate() + self.client.webSocketDidFailWithError(error) + } } } \ No newline at end of file diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 9dcc88ba..d40d5706 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -38,7 +38,7 @@ class SocketIOClient { private var currentAck = -1 private var forcePolling = false private var handlers = [SocketEventHandler]() - private var lastSocketMessage:SocketEvent? + private var waitingData = [SocketEvent]() private var paramConnect = false private var _secure = false var closed = false @@ -620,7 +620,7 @@ class SocketIOClient { // Message is binary if let binary = message as? NSData { - if self.lastSocketMessage == nil { + if self.waitingData.isEmpty { return } @@ -667,7 +667,6 @@ class SocketIOClient { mutMessageObject = RegexMutable(binaryGroup[5]) if namespace == "" && self.nsp != nil { - self.lastSocketMessage = nil return } @@ -684,7 +683,7 @@ class SocketIOClient { placeholders: numberOfPlaceholders.toInt()!, ackNum: ackNum.toInt()) } - self.lastSocketMessage = mes + self.waitingData.append(mes) } else if binaryGroup[1].hasPrefix("6") { let messageType = RegexMutable(binaryGroup[1]) let numberOfPlaceholders = (messageType["6"] ~= "") as String @@ -706,8 +705,10 @@ class SocketIOClient { let placeholdersRemoved = mutMessageObject["(\\{\"_placeholder\":true,\"num\":(\\d*)\\})"] ~= "\"~~$2\"" - self.lastSocketMessage = SocketEvent(event: "", args: placeholdersRemoved, + let event = SocketEvent(event: "", args: placeholdersRemoved, placeholders: numberOfPlaceholders.toInt()!, ackNum: ackNum.toInt(), justAck: true) + + self.waitingData.append(event) } /** End check for binary placeholders @@ -717,41 +718,42 @@ class SocketIOClient { // Handles binary data private func parseBinaryData(data:NSData) { - let shouldExecute = self.lastSocketMessage?.addData(data) + let shouldExecute = self.waitingData[0].addData(data) - if shouldExecute != nil && shouldExecute! { - var event = self.lastSocketMessage!.event - var parsedArgs:AnyObject? = SocketIOClient.parseData(self.lastSocketMessage!.args as? String) + if shouldExecute { + let socketEvent = self.waitingData.removeAtIndex(0) + var event = socketEvent.event + var parsedArgs:AnyObject? = SocketIOClient.parseData(socketEvent.args as? String) if let args:AnyObject = parsedArgs { - let filledInArgs:AnyObject = self.lastSocketMessage!.fillInPlaceholders(args) + let filledInArgs:AnyObject = socketEvent.fillInPlaceholders(args) - if self.lastSocketMessage!.justAck! { + if socketEvent.justAck! { // Should handle ack - self.handleAck(self.lastSocketMessage!.ack!, data: filledInArgs) + self.handleAck(socketEvent.ack!, data: filledInArgs) return } // Should do event - if self.lastSocketMessage!.ack != nil { + if socketEvent.ack != nil { self.handleEvent(event, data: filledInArgs, isInternalMessage: false, - wantsAck: self.lastSocketMessage!.ack!, withAckType: 6) + wantsAck: socketEvent.ack!, withAckType: 6) } else { self.handleEvent(event, data: filledInArgs) } } else { - let filledInArgs:AnyObject = self.lastSocketMessage!.fillInPlaceholders() + let filledInArgs:AnyObject = socketEvent.fillInPlaceholders() // Should handle ack - if self.lastSocketMessage!.justAck! { - self.handleAck(self.lastSocketMessage!.ack!, data: filledInArgs) + if socketEvent.justAck! { + self.handleAck(socketEvent.ack!, data: filledInArgs) return } // Should handle ack - if self.lastSocketMessage!.ack != nil { + if socketEvent.ack != nil { self.handleEvent(event, data: filledInArgs, isInternalMessage: false, - wantsAck: self.lastSocketMessage!.ack!, withAckType: 6) + wantsAck: socketEvent.ack!, withAckType: 6) } else { self.handleEvent(event, data: filledInArgs) } From 59a5e1928c76d442559323d53f0cf0f4f8fb5e37 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 5 Mar 2015 15:16:34 -0500 Subject: [PATCH 27/34] clean up --- SwiftIO/SocketEngine.swift | 50 +++++++++++------------------------- SwiftIO/SocketIOClient.swift | 10 ++++++++ 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index a3066619..435a49ce 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -356,7 +356,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private func probeWebSocket() { if self.websocketConnected { - self.ws?.send("2probe") + self.sendWebSocketMessage("probe", withType: PacketType.PING) } } @@ -368,9 +368,11 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } if self!.websocket { - self?.sendWebSocketMessage(msg, datas: datas) + // println("sending ws: \(msg)") + self?.sendWebSocketMessage(msg, withType: PacketType.MESSAGE, datas: datas) } else { - self?.sendPollMessage(msg, datas: datas) + // println("sending poll: \(msg)") + self?.sendPollMessage(msg, withType: PacketType.MESSAGE, datas: datas) } } } @@ -383,42 +385,21 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } func sendPing() { + // println("sending ping") + if self.websocket { - self.ws?.send(PacketType.PING.rawValue) + self.sendWebSocketMessage("", withType: PacketType.PING) } else { - let time = Int(NSDate().timeIntervalSince1970) - var req = NSMutableURLRequest(URL: NSURL(string: - self.urlPolling! + "&sid=\(self.sid)")!) - let postStr = "1:\(PacketType.PING.rawValue)" - let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)! - let postLength = "\(postData.length)" - - req.HTTPMethod = "POST" - req.setValue(postLength, forHTTPHeaderField: "Content-Length") - req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") - req.HTTPBody = postData - - NSURLConnection.sendAsynchronousRequest(req, - queue: self.workQueue) {[weak self] res, data, err in - if self == nil { - return - } else if err != nil { - // println(err) - self?.handlePollingFailed() - return - } - - self?.doPoll() - } + self.sendPollMessage("", withType: PacketType.PING) } } - func sendPollMessage(msg:String, datas:[NSData]?) { + private func sendPollMessage(msg:String, withType type:PacketType, datas:[NSData]? = nil) { // println("Sending: \(msg)") var postData:NSData var bDatas:[String]? var req = NSMutableURLRequest(URL: - NSURL(string:self.urlPolling! + "&sid=\(self.sid)")!) + NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) req.HTTPMethod = "POST" req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") @@ -433,8 +414,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - let strMsg = "\(PacketType.MESSAGE.rawValue)\(msg)" - + let strMsg = "\(type.rawValue)\(msg)" let postCount = countElements(strMsg) var postStr = "\(postCount):\(strMsg)" @@ -460,8 +440,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - func sendWebSocketMessage(str:String, datas:[NSData]?) { - self.ws?.send("\(PacketType.MESSAGE.rawValue)\(str)") + private func sendWebSocketMessage(str:String, withType type:PacketType, datas:[NSData]? = nil) { + self.ws?.send("\(type.rawValue)\(str)") if datas != nil { for data in datas! { @@ -491,7 +471,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.probing = false self._websocket = true self._polling = false - self.ws?.send(PacketType.UPGRADE.rawValue) + self.sendWebSocketMessage("", withType: PacketType.UPGRADE) self.flushProbeWait() } } diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index d40d5706..e5390b48 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -103,11 +103,21 @@ class SocketIOClient { // Connects to the server func connect() { + if self.closed { + println("Warning! This socket was previously closed. This might be dangerous!") + self.closed = false + } + self.engine.open() } // Connect to the server using params func connectWithParams(params:[String: AnyObject]) { + if self.closed { + println("Warning! This socket was previously closed. This might be dangerous!") + self.closed = false + } + self.params = params self.paramConnect = true From 166b2b515ba82ca326f14b5c2cc8d4083d180ac5 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 5 Mar 2015 15:19:17 -0500 Subject: [PATCH 28/34] make sure we're polling --- SwiftIO/SocketEngine.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 435a49ce..6bd66e0e 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -437,6 +437,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.handlePollingFailed() return } + + self?.doPoll() } } From 851ac7635aca9aa2606f676841654aa069782e24 Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 5 Mar 2015 22:06:17 -0500 Subject: [PATCH 29/34] Fix connection issues, binary data in nested data structures, some race conditions, reconnect needs to be redone --- SwiftIO/SocketEngine.swift | 261 +++++++++++++++++++++-------------- SwiftIO/SocketIOClient.swift | 64 ++++----- 2 files changed, 187 insertions(+), 138 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 6bd66e0e..d44f3a2e 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -34,7 +34,7 @@ extension String { } } -private typealias ProbeQueue = [() -> Void] +private typealias PollWaitQueue = [() -> Void] private enum PacketType: String { case OPEN = "0" @@ -49,16 +49,21 @@ private enum PacketType: String { class SocketEngine: NSObject, SRWebSocketDelegate { unowned let client:SocketIOClient private let workQueue = NSOperationQueue() + private let emitQueue = dispatch_queue_create( + "emitQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) private let handleQueue = dispatch_queue_create( "handleQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) private var forcePolling = false private var pingTimer:NSTimer? + private var postWait = [String]() private var _polling = true private var probing = false - private var probeWait = ProbeQueue() - private var wait = false + private var probeWait = PollWaitQueue() + private var waitingForPoll = false + private var waitingForPost = false private var _websocket = false private var websocketConnected = false + var connected = false var pingInterval:Int? var polling:Bool { return self._polling @@ -133,21 +138,22 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } private func doPoll() { - if self.urlPolling == nil || self.websocket || self.wait { + if self.urlPolling == nil || self.websocket || self.waitingForPoll || !self.connected { return } let req = NSURLRequest(URL: NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) - self.wait = true + self.waitingForPoll = true NSURLConnection.sendAsynchronousRequest(req, queue: self.workQueue) {[weak self] res, data, err in if self == nil { return } else if err != nil { - // println(err) - self?.handlePollingFailed() + if self!.polling { + self?.handlePollingFailed(err) + } return } @@ -156,29 +162,91 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if let str = NSString(data: data, encoding: NSUTF8StringEncoding) { // println(str) - self?.parsePollingMessage(str) + dispatch_async(self?.handleQueue) {[weak self] in + self?.parsePollingMessage(str) + return + } } - self?.wait = false + self?.waitingForPoll = false self?.doPoll() } } private func flushProbeWait() { - for waiter in self.probeWait { - waiter() + // println("flushing probe wait") + dispatch_async(self.emitQueue) {[weak self] in + if self == nil { + return + } + + for waiter in self!.probeWait { + waiter() + } + + self?.probeWait.removeAll(keepCapacity: false) + } + } + + private func flushWaitingForPost() { + if self.postWait.count == 0 || !self.connected || !self.polling { + return } - self.probeWait.removeAll(keepCapacity: false) + let postStr = self.postWait.reduce("") {$0 + $1} + assert(self.postWait.count != 0) + self.postWait.removeAll(keepCapacity: true) + + var req = NSMutableURLRequest(URL: + NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) + + req.HTTPMethod = "POST" + req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") + + let postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, + allowLossyConversion: false)! + + + req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") + req.HTTPBody = postData + + self.waitingForPost = true + NSURLConnection.sendAsynchronousRequest(req, queue: self.workQueue) {[weak self] res, data, err in + if err != nil { + if self!.polling { + self?.handlePollingFailed(err) + } + return + } + + self?.flushWaitingForPost() + self?.waitingForPost = false + self?.doPoll() + } + } + + // A poll failed, tell the client about it + // We check to see if we were closed by the server first + private func handlePollingFailed(reason:NSError?) { + if !self.client.reconnecting { + self.connected = false + self.pingTimer?.invalidate() + self.waitingForPoll = false + self.waitingForPost = false + self.client.pollingDidFail(reason) + } } func open(opts:[String: AnyObject]? = nil) { + if self.waitingForPost || self.waitingForPoll || self.websocket || self.connected { + assert(false, "We're in a bad state, this shouldn't happen.") + } + let (urlPolling, urlWebSocket) = self.createURLs(params: opts) self.urlPolling = urlPolling self.urlWebSocket = urlWebSocket - let time = Int(NSDate().timeIntervalSince1970) - let reqPolling = NSURLRequest(URL: NSURL(string: urlPolling + "&t=\(time)-0&b64=1")!) + let reqPolling = NSURLRequest(URL: NSURL(string: urlPolling + "&b64=1")!) NSURLConnection.sendAsynchronousRequest(reqPolling, queue: self.workQueue) {[weak self] res, data, err in @@ -186,10 +254,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if self == nil { return } else if err != nil || data == nil { - // println(err) - self?.handlePollingFailed() + if self!.polling { + self?.handlePollingFailed(err) + } return - } if let dataString = NSString(data: data, encoding: NSUTF8StringEncoding) { @@ -209,6 +277,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } + self?.connected = true + if let json = NSJSONSerialization.JSONObjectWithData(jsonData!, options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { if let sid = json["sid"] as? String { @@ -237,17 +307,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - // A poll failed, tell the client about it - // We check to see if we were closed by the server first - private func handlePollingFailed() { - if !self.client.reconnecting { - self.pingTimer?.invalidate() - self.wait = false - - self.client.pollingDidFail() - } - } - // Translatation of engine.io-parser#decodePayload private func parsePollingMessage(str:String) { if str.length == 1 { @@ -277,8 +336,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if chr != ":" { length += chr } else { - if testLength(length, &n) || length == "" { - self.handlePollingFailed() + if length == "" || testLength(length, &n) { + self.handlePollingFailed(nil) return } @@ -304,54 +363,52 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private func parseEngineMessage(message:AnyObject?) { // println(message) - dispatch_async(self.handleQueue) {[weak self] in - if let data = message as? NSData { - // Strip off message type - self?.client.parseSocketMessage(data.subdataWithRange(NSMakeRange(1, data.length - 1))) + if let data = message as? NSData { + // Strip off message type + self.client.parseSocketMessage(data.subdataWithRange(NSMakeRange(1, data.length - 1))) + return + } + + var messageString = message as String + var strMessage = RegexMutable(messageString) + + // We should upgrade + if strMessage == "3probe" { + self.upgradeTransport() + return + } + + let type = strMessage["^(\\d)"].groups()?[1] + + if type != PacketType.MESSAGE.rawValue { + // TODO Handle other packets + if messageString.hasPrefix("b4") { + // binary in base64 string + messageString.removeRange(Range(start: messageString.startIndex, + end: advance(messageString.startIndex, 2))) + + if let data = NSData(base64EncodedString: messageString, + options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { + // println("sending \(data)") + self.client.parseSocketMessage(data) + } + return } - var messageString = message as String - var strMessage = RegexMutable(messageString) - - // We should upgrade - if strMessage == "3probe" { - self?.upgradeTransport() + if messageString == PacketType.CLOSE.rawValue { + // do nothing return } - - let type = strMessage["^(\\d)"].groups()?[1] - - if type != PacketType.MESSAGE.rawValue { - // TODO Handle other packets - if messageString.hasPrefix("b4") { - // binary in base64 string - messageString.removeRange(Range(start: messageString.startIndex, - end: advance(messageString.startIndex, 2))) - - if let data = NSData(base64EncodedString: messageString, - options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters) { - // println("sending \(data)") - self?.client.parseSocketMessage(data) - } - - return - } - - if messageString == PacketType.CLOSE.rawValue { - // do nothing - return - } - // println("Got something idk what to do with") - // println(messageString) - } - - // Remove message type - messageString.removeAtIndex(messageString.startIndex) - // println("sending \(messageString)") - - self?.client.parseSocketMessage(messageString) + // println("Got something idk what to do with") + // println(messageString) } + + // Remove message type + messageString.removeAtIndex(messageString.startIndex) + // println("sending \(messageString)") + + self.client.parseSocketMessage(messageString) } private func probeWebSocket() { @@ -363,24 +420,30 @@ class SocketEngine: NSObject, SRWebSocketDelegate { func send(msg:String, datas:[NSData]? = nil) { let _send = {[weak self] (msg:String, datas:[NSData]?) -> () -> Void in return { - if self == nil { + if self == nil || !self!.connected { return } if self!.websocket { - // println("sending ws: \(msg)") + // println("sending ws: \(msg):\(datas)") self?.sendWebSocketMessage(msg, withType: PacketType.MESSAGE, datas: datas) } else { - // println("sending poll: \(msg)") + // println("sending poll: \(msg):\(datas)") self?.sendPollMessage(msg, withType: PacketType.MESSAGE, datas: datas) } } } - if self.probing { - self.probeWait.append(_send(msg, datas)) - } else { - _send(msg, datas)() + dispatch_async(self.emitQueue) {[weak self] in + if self == nil { + return + } + + if self!.probing { + self?.probeWait.append(_send(msg, datas)) + } else { + _send(msg, datas)() + } } } @@ -398,11 +461,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // println("Sending: \(msg)") var postData:NSData var bDatas:[String]? - var req = NSMutableURLRequest(URL: - NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) - - req.HTTPMethod = "POST" - req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") if datas != nil { bDatas = [String]() @@ -424,21 +482,13 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } } - postData = postStr.dataUsingEncoding(NSUTF8StringEncoding, - allowLossyConversion: false)! - + self.postWait.append(postStr) - req.setValue(String(postData.length), forHTTPHeaderField: "Content-Length") - req.HTTPBody = postData - - NSURLConnection.sendAsynchronousRequest(req, queue: self.workQueue) {[weak self] res, data, err in - if err != nil { - // println(err) - self?.handlePollingFailed() - return - } - - self?.doPoll() + if waitingForPost { + self.doPoll() + return + } else { + self.flushWaitingForPost() } } @@ -472,6 +522,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if self.websocketConnected { self.probing = false self._websocket = true + self.waitingForPoll = false self._polling = false self.sendWebSocketMessage("", withType: PacketType.UPGRADE) self.flushProbeWait() @@ -481,7 +532,11 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // Called when a message is recieved func webSocket(webSocket:SRWebSocket!, didReceiveMessage message:AnyObject?) { // println(message) - self.parseEngineMessage(message) + + dispatch_async(self.handleQueue) {[weak self] in + self?.parseEngineMessage(message) + return + } } // Called when the socket is opened @@ -495,13 +550,15 @@ class SocketEngine: NSObject, SRWebSocketDelegate { func webSocket(webSocket:SRWebSocket!, didCloseWithCode code:Int, reason:String!, wasClean:Bool) { self.websocketConnected = false self.probing = false - self.flushProbeWait() if self.websocket { self.pingTimer?.invalidate() + self.connected = false self._websocket = false self._polling = true self.client.webSocketDidCloseWithCode(code, reason: reason, wasClean: wasClean) + } else { + self.flushProbeWait() } } @@ -510,11 +567,13 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.websocketConnected = false self._polling = true self.probing = false - self.flushProbeWait() if self.websocket { self.pingTimer?.invalidate() + self.connected = false self.client.webSocketDidFailWithError(error) + } else { + self.flushProbeWait() } } } \ No newline at end of file diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index e5390b48..c761a392 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -151,11 +151,8 @@ class SocketIOClient { } dispatch_async(self.emitQueue) {[weak self] in - if self == nil { - return - } - self?._emit(event, args) + return } } @@ -169,11 +166,8 @@ class SocketIOClient { self.ackHandlers.append(ackHandler) dispatch_async(self.emitQueue) {[weak self] in - if self == nil { - return - } - self?._emit(event, args, ack: true) + return } return ackHandler @@ -324,29 +318,27 @@ class SocketIOClient { } // Parse an NSArray looking for binary data - private class func parseArray(arr:NSArray, var placeholders:Int) -> (NSArray, Bool, [NSData]) { + private class func parseArray(arr:NSArray, var currentPlaceholder:Int) -> (NSArray, Bool, [NSData]) { var replacementArr = [AnyObject](count: arr.count, repeatedValue: 1) var hasBinary = false var arrayDatas = [NSData]() - if placeholders == -1 { - placeholders = 0 - } - for g in 0.. ([AnyObject], Bool, [NSData]) { var items = [AnyObject](count: args.count, repeatedValue: 1) - var numberOfPlaceholders = -1 + var currentPlaceholder = -1 var hasBinary = false var emitDatas = [NSData]() @@ -401,9 +394,9 @@ class SocketIOClient { if let dict = args[i] as? NSDictionary { // Check for binary data let (newDict, hadBinary, binaryDatas) = SocketIOClient.parseNSDictionary(dict, - placeholders: numberOfPlaceholders) + currentPlaceholder: currentPlaceholder) if hadBinary { - numberOfPlaceholders = binaryDatas.count + currentPlaceholder += binaryDatas.count emitDatas.extend(binaryDatas) hasBinary = true @@ -414,11 +407,11 @@ class SocketIOClient { } else if let arr = args[i] as? NSArray { // arg is array, check for binary let (replace, hadData, newDatas) = SocketIOClient.parseArray(arr, - placeholders: numberOfPlaceholders) + currentPlaceholder: currentPlaceholder) if hadData { hasBinary = true - numberOfPlaceholders += emitDatas.count + currentPlaceholder += newDatas.count for data in newDatas { emitDatas.append(data) @@ -432,8 +425,8 @@ class SocketIOClient { // args is just binary hasBinary = true - numberOfPlaceholders++ - items[i] = ["_placeholder": true, "num": numberOfPlaceholders] + currentPlaceholder++ + items[i] = ["_placeholder": true, "num": currentPlaceholder] emitDatas.append(binaryData) } else { items[i] = args[i] @@ -444,39 +437,36 @@ class SocketIOClient { } // Parses a NSDictionary, looking for NSData objects - private class func parseNSDictionary(dict:NSDictionary, var placeholders:Int) -> (NSDictionary, Bool, [NSData]) { + private class func parseNSDictionary(dict:NSDictionary, var currentPlaceholder:Int) -> (NSDictionary, Bool, [NSData]) { var returnDict = NSMutableDictionary() var hasBinary = false - if placeholders == -1 { - placeholders = 0 - } var returnDatas = [NSData]() for (key, value) in dict { if let binaryData = value as? NSData { + currentPlaceholder++ hasBinary = true - returnDatas.append(binaryData) - returnDict[key as String] = ["_placeholder": true, "num": placeholders++] + returnDict[key as String] = ["_placeholder": true, "num": currentPlaceholder++] } else if let arr = value as? NSArray { - let (replace, hadBinary, arrDatas) = self.parseArray(arr, placeholders: placeholders) + let (replace, hadBinary, arrDatas) = self.parseArray(arr, currentPlaceholder: currentPlaceholder) if hadBinary { hasBinary = true returnDict[key as String] = replace - placeholders += arrDatas.count + currentPlaceholder += arrDatas.count returnDatas.extend(arrDatas) } else { returnDict[key as String] = arr } } else if let dict = value as? NSDictionary { // Recursive - let (nestDict, hadBinary, nestDatas) = self.parseNSDictionary(dict, placeholders: placeholders) + let (nestDict, hadBinary, nestDatas) = self.parseNSDictionary(dict, currentPlaceholder: currentPlaceholder) if hadBinary { hasBinary = true returnDict[key as String] = nestDict - placeholders += nestDatas.count + currentPlaceholder += nestDatas.count returnDatas.extend(nestDatas) } else { returnDict[key as String] = dict @@ -772,9 +762,9 @@ class SocketIOClient { } // Something happened while polling - func pollingDidFail() { + func pollingDidFail(err:NSError?) { if !self.reconnecting { - self.handleEvent("reconnect", data: "XHR polling error", isInternalMessage: true) + self.handleEvent("reconnect", data: err?.localizedDescription, isInternalMessage: true) self.tryReconnect(triesLeft: self.reconnectAttempts) } } From 44bff82a43260e6285056e4069999ae598a7bf44 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 6 Mar 2015 12:13:00 -0500 Subject: [PATCH 30/34] better reconnects --- SwiftIO/SocketEngine.swift | 2 -- SwiftIO/SocketIOClient.swift | 52 +++++++++++++++++++----------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index d44f3a2e..cedb4577 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -194,7 +194,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } let postStr = self.postWait.reduce("") {$0 + $1} - assert(self.postWait.count != 0) self.postWait.removeAll(keepCapacity: true) var req = NSMutableURLRequest(URL: @@ -221,7 +220,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.flushWaitingForPost() self?.waitingForPost = false - self?.doPoll() } } diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index c761a392..516ee096 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -33,21 +33,23 @@ class SocketIOClient { DISPATCH_QUEUE_SERIAL) let emitQueue = dispatch_queue_create("emitQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) - private lazy var params:[String: AnyObject] = [String: AnyObject]() + let reconnectAttempts:Int! + private lazy var params = [String: AnyObject]() private var ackHandlers = [SocketAckHandler]() private var currentAck = -1 + private var currentReconnectAttempt = 0 private var forcePolling = false private var handlers = [SocketEventHandler]() private var waitingData = [SocketEvent]() private var paramConnect = false private var _secure = false + private var reconnectTimer:NSTimer? var closed = false var connected = false var connecting = false var nsp:String? var reconnects = true var reconnecting = false - var reconnectAttempts = -1 var reconnectWait = 10 var secure:Bool { return self._secure @@ -65,6 +67,7 @@ class SocketIOClient { mutURL = mutURL["https://"] ~= "" self.socketURL = mutURL + self.reconnectAttempts = -1 // Set options if opts != nil { @@ -129,6 +132,9 @@ class SocketIOClient { self.connected = true self.connecting = false self.reconnecting = false + self.currentReconnectAttempt = 0 + self.reconnectTimer?.invalidate() + self.reconnectTimer = nil self.handleEvent("connect", data: nil, isInternalMessage: false) } @@ -764,16 +770,15 @@ class SocketIOClient { // Something happened while polling func pollingDidFail(err:NSError?) { if !self.reconnecting { + self.connected = false self.handleEvent("reconnect", data: err?.localizedDescription, isInternalMessage: true) - self.tryReconnect(triesLeft: self.reconnectAttempts) + self.tryReconnect() } } // We lost connection and should attempt to reestablish - func tryReconnect(var #triesLeft:Int) { - self.connected = false - - if triesLeft != -1 && triesLeft <= 0 { + @objc func tryReconnect() { + if self.reconnectAttempts != -1 && self.currentReconnectAttempt + 1 > self.reconnectAttempts { self.didForceClose() return } else if self.connected { @@ -782,26 +787,25 @@ class SocketIOClient { return } - // println("Trying to reconnect #\(reconnectAttempts - triesLeft)") - self.handleEvent("reconnectAttempt", data: triesLeft - 1, isInternalMessage: true) - - let waitTime = UInt64(self.reconnectWait) * NSEC_PER_SEC - let time = dispatch_time(DISPATCH_TIME_NOW, Int64(waitTime)) - - // Wait reconnectWait seconds and then check if connected. Repeat if not - dispatch_after(time, dispatch_get_main_queue()) {[weak self] in - if self == nil || self!.connected || self!.closed { + if self.reconnectTimer == nil { + self.reconnecting = true + dispatch_async(dispatch_get_main_queue()) {[weak self] in + if self == nil { + return + } + + self?.reconnectTimer = NSTimer.scheduledTimerWithTimeInterval(Double(self!.reconnectWait), + target: self!, selector: "tryReconnect", userInfo: nil, repeats: true) return } - if triesLeft != -1 { - triesLeft = triesLeft - 1 - } - - self!.tryReconnect(triesLeft: triesLeft) + return } - self.reconnecting = true + self.handleEvent("reconnectAttempt", data: self.reconnectAttempts - self.currentReconnectAttempt, + isInternalMessage: true) + + self.currentReconnectAttempt++ if self.paramConnect { self.connectWithParams(self.params) } else { @@ -817,7 +821,7 @@ class SocketIOClient { self.didForceClose() } else { self.handleEvent("reconnect", data: reason, isInternalMessage: true) - self.tryReconnect(triesLeft: self.reconnectAttempts) + self.tryReconnect() } } @@ -830,7 +834,7 @@ class SocketIOClient { self.didForceClose() } else if !self.reconnecting { self.handleEvent("reconnect", data: error.localizedDescription, isInternalMessage: true) - self.tryReconnect(triesLeft: self.reconnectAttempts) + self.tryReconnect() } } } \ No newline at end of file From 41c168a29ac468d1c060f356dda5d03b11e606ec Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 6 Mar 2015 16:29:13 -0500 Subject: [PATCH 31/34] Fixes. Add onAny method --- README.md | 3 +- SwiftIO/SocketEngine.swift | 69 +++++++++++++++++++++++------------- SwiftIO/SocketIOClient.swift | 24 +++++++++---- 3 files changed, 63 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index eaf9169b..620b4f77 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,9 @@ Constructor Methods ------- 1. `socket.on(name:String, callback:((data:NSArray?, ack:AckEmitter?) -> Void))` - Adds a handler for an event. Items are passed by an array. `ack` can be used to send an ack when one is requested. See example. +2. `socket.onAny(callback:((event:String, items:AnyObject?)) -> Void)` - Adds a handler for all events. It will be called on any received event. 3. `socket.emit(event:String, args:AnyObject...)` - Sends a message. Can send multiple args. -4. `socket.emitWithAck(event:String, args:AnyObject...) -> SocketAckHandler` - Sends a message that requests an acknoweldgement from the server. Returns a SocketAckHandler which you can use to add an onAck handler. See example. +4. `socket.emitWithAck(event:String, args:AnyObject...) -> SocketAckHandler` - Sends a message that requests an acknowledgement from the server. Returns a SocketAckHandler which you can use to add an onAck handler. See example. 5. `socket.connect()` - Establishes a connection to the server. A "connect" event is fired upon successful connection. 6. `socket.connectWithParams(params:[String: AnyObject])` - Establishes a connection to the server passing the specified params. A "connect" event is fired upon successful connection. 7. `socket.close()` - Closes the socket. Once a socket is closed it should not be reopened. diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index cedb4577..a45e49e4 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -51,6 +51,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private let workQueue = NSOperationQueue() private let emitQueue = dispatch_queue_create( "emitQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) + private let parseQueue = dispatch_queue_create( + "parseQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) private let handleQueue = dispatch_queue_create( "handleQueue".cStringUsingEncoding(NSUTF8StringEncoding), DISPATCH_QUEUE_SERIAL) private var forcePolling = false @@ -162,7 +164,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if let str = NSString(data: data, encoding: NSUTF8StringEncoding) { // println(str) - dispatch_async(self?.handleQueue) {[weak self] in + dispatch_async(self?.parseQueue) {[weak self] in self?.parsePollingMessage(str) return } @@ -189,11 +191,21 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } private func flushWaitingForPost() { - if self.postWait.count == 0 || !self.connected || !self.polling { + if self.postWait.count == 0 || !self.connected { return + } else if self.websocket { + self.flushWaitingForPostToWebSocket() + return + } + + var postStr = "" + + for packet in self.postWait { + let len = countElements(packet) + + postStr += "\(len):\(packet)" } - let postStr = self.postWait.reduce("") {$0 + $1} self.postWait.removeAll(keepCapacity: true) var req = NSMutableURLRequest(URL: @@ -220,7 +232,18 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.flushWaitingForPost() self?.waitingForPost = false + self?.doPoll() + } + } + + // We had packets waiting for send when we upgraded + // Send them raw + private func flushWaitingForPostToWebSocket() { + for msg in self.postWait { + self.ws?.send(msg) } + + self.postWait.removeAll(keepCapacity: true) } // A poll failed, tell the client about it @@ -313,6 +336,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // println(str) + let strArray = Array(str) var length = "" var n = 0 var msg = "" @@ -328,7 +352,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } for var i = 0, l = str.length; i < l; i = i &+ 1 { - let strArray = Array(str) let chr = String(strArray[i]) if chr != ":" { @@ -349,8 +372,12 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } if msg.length != 0 { - fixSwift = msg - self.parseEngineMessage(fixSwift) + // Be sure to capture the value of the msg + dispatch_async(self.handleQueue) {[weak self, msg] in + fixSwift = msg + self?.parseEngineMessage(fixSwift) + return + } } i += n @@ -360,7 +387,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } private func parseEngineMessage(message:AnyObject?) { - // println(message) + // println(message!) if let data = message as? NSData { // Strip off message type self.client.parseSocketMessage(data.subdataWithRange(NSMakeRange(1, data.length - 1))) @@ -382,6 +409,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // TODO Handle other packets if messageString.hasPrefix("b4") { // binary in base64 string + messageString.removeRange(Range(start: messageString.startIndex, end: advance(messageString.startIndex, 2))) @@ -391,6 +419,9 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.client.parseSocketMessage(data) } + return + } else if type == PacketType.NOOP.rawValue { + self.doPoll() return } @@ -456,32 +487,19 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } private func sendPollMessage(msg:String, withType type:PacketType, datas:[NSData]? = nil) { - // println("Sending: \(msg)") - var postData:NSData - var bDatas:[String]? + // println("Sending: poll: \(msg) as type: \(type.rawValue)") + let strMsg = "\(type.rawValue)\(msg)" + + self.postWait.append(strMsg) if datas != nil { - bDatas = [String]() for data in datas! { let (nilData, b64Data) = self.createBinaryDataForSend(data) - let dataLen = countElements(b64Data!) - bDatas!.append("\(dataLen):\(b64Data!)") + self.postWait.append(b64Data!) } } - let strMsg = "\(type.rawValue)\(msg)" - let postCount = countElements(strMsg) - var postStr = "\(postCount):\(strMsg)" - - if bDatas != nil { - for data in bDatas! { - postStr += data - } - } - - self.postWait.append(postStr) - if waitingForPost { self.doPoll() return @@ -491,6 +509,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } private func sendWebSocketMessage(str:String, withType type:PacketType, datas:[NSData]? = nil) { + // println("Sending: ws: \(str) as type: \(type.rawValue)") self.ws?.send("\(type.rawValue)\(str)") if datas != nil { diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index 516ee096..c3d76c84 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -36,6 +36,7 @@ class SocketIOClient { let reconnectAttempts:Int! private lazy var params = [String: AnyObject]() private var ackHandlers = [SocketAckHandler]() + private var anyHandler:((AnyHandler) -> Void)? private var currentAck = -1 private var currentReconnectAttempt = 0 private var forcePolling = false @@ -269,13 +270,17 @@ class SocketIOClient { func handleEvent(event:String, data:AnyObject?, isInternalMessage:Bool = false, wantsAck ack:Int? = nil, withAckType ackType:Int = 3) { // println("Should do event: \(event) with data: \(data)") - dispatch_async(dispatch_get_main_queue()) { - if !self.connected && !isInternalMessage { - return - } - - for handler in self.handlers { - if handler.event == event { + if !self.connected && !isInternalMessage { + return + } + + dispatch_async(dispatch_get_main_queue()) {[weak self] in + self?.anyHandler?((event, data)) + return + } + for handler in self.handlers { + if handler.event == event { + dispatch_async(dispatch_get_main_queue()) { if data is NSArray { if ack != nil { handler.executeCallback(data as? NSArray, withAck: ack!, @@ -318,6 +323,11 @@ class SocketIOClient { self.handlers.append(handler) } + // Adds a handler for any event + func onAny(handler:(AnyHandler) -> Void) { + self.anyHandler = handler + } + // Opens the connection to the socket func open() { self.connect() From ae28a02163d7405891b284f329fd9ee95b587fc7 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 6 Mar 2015 18:24:06 -0500 Subject: [PATCH 32/34] Use a ephemeral NSURLSession for polling, because NSURLConnection.sendAsnc.. seems to either leak memory, or cache --- SwiftIO/SocketEngine.swift | 32 +++++++++++++++----------------- SwiftIO/SocketEventHandler.swift | 1 + 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index a45e49e4..8d6b9610 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -61,6 +61,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private var _polling = true private var probing = false private var probeWait = PollWaitQueue() + private let session:NSURLSession! private var waitingForPoll = false private var waitingForPost = false private var _websocket = false @@ -81,6 +82,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { init(client:SocketIOClient, forcePolling:Bool = false) { self.client = client self.forcePolling = forcePolling + self.session = NSURLSession(configuration: NSURLSessionConfiguration.ephemeralSessionConfiguration(), + delegate: nil, delegateQueue: self.workQueue) } func close() { @@ -144,12 +147,10 @@ class SocketEngine: NSObject, SRWebSocketDelegate { return } - let req = NSURLRequest(URL: - NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) + let req = NSURLRequest(URL: NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) self.waitingForPoll = true - NSURLConnection.sendAsynchronousRequest(req, - queue: self.workQueue) {[weak self] res, data, err in + self.session.dataTaskWithRequest(req) {[weak self] data, res, err in if self == nil { return } else if err != nil { @@ -161,7 +162,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { // println(data) - if let str = NSString(data: data, encoding: NSUTF8StringEncoding) { + if let str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String { // println(str) dispatch_async(self?.parseQueue) {[weak self] in @@ -172,7 +173,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.waitingForPoll = false self?.doPoll() - } + }.resume() } private func flushProbeWait() { @@ -206,10 +207,9 @@ class SocketEngine: NSObject, SRWebSocketDelegate { postStr += "\(len):\(packet)" } - self.postWait.removeAll(keepCapacity: true) + self.postWait.removeAll(keepCapacity: false) - var req = NSMutableURLRequest(URL: - NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) + let req = NSMutableURLRequest(URL: NSURL(string: self.urlPolling! + "&sid=\(self.sid)")!) req.HTTPMethod = "POST" req.setValue("application/html-text", forHTTPHeaderField: "Content-Type") @@ -222,7 +222,8 @@ class SocketEngine: NSObject, SRWebSocketDelegate { req.HTTPBody = postData self.waitingForPost = true - NSURLConnection.sendAsynchronousRequest(req, queue: self.workQueue) {[weak self] res, data, err in + + self.session.dataTaskWithRequest(req) {[weak self] data, res, err in if err != nil { if self!.polling { self?.handlePollingFailed(err) @@ -233,7 +234,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.flushWaitingForPost() self?.waitingForPost = false self?.doPoll() - } + }.resume() } // We had packets waiting for send when we upgraded @@ -269,8 +270,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.urlWebSocket = urlWebSocket let reqPolling = NSURLRequest(URL: NSURL(string: urlPolling + "&b64=1")!) - NSURLConnection.sendAsynchronousRequest(reqPolling, - queue: self.workQueue) {[weak self] res, data, err in + self.session.dataTaskWithRequest(reqPolling) {[weak self] data, res, err in var err:NSError? if self == nil { return @@ -325,7 +325,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.doPoll() self?.startPingTimer() } - } + }.resume() } // Translatation of engine.io-parser#decodePayload @@ -477,8 +477,6 @@ class SocketEngine: NSObject, SRWebSocketDelegate { } func sendPing() { - // println("sending ping") - if self.websocket { self.sendWebSocketMessage("", withType: PacketType.PING) } else { @@ -527,7 +525,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if self.pingInterval == nil { return } - + self.pingTimer?.invalidate() dispatch_async(dispatch_get_main_queue()) { self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(self.pingInterval!), target: self, diff --git a/SwiftIO/SocketEventHandler.swift b/SwiftIO/SocketEventHandler.swift index 3fe92041..87e8a16b 100644 --- a/SwiftIO/SocketEventHandler.swift +++ b/SwiftIO/SocketEventHandler.swift @@ -23,6 +23,7 @@ // THE SOFTWARE. typealias NormalCallback = (NSArray?, AckEmitter?) -> Void +typealias AnyHandler = (event:String, items:AnyObject?) typealias AckEmitter = (AnyObject...) -> Void private func emitAckCallback(socket:SocketIOClient, num:Int, type:Int) -> AckEmitter { From 14bf64233b7bb3b841c7c8abcfa0236cbe374a00 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 6 Mar 2015 18:38:12 -0500 Subject: [PATCH 33/34] add example for onAny --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 620b4f77..5c0d49b4 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,9 @@ let socket = SocketIOClient(socketURL: "https://localhost:8080", opts: [ "forcePolling": true // if true, the socket will only use XHR polling, default is false (polling/WebSockets) ]) +// Called on every event +socket.onAny {println("got event: \($0.event) with items \($0.items)")} + // Socket Events socket.on("connect") {data, ack in println("socket connected") From 6399df4c337383c9bbc17ecd6ad2e9feac9751ba Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 6 Mar 2015 21:17:26 -0500 Subject: [PATCH 34/34] tweaks --- SwiftIO/SocketEngine.swift | 146 ++++++++++++++++--------------- SwiftIO/SocketEventHandler.swift | 5 +- SwiftIO/SocketIOClient.swift | 44 +++++----- 3 files changed, 99 insertions(+), 96 deletions(-) diff --git a/SwiftIO/SocketEngine.swift b/SwiftIO/SocketEngine.swift index 8d6b9610..1c743385 100644 --- a/SwiftIO/SocketEngine.swift +++ b/SwiftIO/SocketEngine.swift @@ -151,29 +151,29 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self.waitingForPoll = true self.session.dataTaskWithRequest(req) {[weak self] data, res, err in - if self == nil { - return - } else if err != nil { - if self!.polling { - self?.handlePollingFailed(err) - } - return + if self == nil { + return + } else if err != nil { + if self!.polling { + self?.handlePollingFailed(err) } + return + } + + // println(data) + + if let str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String { + // println(str) - // println(data) - - if let str = NSString(data: data, encoding: NSUTF8StringEncoding) as? String { - // println(str) - - dispatch_async(self?.parseQueue) {[weak self] in - self?.parsePollingMessage(str) - return - } + dispatch_async(self?.parseQueue) {[weak self] in + self?.parsePollingMessage(str) + return } - - self?.waitingForPoll = false - self?.doPoll() - }.resume() + } + + self?.waitingForPoll = false + self?.doPoll() + }.resume() } private func flushProbeWait() { @@ -229,12 +229,16 @@ class SocketEngine: NSObject, SRWebSocketDelegate { self?.handlePollingFailed(err) } return + } else if self == nil { + return } - self?.flushWaitingForPost() self?.waitingForPost = false - self?.doPoll() - }.resume() + dispatch_async(self!.emitQueue) { + self?.flushWaitingForPost() + self?.doPoll() + return + }}.resume() } // We had packets waiting for send when we upgraded @@ -252,6 +256,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { private func handlePollingFailed(reason:NSError?) { if !self.client.reconnecting { self.connected = false + self.ws?.close() self.pingTimer?.invalidate() self.waitingForPoll = false self.waitingForPost = false @@ -271,61 +276,58 @@ class SocketEngine: NSObject, SRWebSocketDelegate { let reqPolling = NSURLRequest(URL: NSURL(string: urlPolling + "&b64=1")!) self.session.dataTaskWithRequest(reqPolling) {[weak self] data, res, err in - var err:NSError? - if self == nil { + var err2:NSError? + if self == nil { + return + } else if err != nil || data == nil { + self?.handlePollingFailed(err) + return + } + + if let dataString = NSString(data: data, encoding: NSUTF8StringEncoding) { + var mutString = RegexMutable(dataString) + let parsed:[String]? = mutString["(\\d*):(\\d)(\\{.*\\})?"].groups() + + if parsed == nil || parsed?.count != 4 { return - } else if err != nil || data == nil { - if self!.polling { - self?.handlePollingFailed(err) - } + } + + let length = parsed![1] + let type = parsed![2] + let jsonData = parsed![3].dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) + + if type != "0" { + NSLog("Error handshaking") return } - if let dataString = NSString(data: data, encoding: NSUTF8StringEncoding) { - var mutString = RegexMutable(dataString) - let parsed = mutString["(\\d*):(\\d)(\\{.*\\})?"].groups() - - if parsed.count != 4 { - return - } - - let length = parsed[1] - let type = parsed[2] - let jsonData = parsed[3].dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) - - if type != "0" { - NSLog("Error handshaking") - return - } - - self?.connected = true - - if let json = NSJSONSerialization.JSONObjectWithData(jsonData!, - options: NSJSONReadingOptions.AllowFragments, error: &err) as? NSDictionary { - if let sid = json["sid"] as? String { - // println(json) - self?.sid = sid - - if !self!.forcePolling { - self?.ws = SRWebSocket(URL: - NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) - self?.ws?.delegate = self - self?.ws?.open() - } - } else { - NSLog("Error handshaking") - return - } + self?.connected = true + + if let json = NSJSONSerialization.JSONObjectWithData(jsonData!, + options: NSJSONReadingOptions.AllowFragments, error: &err2) as? NSDictionary { + if let sid = json["sid"] as? String { + // println(json) + self?.sid = sid - if let pingInterval = json["pingInterval"] as? Int { - self?.pingInterval = pingInterval / 1000 + if !self!.forcePolling { + self?.ws = SRWebSocket(URL: + NSURL(string: urlWebSocket + "&sid=\(self!.sid)")!) + self?.ws?.delegate = self + self?.ws?.open() } - } - - self?.doPoll() - self?.startPingTimer() + } else { + NSLog("Error handshaking") + return + } + + if let pingInterval = json["pingInterval"] as? Int { + self?.pingInterval = pingInterval / 1000 + } } - }.resume() + + self?.doPoll() + self?.startPingTimer() + }}.resume() } // Translatation of engine.io-parser#decodePayload @@ -525,7 +527,7 @@ class SocketEngine: NSObject, SRWebSocketDelegate { if self.pingInterval == nil { return } - + self.pingTimer?.invalidate() dispatch_async(dispatch_get_main_queue()) { self.pingTimer = NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(self.pingInterval!), target: self, diff --git a/SwiftIO/SocketEventHandler.swift b/SwiftIO/SocketEventHandler.swift index 87e8a16b..c59e92ca 100644 --- a/SwiftIO/SocketEventHandler.swift +++ b/SwiftIO/SocketEventHandler.swift @@ -45,6 +45,9 @@ class SocketEventHandler { func executeCallback(_ items:NSArray? = nil, withAck ack:Int? = nil, withAckType type:Int? = nil, withSocket socket:SocketIOClient? = nil) { - callback?(items, ack != nil ? emitAckCallback(socket!, ack!, type!) : nil) + dispatch_async(dispatch_get_main_queue()) {[weak self] in + self?.callback?(items, ack != nil ? emitAckCallback(socket!, ack!, type!) : nil) + return + } } } \ No newline at end of file diff --git a/SwiftIO/SocketIOClient.swift b/SwiftIO/SocketIOClient.swift index c3d76c84..a37f6418 100644 --- a/SwiftIO/SocketIOClient.swift +++ b/SwiftIO/SocketIOClient.swift @@ -280,30 +280,28 @@ class SocketIOClient { } for handler in self.handlers { if handler.event == event { - dispatch_async(dispatch_get_main_queue()) { - if data is NSArray { - if ack != nil { - handler.executeCallback(data as? NSArray, withAck: ack!, - withAckType: ackType, withSocket: self) - } else { - handler.executeCallback(data as? NSArray) - } + if data is NSArray { + if ack != nil { + handler.executeCallback(data as? NSArray, withAck: ack!, + withAckType: ackType, withSocket: self) } else { - - // Trying to do a ternary expression in the executeCallback method - // seemed to crash Swift - var dataArr:NSArray? = nil - - if let data:AnyObject = data { - dataArr = [data] - } - - if ack != nil { - handler.executeCallback(dataArr, withAck: ack!, - withAckType: ackType, withSocket: self) - } else { - handler.executeCallback(dataArr) - } + handler.executeCallback(data as? NSArray) + } + } else { + + // Trying to do a ternary expression in the executeCallback method + // seemed to crash Swift + var dataArr:NSArray? = nil + + if let data:AnyObject = data { + dataArr = [data] + } + + if ack != nil { + handler.executeCallback(dataArr, withAck: ack!, + withAckType: ackType, withSocket: self) + } else { + handler.executeCallback(dataArr) } } }