SocketEngine.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. //
  2. // SocketEngine.swift
  3. // Socket.IO-Client-Swift
  4. //
  5. // Created by Erik Little on 3/3/15.
  6. //
  7. // Permission is hereby granted, free of charge, to any person obtaining a copy
  8. // of this software and associated documentation files (the "Software"), to deal
  9. // in the Software without restriction, including without limitation the rights
  10. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. // copies of the Software, and to permit persons to whom the Software is
  12. // furnished to do so, subject to the following conditions:
  13. //
  14. // The above copyright notice and this permission notice shall be included in
  15. // all copies or substantial portions of the Software.
  16. //
  17. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. // THE SOFTWARE.
  24. import Foundation
  25. public final class SocketEngine : NSObject, SocketEnginePollable, SocketEngineWebsocket {
  26. public let emitQueue = dispatch_queue_create("com.socketio.engineEmitQueue", DISPATCH_QUEUE_SERIAL)
  27. public let handleQueue = dispatch_queue_create("com.socketio.engineHandleQueue", DISPATCH_QUEUE_SERIAL)
  28. public let parseQueue = dispatch_queue_create("com.socketio.engineParseQueue", DISPATCH_QUEUE_SERIAL)
  29. public var connectParams: [String: AnyObject]? {
  30. didSet {
  31. (urlPolling, urlWebSocket) = createURLs()
  32. }
  33. }
  34. public var postWait = [String]()
  35. public var waitingForPoll = false
  36. public var waitingForPost = false
  37. public private(set) var closed = false
  38. public private(set) var connected = false
  39. public private(set) var cookies: [NSHTTPCookie]?
  40. public private(set) var doubleEncodeUTF8 = true
  41. public private(set) var extraHeaders: [String: String]?
  42. public private(set) var fastUpgrade = false
  43. public private(set) var forcePolling = false
  44. public private(set) var forceWebsockets = false
  45. public private(set) var invalidated = false
  46. public private(set) var polling = true
  47. public private(set) var probing = false
  48. public private(set) var session: NSURLSession?
  49. public private(set) var sid = ""
  50. public private(set) var socketPath = "/engine.io/"
  51. public private(set) var urlPolling = NSURL()
  52. public private(set) var urlWebSocket = NSURL()
  53. public private(set) var websocket = false
  54. public private(set) var ws: WebSocket?
  55. public weak var client: SocketEngineClient?
  56. private weak var sessionDelegate: NSURLSessionDelegate?
  57. private let logType = "SocketEngine"
  58. private let url: NSURL
  59. private var pingInterval: Double?
  60. private var pingTimeout = 0.0 {
  61. didSet {
  62. pongsMissedMax = Int(pingTimeout / (pingInterval ?? 25))
  63. }
  64. }
  65. private var pongsMissed = 0
  66. private var pongsMissedMax = 0
  67. private var probeWait = ProbeWaitQueue()
  68. private var secure = false
  69. private var selfSigned = false
  70. private var voipEnabled = false
  71. public init(client: SocketEngineClient, url: NSURL, options: Set<SocketIOClientOption>) {
  72. self.client = client
  73. self.url = url
  74. for option in options {
  75. switch option {
  76. case let .ConnectParams(params):
  77. connectParams = params
  78. case let .Cookies(cookies):
  79. self.cookies = cookies
  80. case let .DoubleEncodeUTF8(encode):
  81. doubleEncodeUTF8 = encode
  82. case let .ExtraHeaders(headers):
  83. extraHeaders = headers
  84. case let .SessionDelegate(delegate):
  85. sessionDelegate = delegate
  86. case let .ForcePolling(force):
  87. forcePolling = force
  88. case let .ForceWebsockets(force):
  89. forceWebsockets = force
  90. case let .Path(path):
  91. socketPath = path
  92. case let .VoipEnabled(enable):
  93. voipEnabled = enable
  94. case let .Secure(secure):
  95. self.secure = secure
  96. case let .SelfSigned(selfSigned):
  97. self.selfSigned = selfSigned
  98. default:
  99. continue
  100. }
  101. }
  102. super.init()
  103. (urlPolling, urlWebSocket) = createURLs()
  104. }
  105. public convenience init(client: SocketEngineClient, url: NSURL, options: NSDictionary?) {
  106. self.init(client: client, url: url, options: options?.toSocketOptionsSet() ?? [])
  107. }
  108. deinit {
  109. DefaultSocketLogger.Logger.log("Engine is being released", type: logType)
  110. closed = true
  111. stopPolling()
  112. }
  113. private func checkAndHandleEngineError(msg: String) {
  114. guard let stringData = msg.dataUsingEncoding(NSUTF8StringEncoding,
  115. allowLossyConversion: false) else { return }
  116. do {
  117. if let dict = try NSJSONSerialization.JSONObjectWithData(stringData, options: .MutableContainers) as? NSDictionary {
  118. guard let error = dict["message"] as? String else { return }
  119. /*
  120. 0: Unknown transport
  121. 1: Unknown sid
  122. 2: Bad handshake request
  123. 3: Bad request
  124. */
  125. didError(error)
  126. }
  127. } catch {
  128. didError("Got unknown error from server \(msg)")
  129. }
  130. }
  131. private func checkIfMessageIsBase64Binary(message: String) -> Bool {
  132. if message.hasPrefix("b4") {
  133. // binary in base64 string
  134. let noPrefix = message[message.startIndex.advancedBy(2)..<message.endIndex]
  135. if let data = NSData(base64EncodedString: noPrefix,
  136. options: .IgnoreUnknownCharacters) {
  137. client?.parseEngineBinaryData(data)
  138. }
  139. return true
  140. } else {
  141. return false
  142. }
  143. }
  144. /// Starts the connection to the server
  145. public func connect() {
  146. if connected {
  147. DefaultSocketLogger.Logger.error("Engine tried opening while connected. Assuming this was a reconnect", type: logType)
  148. disconnect("reconnect")
  149. }
  150. DefaultSocketLogger.Logger.log("Starting engine", type: logType)
  151. DefaultSocketLogger.Logger.log("Handshaking", type: logType)
  152. resetEngine()
  153. if forceWebsockets {
  154. polling = false
  155. websocket = true
  156. createWebsocketAndConnect()
  157. return
  158. }
  159. let reqPolling = NSMutableURLRequest(URL: urlPolling)
  160. if cookies != nil {
  161. let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!)
  162. reqPolling.allHTTPHeaderFields = headers
  163. }
  164. if let extraHeaders = extraHeaders {
  165. for (headerName, value) in extraHeaders {
  166. reqPolling.setValue(value, forHTTPHeaderField: headerName)
  167. }
  168. }
  169. dispatch_async(emitQueue) {
  170. self.doLongPoll(reqPolling)
  171. }
  172. }
  173. private func createURLs() -> (NSURL, NSURL) {
  174. if client == nil {
  175. return (NSURL(), NSURL())
  176. }
  177. let urlPolling = NSURLComponents(string: url.absoluteString)!
  178. let urlWebSocket = NSURLComponents(string: url.absoluteString)!
  179. var queryString = ""
  180. urlWebSocket.path = socketPath
  181. urlPolling.path = socketPath
  182. if secure {
  183. urlPolling.scheme = "https"
  184. urlWebSocket.scheme = "wss"
  185. } else {
  186. urlPolling.scheme = "http"
  187. urlWebSocket.scheme = "ws"
  188. }
  189. if connectParams != nil {
  190. for (key, value) in connectParams! {
  191. let keyEsc = key.urlEncode()!
  192. let valueEsc = "\(value)".urlEncode()!
  193. queryString += "&\(keyEsc)=\(valueEsc)"
  194. }
  195. }
  196. urlWebSocket.percentEncodedQuery = "transport=websocket" + queryString
  197. urlPolling.percentEncodedQuery = "transport=polling&b64=1" + queryString
  198. return (urlPolling.URL!, urlWebSocket.URL!)
  199. }
  200. private func createWebsocketAndConnect() {
  201. ws = WebSocket(url: urlWebSocketWithSid)
  202. if cookies != nil {
  203. let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!)
  204. for (key, value) in headers {
  205. ws?.headers[key] = value
  206. }
  207. }
  208. if extraHeaders != nil {
  209. for (headerName, value) in extraHeaders! {
  210. ws?.headers[headerName] = value
  211. }
  212. }
  213. ws?.queue = handleQueue
  214. ws?.voipEnabled = voipEnabled
  215. ws?.delegate = self
  216. ws?.selfSignedSSL = selfSigned
  217. ws?.connect()
  218. }
  219. public func didError(error: String) {
  220. DefaultSocketLogger.Logger.error(error, type: logType)
  221. client?.engineDidError(error)
  222. disconnect(error)
  223. }
  224. public func disconnect(reason: String) {
  225. func postSendClose(data: NSData?, _ res: NSURLResponse?, _ err: NSError?) {
  226. sid = ""
  227. closed = true
  228. invalidated = true
  229. connected = false
  230. ws?.disconnect()
  231. stopPolling()
  232. client?.engineDidClose(reason)
  233. }
  234. DefaultSocketLogger.Logger.log("Engine is being closed.", type: logType)
  235. if closed {
  236. client?.engineDidClose(reason)
  237. return
  238. }
  239. if websocket {
  240. sendWebSocketMessage("", withType: .Close, withData: [])
  241. postSendClose(nil, nil, nil)
  242. } else {
  243. // We need to take special care when we're polling that we send it ASAP
  244. // Also make sure we're on the emitQueue since we're touching postWait
  245. dispatch_sync(emitQueue) {
  246. self.postWait.append(String(SocketEnginePacketType.Close.rawValue))
  247. let req = self.createRequestForPostWithPostWait()
  248. self.doRequest(req, withCallback: postSendClose)
  249. }
  250. }
  251. }
  252. public func doFastUpgrade() {
  253. if waitingForPoll {
  254. DefaultSocketLogger.Logger.error("Outstanding poll when switched to WebSockets," +
  255. "we'll probably disconnect soon. You should report this.", type: logType)
  256. }
  257. sendWebSocketMessage("", withType: .Upgrade, withData: [])
  258. websocket = true
  259. polling = false
  260. fastUpgrade = false
  261. probing = false
  262. flushProbeWait()
  263. }
  264. private func flushProbeWait() {
  265. DefaultSocketLogger.Logger.log("Flushing probe wait", type: logType)
  266. dispatch_async(emitQueue) {
  267. for waiter in self.probeWait {
  268. self.write(waiter.msg, withType: waiter.type, withData: waiter.data)
  269. }
  270. self.probeWait.removeAll(keepCapacity: false)
  271. if self.postWait.count != 0 {
  272. self.flushWaitingForPostToWebSocket()
  273. }
  274. }
  275. }
  276. // We had packets waiting for send when we upgraded
  277. // Send them raw
  278. public func flushWaitingForPostToWebSocket() {
  279. guard let ws = self.ws else { return }
  280. for msg in postWait {
  281. ws.writeString(fixDoubleUTF8(msg))
  282. }
  283. postWait.removeAll(keepCapacity: true)
  284. }
  285. private func handleClose(reason: String) {
  286. client?.engineDidClose(reason)
  287. }
  288. private func handleMessage(message: String) {
  289. client?.parseEngineMessage(message)
  290. }
  291. private func handleNOOP() {
  292. doPoll()
  293. }
  294. private func handleOpen(openData: String) {
  295. let mesData = openData.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
  296. do {
  297. let json = try NSJSONSerialization.JSONObjectWithData(mesData,
  298. options: NSJSONReadingOptions.AllowFragments) as? NSDictionary
  299. if let sid = json?["sid"] as? String {
  300. let upgradeWs: Bool
  301. self.sid = sid
  302. connected = true
  303. if let upgrades = json?["upgrades"] as? [String] {
  304. upgradeWs = upgrades.contains("websocket")
  305. } else {
  306. upgradeWs = false
  307. }
  308. if let pingInterval = json?["pingInterval"] as? Double, pingTimeout = json?["pingTimeout"] as? Double {
  309. self.pingInterval = pingInterval / 1000.0
  310. self.pingTimeout = pingTimeout / 1000.0
  311. }
  312. if !forcePolling && !forceWebsockets && upgradeWs {
  313. createWebsocketAndConnect()
  314. }
  315. sendPing()
  316. if !forceWebsockets {
  317. doPoll()
  318. }
  319. client?.engineDidOpen("Connect")
  320. }
  321. } catch {
  322. didError("Error parsing open packet")
  323. }
  324. }
  325. private func handlePong(pongMessage: String) {
  326. pongsMissed = 0
  327. // We should upgrade
  328. if pongMessage == "3probe" {
  329. upgradeTransport()
  330. }
  331. }
  332. public func parseEngineData(data: NSData) {
  333. DefaultSocketLogger.Logger.log("Got binary data: %@", type: "SocketEngine", args: data)
  334. client?.parseEngineBinaryData(data.subdataWithRange(NSMakeRange(1, data.length - 1)))
  335. }
  336. public func parseEngineMessage(message: String, fromPolling: Bool) {
  337. DefaultSocketLogger.Logger.log("Got message: %@", type: logType, args: message)
  338. let reader = SocketStringReader(message: message)
  339. let fixedString: String
  340. guard let type = SocketEnginePacketType(rawValue: Int(reader.currentCharacter) ?? -1) else {
  341. if !checkIfMessageIsBase64Binary(message) {
  342. checkAndHandleEngineError(message)
  343. }
  344. return
  345. }
  346. if fromPolling && type != .Noop && doubleEncodeUTF8 {
  347. fixedString = fixDoubleUTF8(message)
  348. } else {
  349. fixedString = message
  350. }
  351. switch type {
  352. case .Message:
  353. handleMessage(fixedString[fixedString.startIndex.successor()..<fixedString.endIndex])
  354. case .Noop:
  355. handleNOOP()
  356. case .Pong:
  357. handlePong(fixedString)
  358. case .Open:
  359. handleOpen(fixedString[fixedString.startIndex.successor()..<fixedString.endIndex])
  360. case .Close:
  361. handleClose(fixedString)
  362. default:
  363. DefaultSocketLogger.Logger.log("Got unknown packet type", type: logType)
  364. }
  365. }
  366. // Puts the engine back in its default state
  367. private func resetEngine() {
  368. closed = false
  369. connected = false
  370. fastUpgrade = false
  371. polling = true
  372. probing = false
  373. invalidated = false
  374. session = NSURLSession(configuration: .defaultSessionConfiguration(),
  375. delegate: sessionDelegate,
  376. delegateQueue: NSOperationQueue())
  377. sid = ""
  378. waitingForPoll = false
  379. waitingForPost = false
  380. websocket = false
  381. }
  382. private func sendPing() {
  383. if !connected {
  384. return
  385. }
  386. //Server is not responding
  387. if pongsMissed > pongsMissedMax {
  388. client?.engineDidClose("Ping timeout")
  389. return
  390. }
  391. if let pingInterval = pingInterval {
  392. pongsMissed += 1
  393. write("", withType: .Ping, withData: [])
  394. let time = dispatch_time(DISPATCH_TIME_NOW, Int64(pingInterval * Double(NSEC_PER_SEC)))
  395. dispatch_after(time, dispatch_get_main_queue()) {[weak self] in
  396. self?.sendPing()
  397. }
  398. }
  399. }
  400. // Moves from long-polling to websockets
  401. private func upgradeTransport() {
  402. if ws?.isConnected ?? false {
  403. DefaultSocketLogger.Logger.log("Upgrading transport to WebSockets", type: logType)
  404. fastUpgrade = true
  405. sendPollMessage("", withType: .Noop, withData: [])
  406. // After this point, we should not send anymore polling messages
  407. }
  408. }
  409. /// Write a message, independent of transport.
  410. public func write(msg: String, withType type: SocketEnginePacketType, withData data: [NSData]) {
  411. dispatch_async(emitQueue) {
  412. guard self.connected else { return }
  413. if self.websocket {
  414. DefaultSocketLogger.Logger.log("Writing ws: %@ has data: %@",
  415. type: self.logType, args: msg, data.count != 0)
  416. self.sendWebSocketMessage(msg, withType: type, withData: data)
  417. } else if !self.probing {
  418. DefaultSocketLogger.Logger.log("Writing poll: %@ has data: %@",
  419. type: self.logType, args: msg, data.count != 0)
  420. self.sendPollMessage(msg, withType: type, withData: data)
  421. } else {
  422. self.probeWait.append((msg, type, data))
  423. }
  424. }
  425. }
  426. // Delegate methods
  427. public func websocketDidConnect(socket: WebSocket) {
  428. if !forceWebsockets {
  429. probing = true
  430. probeWebSocket()
  431. } else {
  432. connected = true
  433. probing = false
  434. polling = false
  435. }
  436. }
  437. public func websocketDidDisconnect(socket: WebSocket, error: NSError?) {
  438. probing = false
  439. if closed {
  440. client?.engineDidClose("Disconnect")
  441. return
  442. }
  443. if websocket {
  444. connected = false
  445. websocket = false
  446. let reason = error?.localizedDescription ?? "Socket Disconnected"
  447. if error != nil {
  448. didError(reason)
  449. }
  450. client?.engineDidClose(reason)
  451. } else {
  452. flushProbeWait()
  453. }
  454. }
  455. }