I ended up switched to Apple's swift-nio - https://github.com/apple/swift-nio for websocket functionality. More specifically, I decided to use Vapor's websocket-kit - https://github.com/vapor/websocket-kit, which is a light wrapper over swift-nio.
Network.framework's websocket implementation just doesn't seem to be ready for production use. I try very hard to use native libraries whenever I can, but URLSessionWebSocketTask was giving me too much trouble. I managed with the quirks of URLSessionWebSocketTask until I encountered an issue that made my application unusable: my server immediately sends information to the client when the client's connection upgrades to a websocket connection, and URLSessionWebSocketTask's input stream would intermittently close. So, either I was using the API incorrectly, or there is some sort of race condition within URLSessionWebSocketTask; either way, I couldn't afford to spend the time to figure out what was wrong. When I switched to swift-nio, all of my websocket woes disappeared.
Here are a few issues that I encountered:
URLSessionWebSocketTask logs ominous error messages even with a .normalClosure. I never figured out if I was using the API incorrectly, or if these error messages were expected.
URLSessionWebSocketTask.receive appears to require recursive calls. This seems to open up the possibilities for race conditions, stack overflows, and overall just unnecessary general confusion. Could the API change to a publisher?
Having to pass a URLSessionWebSocketDelegate into a URLSession means that you can have a strong reference to "self" if your websocket wrapper class implements this protocol. A strong reference means that you open up the possibility for a memory leak since the class isn't disposed when dereferenced. I avoided a strong reference with an inner class object, but I would prefer if the API avoided this issue altogether.
There should be an easier way to discover connection issues. For instance Vapor's websocket-kit simply lets you set a variable (i.e. "ws.pingInterval = TimeAmount.seconds(10)") and the library will automatically close the connection if it doesn't receive a pong within an interval after sending a ping.
As mentioned above, URLSessionWebSocketTask seems to close the input stream if the server sends a message too soon after a connection is upgraded to a websocket connection. What makes things more confusing is that the websocket's output stream still appears to be connected to the server and the server can receive messages, but the server can no longer send messages to the websocket client.
It would be nice if the library differentiated between the initial GET connection, and then when the connection is upgraded to a websocket connection.
Post
Replies
Boosts
Views
Activity
In case anyone on Apple's Network.framework team is listening: below is the WebSocket API that I use in my application to wrap WebSocket implementations. My API doesn't handle every scenario that every application might encounter and it's tailored for my application, but I would really appreciate if Network.framework produced a similar WebSocket API.
protocol WebSocketConnection: MessageClient {
func connect()
func disconnect()
var status: AnyPublisher<ConnectionStatus, Never> { get }
var message: AnyPublisher<String, Never> { get }
}
protocol MessageClient {
func send(text: String)
}
enum ConnectionStatus {
case idle
case connected
case upgraded(MessageClient)
case disconnected(DisconnectReason)
case reconnect(Date)
}
enum DisconnectReason {
case error(Error)
case unknown(Int)
case unauthorized
case not_found
case normal
}