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
```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
print("socket connected")
@ -26,7 +26,8 @@ socket.connect()
##Objective-C Example
```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) {
NSLog(@"socket connected");
@ -136,9 +137,9 @@ Run `seed install`.
##API
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
-------

View File

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

View File

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

View File

@ -16,7 +16,7 @@ class SocketSideEffectTest: XCTestCase {
override func setUp() {
super.setUp()
socket = SocketIOClient(socketURL: "")
socket = SocketIOClient(socketURL: NSURL())
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 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 waitingForPoll = 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 session: NSURLSession?
public private(set) var sid = ""
public private(set) var socketPath = "/engine.io"
public private(set) var urlPolling = ""
public private(set) var urlWebSocket = ""
public private(set) var socketPath = "/engine.io/"
public private(set) var urlPolling = NSURL()
public private(set) var urlWebSocket = NSURL()
public private(set) var websocket = false
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 ProbeWaitQueue = [Probe]
private let allowedCharacterSet = NSCharacterSet(charactersInString: "!*'();:@&=+$,/?%#[]\" {}").invertedSet
private let logType = "SocketEngine"
private let url: String
private let url: NSURL
private var connectParams: [String: AnyObject]?
private var pingInterval: Double?
private var pingTimeout = 0.0 {
didSet {
@ -76,13 +79,15 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb
private var secure = false
private var selfSigned = 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.url = url
for option in options {
switch option {
case let .ConnectParams(params):
connectParams = params
case let .SessionDelegate(delegate):
sessionDelegate = delegate
case let .ForcePolling(force):
@ -105,11 +110,26 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb
continue
}
}
super.init()
(urlPolling, urlWebSocket) = createURLs()
}
public convenience init(client: SocketEngineClient, url: String, options: NSDictionary?) {
self.init(client: client, url: url,
options: options?.toSocketOptionsSet() ?? [])
public convenience init(client: SocketEngineClient, url: NSURL, options: NSDictionary?) {
self.init(client: client, url: url, 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 {
@ -131,7 +151,7 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb
switch code {
case 0: // Unknown transport
didError(error)
case 1: // Unknown sid. clear and retry connect
case 1: // Unknown sid.
didError(error)
case 2: // Bad handshake request
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 {
return ("", "")
return (NSURL(), NSURL())
}
let socketURL = "\(url)\(socketPath)/?transport="
var urlPolling: String
var urlWebSocket: String
let urlPolling = NSURLComponents(string: url.absoluteString)!
let urlWebSocket = NSURLComponents(string: url.absoluteString)!
var queryString = ""
urlWebSocket.path = socketPath
urlPolling.path = socketPath
urlWebSocket.query = "transport=websocket"
urlPolling.query = "transport=polling&b64=1"
if secure {
urlPolling = "https://" + socketURL + "polling"
urlWebSocket = "wss://" + socketURL + "websocket"
urlPolling.scheme = "https"
urlWebSocket.scheme = "wss"
} else {
urlPolling = "http://" + socketURL + "polling"
urlWebSocket = "ws://" + socketURL + "websocket"
urlPolling.scheme = "http"
urlWebSocket.scheme = "ws"
}
if params != nil {
for (key, value) in params! {
let keyEsc = key.stringByAddingPercentEncodingWithAllowedCharacters(
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)"
}
if connectParams != nil {
for (key, value) in connectParams! {
queryString += "&\(key)=\(value)"
}
}
return (urlPolling, urlWebSocket)
urlWebSocket.query = urlWebSocket.query! + queryString
urlPolling.query = urlPolling.query! + queryString
return (urlPolling.URL!, urlWebSocket.URL!)
}
private func createWebsocketAndConnect() {
let wsUrl = urlWebSocket + (sid == "" ? "" : "&sid=\(sid)")
ws = WebSocket(url: NSURL(string: wsUrl)!)
let component = NSURLComponents(URL: urlWebSocket, resolvingAgainstBaseURL: false)!
component.query = component.query! + (sid == "" ? "" : "&sid=\(sid)")
ws = WebSocket(url: component.URL!)
if cookies != nil {
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 {
DefaultSocketLogger.Logger.error("Engine tried opening while connected. This is probably a programming error. "
+ "Abandoning open attempt", type: logType)
return
}
connectParams = opts
DefaultSocketLogger.Logger.log("Starting engine", type: logType)
DefaultSocketLogger.Logger.log("Handshaking", type: logType)
resetEngine()
(urlPolling, urlWebSocket) = createURLs(opts)
if forceWebsockets {
polling = false
websocket = true
@ -386,7 +398,7 @@ public final class SocketEngine: NSObject, SocketEnginePollable, SocketEngineWeb
return
}
let reqPolling = NSMutableURLRequest(URL: NSURL(string: urlPolling + "&b64=1")!)
let reqPolling = NSMutableURLRequest(URL: urlPolling)
if cookies != nil {
let headers = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies!)

View File

@ -71,7 +71,7 @@ extension SocketEnginePollable {
postWait.removeAll(keepCapacity: false)
let req = NSMutableURLRequest(URL: NSURL(string: urlPolling + "&sid=\(sid)")!)
let req = NSMutableURLRequest(URL: urlPollingWithSid)
addHeaders(req)
@ -93,7 +93,7 @@ extension SocketEnginePollable {
}
waitingForPoll = true
let req = NSMutableURLRequest(URL: NSURL(string: urlPolling + "&sid=\(sid)&b64=1")!)
let req = NSMutableURLRequest(URL: urlPollingWithSid)
addHeaders(req)
doLongPoll(req)
@ -112,7 +112,7 @@ extension SocketEnginePollable {
func doLongPoll(req: NSURLRequest) {
doRequest(req) {[weak self] data, res, err in
guard let this = self else {return}
guard let this = self else { return }
if err != nil || data == nil {
DefaultSocketLogger.Logger.error(err?.localizedDescription ?? "Error", type: "SocketEnginePolling")

View File

@ -29,6 +29,7 @@ import Foundation
weak var client: SocketEngineClient? { get set }
var closed: Bool { get }
var connected: Bool { get }
var connectParams: [String: AnyObject]? { get set }
var cookies: [NSHTTPCookie]? { get }
var extraHeaders: [String: String]? { get }
var fastUpgrade: Bool { get }
@ -42,23 +43,30 @@ import Foundation
var handleQueue: dispatch_queue_t! { get }
var sid: String { get }
var socketPath: String { get }
var urlPolling: String { get }
var urlWebSocket: String { get }
var urlPolling: NSURL { get }
var urlWebSocket: NSURL { get }
var websocket: Bool { get }
init(client: SocketEngineClient, url: String, options: NSDictionary?)
init(client: SocketEngineClient, url: NSURL, options: NSDictionary?)
func close(reason: String)
func didError(error: String)
func doFastUpgrade()
func flushWaitingForPostToWebSocket()
func open(opts: [String: AnyObject]?)
func open()
func parseEngineData(data: NSData)
func parseEngineMessage(message: String, fromPolling: Bool)
func write(msg: String, withType type: SocketEnginePacketType, withData data: [NSData])
}
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> {
if websocket {
var byteArray = [UInt8](count: 1, repeatedValue: 0x4)

View File

@ -25,12 +25,11 @@
import Foundation
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 status = SocketIOClientStatus.NotConnected
public var connectParams: [String: AnyObject]?
public var forceNew = false
public var nsp = "/"
public var options: Set<SocketIOClientOption>
@ -55,29 +54,20 @@ public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable
private(set) var reconnectAttempts = -1
var waitingData = [SocketPacket]()
/**
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
if socketURL.hasPrefix("https://") {
self.socketURL = socketURL
if socketURL.absoluteString.hasPrefix("https://") {
self.options.insertIgnore(.Secure(true))
}
var cleanedURL = socketURL["https?://"] <~ ""
if cleanedURL.hasSuffix("/") {
cleanedURL = String(cleanedURL.characters.dropLast())
}
self.socketURL = cleanedURL
for option in options {
switch option {
case let .ConnectParams(params):
connectParams = params
case let .Reconnects(reconnects):
self.reconnects = reconnects
case let .ReconnectAttempts(attempts):
@ -98,19 +88,32 @@ public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable
continue
}
}
self.options.insertIgnore(.Path("/socket.io"))
self.options.insertIgnore(.Path("/socket.io/"))
super.init()
}
/**
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?) {
self.init(socketURL: socketURL,
options: options?.toSocketOptionsSet() ?? [])
public convenience init(socketURL: NSURL, options: NSDictionary?) {
self.init(socketURL: socketURL, 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 {
@ -158,9 +161,9 @@ public final class SocketIOClient: NSObject, SocketEngineClient, SocketParsable
status = .Connecting
if engine == nil || forceNew {
addEngine().open(connectParams)
addEngine().open()
} else {
engine?.open(connectParams)
engine?.open()
}
guard timeoutAfter != 0 else { return }

View File

@ -15,6 +15,7 @@ import Foundation
infix operator <~ { associativity none precedence 130 }
private let lock = dispatch_semaphore_create(1)
private var swiftRegexCache = [String: NSRegularExpression]()
internal final class SwiftRegex: NSObject, BooleanType {
@ -22,6 +23,10 @@ internal final class SwiftRegex: NSObject, BooleanType {
var regex: NSRegularExpression
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
if let regex = swiftRegexCache[pattern] {
self.regex = regex
@ -36,6 +41,7 @@ internal final class SwiftRegex: NSObject, BooleanType {
self.regex = NSRegularExpression()
}
}
dispatch_semaphore_signal(lock)
super.init()
}