My app maintains a WebSocket connection with a remote server, and I would like to notify the user that their connection has dropped when the phone loses network connectivity. I'm simulating a dropped network connection in my test environment by placing my phone in Airplane mode and deactivating WiFi. None of URLSessionWebSocketTask's delegates or callbacks pass back an error, and I only get the following log message:
I'm using a URLSessionWebSocketDelegate and the urlSession(session:, webSocketTask:, didCloseWith:) does not get triggered, neither does webSocketTask.receive(completionHandler:)Connection 1: encountered error(1:53)
I believe the log messages comes from CFNetwork since I can find the log within the Console app and the log references CFNetwork.
I found that webSocketTask.sendPing(pongReceiveHandler:) does eventually discover that connectivity is lost, but after a really long time. As far as I know, sendPing should probably run in a background task that runs every 30 seconds or so, and the error isn't discovered until about another 30+ seconds when the ping request times out. So, it could take 1+ minute(s) before the user is notified of the issue. I don't really like this approach since it requires extra code, extra network requests, and a very delayed response.
So, do errors within CFNetwork propagate to URLSessionWebSocketTask in any way? If not, I would really like if errors within CFNetwork propagated to webSocketTask.receive(completionHandler:) as a .warning URLSessionWebSocketTask.Message along with an error code. Ultimately, I would like to handle connection errors when they are encountered.
I noticed in another forum post that Apple changed iOS WebSockets in a beta release, so I tried to update my software without any luck. I updating macOS to 10.15.6 Beta (19G60d), iOS to 13.6 (17G5059c), and XCode to Version 12.0 beta 2 (12A6163b). However, XCode couldn't connect to my phone, and webSocketTask.send(message:) had intermittent string encoding issues.
System Details:
macOS 10.15.5 (1 June 2020 update)
iOS 13.5.1 (1 June 2020 update)
XCode Version 11.5 (11E608c)
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.