Merge pull request #289 from socketio/nsurl-based

Nsurl based
This commit is contained in:
Erik Little 2016-01-26 11:24:19 -05:00
commit d36ef10404
9 changed files with 119 additions and 89 deletions

View File

@ -5,7 +5,7 @@ Socket.IO-client for iOS/OS X.
##Example ##Example
```swift ```swift
let socket = SocketIOClient(socketURL: "localhost:8080", options: [.Log(true), .ForcePolling(true)]) let socket = SocketIOClient(socketURL: NSURL(string: "http://localhost:8080")!, options: [.Log(true), .ForcePolling(true)])
socket.on("connect") {data, ack in socket.on("connect") {data, ack in
print("socket connected") print("socket connected")
@ -26,7 +26,8 @@ socket.connect()
##Objective-C Example ##Objective-C Example
```objective-c ```objective-c
SocketIOClient* socket = [[SocketIOClient alloc] initWithSocketURL:@"localhost:8080" options:@{@"log": @YES, @"forcePolling": @YES}]; NSURL* url = [[NSURL alloc] initWithString:@"http://localhost:8080"];
SocketIOClient* socket = [[SocketIOClient alloc] initWithSocketURL:url options:@{@"log": @YES, @"forcePolling": @YES}];
[socket on:@"connect" callback:^(NSArray* data, SocketAckEmitter* ack) { [socket on:@"connect" callback:^(NSArray* data, SocketAckEmitter* ack) {
NSLog(@"socket connected"); NSLog(@"socket connected");
@ -136,9 +137,9 @@ Run `seed install`.
##API ##API
Constructors Constructors
----------- -----------
`init(var socketURL: String, options: Set<SocketIOClientOption> = [])` - Creates a new SocketIOClient. opts is a Set of SocketIOClientOption. If your socket.io server is secure, you need to specify `https` in your socketURL. `init(var socketURL: NSURL, options: Set<SocketIOClientOption> = [])` - Creates a new SocketIOClient. options is a Set of SocketIOClientOption. If your socket.io server is secure, you need to specify `https` in your socketURL.
`convenience init(socketURL: String, options: NSDictionary?)` - Same as above, but meant for Objective-C. See Options on how convert between SocketIOClientOptions and dictionary keys. `convenience init(socketURL: NSURL, options: NSDictionary?)` - Same as above, but meant for Objective-C. See Options on how convert between SocketIOClientOptions and dictionary keys.
Options Options
------- -------

View File

@ -15,8 +15,8 @@ class SocketEngineTest: XCTestCase {
override func setUp() { override func setUp() {
super.setUp() super.setUp()
client = SocketIOClient(socketURL: "") client = SocketIOClient(socketURL: NSURL(string: "http://localhost")!)
engine = SocketEngine(client: client, url: "", options: nil) engine = SocketEngine(client: client, url: NSURL(string: "http://localhost")!, options: nil)
client.setTestable() client.setTestable()
} }

View File

@ -10,7 +10,7 @@ import XCTest
@testable import SocketIOClientSwift @testable import SocketIOClientSwift
class SocketParserTest: XCTestCase { class SocketParserTest: XCTestCase {
let testSocket = SocketIOClient(socketURL: "") let testSocket = SocketIOClient(socketURL: NSURL())
//Format key: message; namespace-data-binary-id //Format key: message; namespace-data-binary-id
static let packetTypes: Dictionary<String, (String, [AnyObject], [NSData], Int)> = [ static let packetTypes: Dictionary<String, (String, [AnyObject], [NSData], Int)> = [

View File

@ -16,7 +16,7 @@ class SocketSideEffectTest: XCTestCase {
override func setUp() { override func setUp() {
super.setUp() super.setUp()
socket = SocketIOClient(socketURL: "") socket = SocketIOClient(socketURL: NSURL())
socket.setTestable() socket.setTestable()
} }

View File

@ -29,6 +29,11 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb
public let handleQueue = dispatch_queue_create("com.socketio.engineHandleQueue", DISPATCH_QUEUE_SERIAL) public let handleQueue = dispatch_queue_create("com.socketio.engineHandleQueue", DISPATCH_QUEUE_SERIAL)
public let parseQueue = dispatch_queue_create("com.socketio.engineParseQueue", DISPATCH_QUEUE_SERIAL) public let parseQueue = dispatch_queue_create("com.socketio.engineParseQueue", DISPATCH_QUEUE_SERIAL)
public var connectParams: [String: AnyObject]? {
didSet {
(urlPolling, urlWebSocket) = createURLs()
}
}
public var postWait = [String]() public var postWait = [String]()
public var waitingForPoll = false public var waitingForPoll = false
public var waitingForPost = false public var waitingForPost = false
@ -46,9 +51,9 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb
public private(set) var probing = false public private(set) var probing = false
public private(set) var session: NSURLSession? public private(set) var session: NSURLSession?
public private(set) var sid = "" public private(set) var sid = ""
public private(set) var socketPath = "/engine.io" public private(set) var socketPath = "/engine.io/"
public private(set) var urlPolling = "" public private(set) var urlPolling = NSURL()
public private(set) var urlWebSocket = "" public private(set) var urlWebSocket = NSURL()
public private(set) var websocket = false public private(set) var websocket = false
public private(set) var ws: WebSocket? public private(set) var ws: WebSocket?
@ -59,11 +64,9 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb
private typealias Probe = (msg: String, type: SocketEnginePacketType, data: [NSData]) private typealias Probe = (msg: String, type: SocketEnginePacketType, data: [NSData])
private typealias ProbeWaitQueue = [Probe] private typealias ProbeWaitQueue = [Probe]
private let allowedCharacterSet = NSCharacterSet(charactersInString: "!*'();:@&=+$,/?%#[]\" {}").invertedSet
private let logType = "SocketEngine" private let logType = "SocketEngine"
private let url: String private let url: NSURL
private var connectParams: [String: AnyObject]?
private var pingInterval: Double? private var pingInterval: Double?
private var pingTimeout = 0.0 { private var pingTimeout = 0.0 {
didSet { didSet {
@ -77,12 +80,14 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb
private var selfSigned = false private var selfSigned = false
private var voipEnabled = false private var voipEnabled = false
public init(client: SocketEngineClient, url: String, options: Set<SocketIOClientOption>) { public init(client: SocketEngineClient, url: NSURL, options: Set<SocketIOClientOption>) {
self.client = client self.client = client
self.url = url self.url = url
for option in options { for option in options {
switch option { switch option {
case let .ConnectParams(params):
connectParams = params
case let .SessionDelegate(delegate): case let .SessionDelegate(delegate):
sessionDelegate = delegate sessionDelegate = delegate
case let .ForcePolling(force): case let .ForcePolling(force):
@ -105,11 +110,26 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb
continue continue
} }
} }
super.init()
(urlPolling, urlWebSocket) = createURLs()
} }
public convenience init(client: SocketEngineClient, url: String, options: NSDictionary?) { public convenience init(client: SocketEngineClient, url: NSURL, options: NSDictionary?) {
self.init(client: client, url: url, self.init(client: client, url: url, options: options?.toSocketOptionsSet() ?? [])
options: options?.toSocketOptionsSet() ?? []) }
@available(*, deprecated=5.3)
public convenience init(client: SocketEngineClient, urlString: String, options: Set<SocketIOClientOption>) {
guard let url = NSURL(string: urlString) else { fatalError("Incorrect url") }
self.init(client: client, url: url, options: options)
}
@available(*, deprecated=5.3)
public convenience init(client: SocketEngineClient, urlString: String, options: NSDictionary?) {
guard let url = NSURL(string: urlString) else { fatalError("Incorrect url") }
self.init(client: client, url: url, options: options?.toSocketOptionsSet() ?? [])
} }
deinit { deinit {
@ -131,7 +151,7 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb
switch code { switch code {
case 0: // Unknown transport case 0: // Unknown transport
didError(error) didError(error)
case 1: // Unknown sid. clear and retry connect case 1: // Unknown sid.
didError(error) didError(error)
case 2: // Bad handshake request case 2: // Bad handshake request
didError(error) didError(error)
@ -188,49 +208,45 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb
} }
} }
private func createURLs(params: [String: AnyObject]?) -> (String, String) { private func createURLs() -> (NSURL, NSURL) {
if client == nil { if client == nil {
return ("", "") return (NSURL(), NSURL())
} }
let socketURL = "\(url)\(socketPath)/?transport=" let urlPolling = NSURLComponents(string: url.absoluteString)!
var urlPolling: String let urlWebSocket = NSURLComponents(string: url.absoluteString)!
var urlWebSocket: String var queryString = ""
urlWebSocket.path = socketPath
urlPolling.path = socketPath
urlWebSocket.query = "transport=websocket"
urlPolling.query = "transport=polling&b64=1"
if secure { if secure {
urlPolling = "https://" + socketURL + "polling" urlPolling.scheme = "https"
urlWebSocket = "wss://" + socketURL + "websocket" urlWebSocket.scheme = "wss"
} else { } else {
urlPolling = "http://" + socketURL + "polling" urlPolling.scheme = "http"
urlWebSocket = "ws://" + socketURL + "websocket" urlWebSocket.scheme = "ws"
} }
if params != nil { if connectParams != nil {
for (key, value) in params! { for (key, value) in connectParams! {
let keyEsc = key.stringByAddingPercentEncodingWithAllowedCharacters( queryString += "&\(key)=\(value)"
allowedCharacterSet)!
urlPolling += "&\(keyEsc)="
urlWebSocket += "&\(keyEsc)="
if value is String {
let valueEsc = (value as! String).stringByAddingPercentEncodingWithAllowedCharacters(
allowedCharacterSet)!
urlPolling += "\(valueEsc)"
urlWebSocket += "\(valueEsc)"
} else {
urlPolling += "\(value)"
urlWebSocket += "\(value)"
}
} }
} }
return (urlPolling, urlWebSocket) urlWebSocket.query = urlWebSocket.query! + queryString
urlPolling.query = urlPolling.query! + queryString
return (urlPolling.URL!, urlWebSocket.URL!)
} }
private func createWebsocketAndConnect() { private func createWebsocketAndConnect() {
let wsUrl = urlWebSocket + (sid == "" ? "" : "&sid=\(sid)") let component = NSURLComponents(URL: urlWebSocket, resolvingAgainstBaseURL: false)!
component.query = component.query! + (sid == "" ? "" : "&sid=\(sid)")
ws = WebSocket(url: NSURL(string: wsUrl)!) ws = WebSocket(url: component.URL!)
if cookies != nil { if cookies != nil {
let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!)
@ -363,22 +379,18 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb
} }
} }
public func open(opts: [String: AnyObject]?) { public func open() {
if connected { if connected {
DefaultSocketLogger.Logger.error("Engine tried opening while connected. This is probably a programming error. " DefaultSocketLogger.Logger.error("Engine tried opening while connected. This is probably a programming error. "
+ "Abandoning open attempt", type: logType) + "Abandoning open attempt", type: logType)
return return
} }
connectParams = opts
DefaultSocketLogger.Logger.log("Starting engine", type: logType) DefaultSocketLogger.Logger.log("Starting engine", type: logType)
DefaultSocketLogger.Logger.log("Handshaking", type: logType) DefaultSocketLogger.Logger.log("Handshaking", type: logType)
resetEngine() resetEngine()
(urlPolling, urlWebSocket) = createURLs(opts)
if forceWebsockets { if forceWebsockets {
polling = false polling = false
websocket = true websocket = true
@ -386,7 +398,7 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb
return return
} }
let reqPolling = NSMutableURLRequest(URL: NSURL(string: urlPolling + "&b64=1")!) let reqPolling = NSMutableURLRequest(URL: urlPolling)
if cookies != nil { if cookies != nil {
let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!) let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!)

View File

@ -71,7 +71,7 @@ extension SocketEnginePollable {
postWait.removeAll(keepCapacity: false) postWait.removeAll(keepCapacity: false)
let req = NSMutableURLRequest(URL: NSURL(string: urlPolling + "&sid=\(sid)")!) let req = NSMutableURLRequest(URL: urlPollingWithSid)
addHeaders(req) addHeaders(req)
@ -93,7 +93,7 @@ extension SocketEnginePollable {
} }
waitingForPoll = true waitingForPoll = true
let req = NSMutableURLRequest(URL: NSURL(string: urlPolling + "&sid=\(sid)&b64=1")!) let req = NSMutableURLRequest(URL: urlPollingWithSid)
addHeaders(req) addHeaders(req)
doLongPoll(req) doLongPoll(req)

View File

@ -29,6 +29,7 @@ import Foundation
weak var client: SocketEngineClient? { get set } weak var client: SocketEngineClient? { get set }
var closed: Bool { get } var closed: Bool { get }
var connected: Bool { get } var connected: Bool { get }
var connectParams: [String: AnyObject]? { get set }
var cookies: [NSHTTPCookie]? { get } var cookies: [NSHTTPCookie]? { get }
var extraHeaders: [String: String]? { get } var extraHeaders: [String: String]? { get }
var fastUpgrade: Bool { get } var fastUpgrade: Bool { get }
@ -42,23 +43,30 @@ import Foundation
var handleQueue: dispatch_queue_t! { get } var handleQueue: dispatch_queue_t! { get }
var sid: String { get } var sid: String { get }
var socketPath: String { get } var socketPath: String { get }
var urlPolling: String { get } var urlPolling: NSURL { get }
var urlWebSocket: String { get } var urlWebSocket: NSURL { get }
var websocket: Bool { get } var websocket: Bool { get }
init(client: SocketEngineClient, url: String, options: NSDictionary?) init(client: SocketEngineClient, url: NSURL, options: NSDictionary?)
func close(reason: String) func close(reason: String)
func didError(error: String) func didError(error: String)
func doFastUpgrade() func doFastUpgrade()
func flushWaitingForPostToWebSocket() func flushWaitingForPostToWebSocket()
func open(opts: [String: AnyObject]?) func open()
func parseEngineData(data: NSData) func parseEngineData(data: NSData)
func parseEngineMessage(message: String, fromPolling: Bool) func parseEngineMessage(message: String, fromPolling: Bool)
func write(msg: String, withType type: SocketEnginePacketType, withData data: [NSData]) func write(msg: String, withType type: SocketEnginePacketType, withData data: [NSData])
} }
extension SocketEngineSpec { extension SocketEngineSpec {
var urlPollingWithSid: NSURL {
let com = NSURLComponents(URL: urlPolling, resolvingAgainstBaseURL: false)!
com.query = com.query! + "&sid=\(sid)"
return com.URL!
}
func createBinaryDataForSend(data: NSData) -> Either<NSData, String> { func createBinaryDataForSend(data: NSData) -> Either<NSData, String> {
if websocket { if websocket {
var byteArray = [UInt8](count: 1, repeatedValue: 0x4) var byteArray = [UInt8](count: 1, repeatedValue: 0x4)

View File

@ -25,12 +25,11 @@
import Foundation import Foundation
public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable { public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable {
public let socketURL: String public let socketURL: NSURL
public private(set) var engine: SocketEngineSpec? public private(set) var engine: SocketEngineSpec?
public private(set) var status = SocketIOClientStatus.NotConnected public private(set) var status = SocketIOClientStatus.NotConnected
public var connectParams: [String: AnyObject]?
public var forceNew = false public var forceNew = false
public var nsp = "/" public var nsp = "/"
public var options: Set<SocketIOClientOption> public var options: Set<SocketIOClientOption>
@ -59,25 +58,16 @@ public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable
/** /**
Type safe way to create a new SocketIOClient. opts can be omitted Type safe way to create a new SocketIOClient. opts can be omitted
*/ */
public init(socketURL: String, options: Set<SocketIOClientOption> = []) { public init(socketURL: NSURL, options: Set<SocketIOClientOption> = []) {
self.options = options self.options = options
self.socketURL = socketURL
if socketURL.hasPrefix("https://") { if socketURL.absoluteString.hasPrefix("https://") {
self.options.insertIgnore(.Secure(true)) self.options.insertIgnore(.Secure(true))
} }
var cleanedURL = socketURL["https?://"] <~ ""
if cleanedURL.hasSuffix("/") {
cleanedURL = String(cleanedURL.characters.dropLast())
}
self.socketURL = cleanedURL
for option in options { for option in options {
switch option { switch option {
case let .ConnectParams(params):
connectParams = params
case let .Reconnects(reconnects): case let .Reconnects(reconnects):
self.reconnects = reconnects self.reconnects = reconnects
case let .ReconnectAttempts(attempts): case let .ReconnectAttempts(attempts):
@ -99,18 +89,31 @@ public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable
} }
} }
self.options.insertIgnore(.Path("/socket.io")) self.options.insertIgnore(.Path("/socket.io/"))
super.init() super.init()
} }
/** /**
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, options: Set<SocketIOClientOption>)` If using Swift it's recommended to use `init(socketURL: NSURL, options: Set<SocketIOClientOption>)`
*/ */
public convenience init(socketURL: String, options: NSDictionary?) { public convenience init(socketURL: NSURL, options: NSDictionary?) {
self.init(socketURL: socketURL, self.init(socketURL: socketURL, options: options?.toSocketOptionsSet() ?? [])
options: options?.toSocketOptionsSet() ?? []) }
/// Please use the NSURL based init
@available(*, deprecated=5.3)
public convenience init(socketURLString: String, options: Set<SocketIOClientOption> = []) {
guard let url = NSURL(string: socketURLString) else { fatalError("Incorrect url") }
self.init(socketURL: url, options: options)
}
/// Please use the NSURL based init
@available(*, deprecated=5.3)
public convenience init(socketURLString: String, options: NSDictionary?) {
guard let url = NSURL(string: socketURLString) else { fatalError("Incorrect url") }
self.init(socketURL: url, options: options?.toSocketOptionsSet() ?? [])
} }
deinit { deinit {
@ -158,9 +161,9 @@ public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable
status = .Connecting status = .Connecting
if engine == nil || forceNew { if engine == nil || forceNew {
addEngine().open(connectParams) addEngine().open()
} else { } else {
engine?.open(connectParams) engine?.open()
} }
guard timeoutAfter != 0 else { return } guard timeoutAfter != 0 else { return }

View File

@ -15,6 +15,7 @@ import Foundation
infix operator <~ { associativity none precedence 130 } infix operator <~ { associativity none precedence 130 }
private let lock = dispatch_semaphore_create(1)
private var swiftRegexCache = [String: NSRegularExpression]() private var swiftRegexCache = [String: NSRegularExpression]()
internal final class SwiftRegex: NSObject, BooleanType { internal final class SwiftRegex: NSObject, BooleanType {
@ -22,6 +23,10 @@ internal final class SwiftRegex: NSObject, BooleanType {
var regex: NSRegularExpression var regex: NSRegularExpression
init(target:String, pattern:String, options:NSRegularExpressionOptions?) { init(target:String, pattern:String, options:NSRegularExpressionOptions?) {
if dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, Int64(10 * NSEC_PER_MSEC))) != 0 {
fatalError("This should never happen")
}
self.target = target self.target = target
if let regex = swiftRegexCache[pattern] { if let regex = swiftRegexCache[pattern] {
self.regex = regex self.regex = regex
@ -36,6 +41,7 @@ internal final class SwiftRegex: NSObject, BooleanType {
self.regex = NSRegularExpression() self.regex = NSRegularExpression()
} }
} }
dispatch_semaphore_signal(lock)
super.init() super.init()
} }