iossocketswebsocketnsstreamnsinputstream

How to connect to wss socket where host name has additional path using NSStream?


I'm trying to connect to wss socket, and the host name looks like this: "myhostname.com/ws/v2". Here is how I start the connection:

let host = "myhostname.com/ws/v2"

CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host as CFString, 443, &readStream, &writeStream)

inputStream = readStream!.takeRetainedValue()
outputStream = writeStream!.takeRetainedValue()

outputStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL, forKey: Stream.PropertyKey.socketSecurityLevelKey)
inputStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL, forKey: Stream.PropertyKey.socketSecurityLevelKey)

inputStream.schedule(in: .current, forMode: .commonModes)
outputStream.schedule(in: .current, forMode: .commonModes)

inputStream.delegate = self
outputStream.delegate = self

inputStream.open()
outputStream.open()

This fails with an error: The operation couldn’t be completed. (kCFErrorDomainCFNetwork error 1.)

However, if I remove the path from the host name, so it looks like this: myhostname.com then in my delegate I get an event openCompleted. However, it doesn't respond to my messages after that, I assume it's because I'm connected to a wrong socket, since I removed the path.

What is the proper way of connecting to a socket when the host name has an additional path?


Solution

  • myhostname.com/ws/v2 is not a hostname. It is an (incomplete) URL (the complete URL is wss://myhostname.com/ws/v2). The hostname is just myhostname.com, and the Websocket path on that host is just /ws/v2.

    The WebSockets handshake uses HTTP/S, so it is not enough to just connect to the host with an NSStream. You have to connect a TCP socket to the host and port, then negotiate an SSL/TLS handshake if using WSS, then use HTTP to request the path asking for an Upgrade to WebSocket, and only if a successful HTTP 101 reply is returned then perform the WebSocket handshake.

    That is a lot of work to do manually. You really should be using an actual WebSocket client library instead. There are plenty available.