bump websocket

This commit is contained in:
Erik 2016-01-18 10:03:16 -05:00
parent d8b5293ca1
commit bcb556b66c
3 changed files with 79 additions and 72 deletions

View File

@ -61,9 +61,8 @@ import Foundation
extension SocketEngineSpec { extension SocketEngineSpec {
func createBinaryDataForSend(data: NSData) -> Either<NSData, String> { func createBinaryDataForSend(data: NSData) -> Either<NSData, String> {
if websocket { if websocket {
var byteArray = [UInt8](count: 1, repeatedValue: 0x0) var byteArray = [UInt8](count: 1, repeatedValue: 0x4)
byteArray[0] = 4
let mutData = NSMutableData(bytes: &byteArray, length: 1) let mutData = NSMutableData(bytes: &byteArray, length: 1)
mutData.appendData(data) mutData.appendData(data)

View File

@ -100,7 +100,7 @@ public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable
/** /**
Not so type safe way to create a SocketIOClient, meant for Objective-C compatiblity. Not so type safe way to create a SocketIOClient, meant for Objective-C compatiblity.
If using Swift it's recommended to use `init(var socketURL: String, opts: SocketOptionsDictionary? = nil)` If using Swift it's recommended to use `init(var socketURL: String, options: Set<SocketIOClientOption>)`
*/ */
public convenience init(socketURL: String, options: NSDictionary?) { public convenience init(socketURL: String, options: NSDictionary?) {
self.init(socketURL: socketURL, self.init(socketURL: socketURL,

View File

@ -111,13 +111,14 @@ public class WebSocket : NSObject, NSStreamDelegate {
public var selfSignedSSL = false public var selfSignedSSL = false
private var security: SSLSecurity? private var security: SSLSecurity?
public var enabledSSLCipherSuites: [SSLCipherSuite]? public var enabledSSLCipherSuites: [SSLCipherSuite]?
public var origin: String?
public var isConnected :Bool { public var isConnected :Bool {
return connected return connected
} }
public var currentURL: NSURL {return url}
private var url: NSURL private var url: NSURL
private var inputStream: NSInputStream? private var inputStream: NSInputStream?
private var outputStream: NSOutputStream? private var outputStream: NSOutputStream?
private var isRunLoop = false
private var connected = false private var connected = false
private var isCreated = false private var isCreated = false
private var writeQueue = NSOperationQueue() private var writeQueue = NSOperationQueue()
@ -126,10 +127,13 @@ public class WebSocket : NSObject, NSStreamDelegate {
private var fragBuffer: NSData? private var fragBuffer: NSData?
private var certValidated = false private var certValidated = false
private var didDisconnect = false private var didDisconnect = false
//the shared processing queue used for all websocket
private static let sharedWorkQueue = dispatch_queue_create("com.vluxe.starscream.websocket", DISPATCH_QUEUE_SERIAL)
//used for setting protocols. //used for setting protocols.
public init(url: NSURL, protocols: [String]? = nil) { public init(url: NSURL, protocols: [String]? = nil) {
self.url = url self.url = url
self.origin = url.absoluteString
writeQueue.maxConcurrentOperationCount = 1 writeQueue.maxConcurrentOperationCount = 1
optionalProtocols = protocols optionalProtocols = protocols
} }
@ -137,15 +141,10 @@ public class WebSocket : NSObject, NSStreamDelegate {
///Connect to the websocket server on a background thread ///Connect to the websocket server on a background thread
public func connect() { public func connect() {
guard !isCreated else { return } guard !isCreated else { return }
didDisconnect = false
dispatch_async(queue) { [weak self] in isCreated = true
self?.didDisconnect = false createHTTPRequest()
} isCreated = false
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)) { [weak self] in
self?.isCreated = true
self?.createHTTPRequest()
self?.isCreated = false
}
} }
/** /**
@ -175,20 +174,22 @@ public class WebSocket : NSObject, NSStreamDelegate {
///write a string to the websocket. This sends it as a text frame. ///write a string to the websocket. This sends it as a text frame.
public func writeString(str: String) { public func writeString(str: String) {
guard isConnected else { return }
dequeueWrite(str.dataUsingEncoding(NSUTF8StringEncoding)!, code: .TextFrame) dequeueWrite(str.dataUsingEncoding(NSUTF8StringEncoding)!, code: .TextFrame)
} }
///write binary data to the websocket. This sends it as a binary frame. ///write binary data to the websocket. This sends it as a binary frame.
public func writeData(data: NSData) { public func writeData(data: NSData) {
guard isConnected else { return }
dequeueWrite(data, code: .BinaryFrame) dequeueWrite(data, code: .BinaryFrame)
} }
//write a ping to the websocket. This sends it as a control frame. //write a ping to the websocket. This sends it as a control frame.
//yodel a sound to the planet. This sends it as an astroid. http://youtu.be/Eu5ZJELRiJ8?t=42s //yodel a sound to the planet. This sends it as an astroid. http://youtu.be/Eu5ZJELRiJ8?t=42s
public func writePing(data: NSData) { public func writePing(data: NSData) {
guard isConnected else { return }
dequeueWrite(data, code: .Ping) dequeueWrite(data, code: .Ping)
} }
//private methods below!
//private method that starts the connection //private method that starts the connection
private func createHTTPRequest() { private func createHTTPRequest() {
@ -211,7 +212,9 @@ public class WebSocket : NSObject, NSStreamDelegate {
} }
addHeader(urlRequest, key: headerWSVersionName, val: headerWSVersionValue) addHeader(urlRequest, key: headerWSVersionName, val: headerWSVersionValue)
addHeader(urlRequest, key: headerWSKeyName, val: generateWebSocketKey()) addHeader(urlRequest, key: headerWSKeyName, val: generateWebSocketKey())
addHeader(urlRequest, key: headerOriginName, val: url.absoluteString) if let origin = origin {
addHeader(urlRequest, key: headerOriginName, val: origin)
}
addHeader(urlRequest, key: headerWSHostName, val: "\(url.host!):\(port!)") addHeader(urlRequest, key: headerWSHostName, val: "\(url.host!):\(port!)")
for (key,value) in headers { for (key,value) in headers {
addHeader(urlRequest, key: key, val: value) addHeader(urlRequest, key: key, val: value)
@ -221,10 +224,12 @@ public class WebSocket : NSObject, NSStreamDelegate {
initStreamsWithData(serializedRequest, Int(port!)) initStreamsWithData(serializedRequest, Int(port!))
} }
} }
//Add a header to the CFHTTPMessage by using the NSString bridges to CFString //Add a header to the CFHTTPMessage by using the NSString bridges to CFString
private func addHeader(urlRequest: CFHTTPMessage, key: NSString, val: NSString) { private func addHeader(urlRequest: CFHTTPMessage, key: NSString, val: NSString) {
CFHTTPMessageSetHeaderFieldValue(urlRequest, key, val) CFHTTPMessageSetHeaderFieldValue(urlRequest, key, val)
} }
//generate a websocket key as needed in rfc //generate a websocket key as needed in rfc
private func generateWebSocketKey() -> String { private func generateWebSocketKey() -> String {
var key = "" var key = ""
@ -237,6 +242,7 @@ public class WebSocket : NSObject, NSStreamDelegate {
let baseKey = data?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0)) let baseKey = data?.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
return baseKey! return baseKey!
} }
//Start the stream connection and write the data to the output stream //Start the stream connection and write the data to the output stream
private func initStreamsWithData(data: NSData, _ port: Int) { private func initStreamsWithData(data: NSData, _ port: Int) {
//higher level API we will cut over to at some point //higher level API we will cut over to at some point
@ -283,15 +289,16 @@ public class WebSocket : NSObject, NSStreamDelegate {
} }
} }
} }
isRunLoop = true CFReadStreamSetDispatchQueue(inStream, WebSocket.sharedWorkQueue)
inStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) CFWriteStreamSetDispatchQueue(outStream, WebSocket.sharedWorkQueue)
outStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
inStream.open() inStream.open()
outStream.open() outStream.open()
let bytes = UnsafePointer<UInt8>(data.bytes) let bytes = UnsafePointer<UInt8>(data.bytes)
outStream.write(bytes, maxLength: data.length) writeQueue.addOperationWithBlock {
while(isRunLoop) { while !outStream.hasSpaceAvailable {
NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture() as NSDate) usleep(100) //wait until the socket is ready
}
outStream.write(bytes, maxLength: data.length)
} }
} }
//delegate for the stream methods. Processes incoming bytes //delegate for the stream methods. Processes incoming bytes
@ -324,18 +331,16 @@ public class WebSocket : NSObject, NSStreamDelegate {
private func disconnectStream(error: NSError?) { private func disconnectStream(error: NSError?) {
writeQueue.waitUntilAllOperationsAreFinished() writeQueue.waitUntilAllOperationsAreFinished()
if let stream = inputStream { if let stream = inputStream {
stream.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) CFReadStreamSetDispatchQueue(stream, nil)
stream.close() stream.close()
} }
if let stream = outputStream { if let stream = outputStream {
stream.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode) CFWriteStreamSetDispatchQueue(stream, nil)
stream.close() stream.close()
} }
outputStream = nil outputStream = nil
isRunLoop = false
certValidated = false certValidated = false
doDisconnect(error) doDisconnect(error)
connected = false
} }
///handles the incoming bytes and sending them to the proper processing method ///handles the incoming bytes and sending them to the proper processing method
@ -345,24 +350,13 @@ public class WebSocket : NSObject, NSStreamDelegate {
let length = inputStream!.read(buffer, maxLength: BUFFER_MAX) let length = inputStream!.read(buffer, maxLength: BUFFER_MAX)
guard length > 0 else { return } guard length > 0 else { return }
var process = false
if !connected { if inputQueue.count == 0 {
connected = processHTTP(buffer, bufferLen: length) process = true
if !connected { }
let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false).takeRetainedValue() inputQueue.append(NSData(bytes: buffer, length: length))
CFHTTPMessageAppendBytes(response, buffer, length) if process {
let code = CFHTTPMessageGetResponseStatusCode(response) dequeueInput()
doDisconnect(errorWithDetail("Invalid HTTP upgrade", code: UInt16(code)))
}
} else {
var process = false
if inputQueue.count == 0 {
process = true
}
inputQueue.append(NSData(bytes: buffer, length: length))
if process {
dequeueInput()
}
} }
} }
///dequeue the incoming input so it is processed in order ///dequeue the incoming input so it is processed in order
@ -378,12 +372,34 @@ public class WebSocket : NSObject, NSStreamDelegate {
self.fragBuffer = nil self.fragBuffer = nil
} }
let buffer = UnsafePointer<UInt8>(work.bytes) let buffer = UnsafePointer<UInt8>(work.bytes)
processRawMessage(buffer, bufferLen: work.length) let length = work.length
if !connected {
processTCPHandshake(buffer, bufferLen: length)
} else {
processRawMessage(buffer, bufferLen: length)
}
inputQueue = inputQueue.filter{$0 != data} inputQueue = inputQueue.filter{$0 != data}
dequeueInput() dequeueInput()
} }
//handle checking the inital connection status
private func processTCPHandshake(buffer: UnsafePointer<UInt8>, bufferLen: Int) {
let code = processHTTP(buffer, bufferLen: bufferLen)
switch code {
case 0:
connected = true
dispatch_async(queue) { [weak self] in
guard let s = self else { return }
s.onConnect?()
s.delegate?.websocketDidConnect(s)
}
case -1: break //do nothing, we are going to collect more data
default:
doDisconnect(errorWithDetail("Invalid HTTP upgrade", code: UInt16(code)))
}
}
///Finds the HTTP Packet in the TCP stream, by looking for the CRLF. ///Finds the HTTP Packet in the TCP stream, by looking for the CRLF.
private func processHTTP(buffer: UnsafePointer<UInt8>, bufferLen: Int) -> Bool { private func processHTTP(buffer: UnsafePointer<UInt8>, bufferLen: Int) -> Int {
let CRLFBytes = [UInt8(ascii: "\r"), UInt8(ascii: "\n"), UInt8(ascii: "\r"), UInt8(ascii: "\n")] let CRLFBytes = [UInt8(ascii: "\r"), UInt8(ascii: "\n"), UInt8(ascii: "\r"), UInt8(ascii: "\n")]
var k = 0 var k = 0
var totalSize = 0 var totalSize = 0
@ -399,39 +415,37 @@ public class WebSocket : NSObject, NSStreamDelegate {
} }
} }
if totalSize > 0 { if totalSize > 0 {
if validateResponse(buffer, bufferLen: totalSize) { let code = validateResponse(buffer, bufferLen: totalSize)
dispatch_async(queue) { [weak self] in if code != 0 {
guard let s = self else { return } return code
s.onConnect?()
s.delegate?.websocketDidConnect(s)
}
totalSize += 1 //skip the last \n
let restSize = bufferLen - totalSize
if restSize > 0 {
processRawMessage((buffer+totalSize),bufferLen: restSize)
}
return true
} }
totalSize += 1 //skip the last \n
let restSize = bufferLen - totalSize
if restSize > 0 {
processRawMessage((buffer+totalSize),bufferLen: restSize)
}
return 0 //success
} }
return false return -1 //was unable to find the full TCP header
} }
///validates the HTTP is a 101 as per the RFC spec ///validates the HTTP is a 101 as per the RFC spec
private func validateResponse(buffer: UnsafePointer<UInt8>, bufferLen: Int) -> Bool { private func validateResponse(buffer: UnsafePointer<UInt8>, bufferLen: Int) -> Int {
let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false).takeRetainedValue() let response = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, false).takeRetainedValue()
CFHTTPMessageAppendBytes(response, buffer, bufferLen) CFHTTPMessageAppendBytes(response, buffer, bufferLen)
if CFHTTPMessageGetResponseStatusCode(response) != 101 { let code = CFHTTPMessageGetResponseStatusCode(response)
return false if code != 101 {
return code
} }
if let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response) { if let cfHeaders = CFHTTPMessageCopyAllHeaderFields(response) {
let headers = cfHeaders.takeRetainedValue() as NSDictionary let headers = cfHeaders.takeRetainedValue() as NSDictionary
if let acceptKey = headers[headerWSAcceptName] as? NSString { if let acceptKey = headers[headerWSAcceptName] as? NSString {
if acceptKey.length > 0 { if acceptKey.length > 0 {
return true return 0
} }
} }
} }
return false return -1
} }
///read a 16 bit big endian value from a buffer ///read a 16 bit big endian value from a buffer
@ -687,8 +701,6 @@ public class WebSocket : NSObject, NSStreamDelegate {
} }
///used to write things to the stream ///used to write things to the stream
private func dequeueWrite(data: NSData, code: OpCode) { private func dequeueWrite(data: NSData, code: OpCode) {
guard isConnected else { return }
writeQueue.addOperationWithBlock { [weak self] in writeQueue.addOperationWithBlock { [weak self] in
//stream isn't ready, let's wait //stream isn't ready, let's wait
guard let s = self else { return } guard let s = self else { return }
@ -720,9 +732,6 @@ public class WebSocket : NSObject, NSStreamDelegate {
} }
var total = 0 var total = 0
while true { while true {
if !s.isConnected {
break
}
guard let outStream = s.outputStream else { break } guard let outStream = s.outputStream else { break }
let writeBuffer = UnsafePointer<UInt8>(frame!.bytes+total) let writeBuffer = UnsafePointer<UInt8>(frame!.bytes+total)
let len = outStream.write(writeBuffer, maxLength: offset-total) let len = outStream.write(writeBuffer, maxLength: offset-total)
@ -750,17 +759,16 @@ public class WebSocket : NSObject, NSStreamDelegate {
///used to preform the disconnect delegate ///used to preform the disconnect delegate
private func doDisconnect(error: NSError?) { private func doDisconnect(error: NSError?) {
guard !didDisconnect else { return } guard !didDisconnect else { return }
didDisconnect = true
connected = false
dispatch_async(queue) { [weak self] in dispatch_async(queue) { [weak self] in
guard let s = self else { return } guard let s = self else { return }
s.didDisconnect = true
s.onDisconnect?(error) s.onDisconnect?(error)
s.delegate?.websocketDidDisconnect(s, error: error) s.delegate?.websocketDidDisconnect(s, error: error)
} }
} }
} }
private class SSLCert { private class SSLCert {
var certData: NSData? var certData: NSData?
var key: SecKeyRef? var key: SecKeyRef?