How to build a server that can accept both HTTP and WebSocket connections using NWListener ?

So I'd like to listen on port 80 for instance and depending on the request, upgrade the connection to WebSocket or leave it as HTTP.

I could have two different servers on two different ports, but that is not what I want as they need to share the same port.

Thank you !

Daniel Tapie

Accepted Reply

Could it be that the problem is that the nw_connection is only partially upgraded and
does not properly format the incoming and outgoing packets ?

From nw_connection_t's perspective using NWProtocolWebSocket is a pretty drastic change from just reading TCP data at maximum length. This is usually where issues can be introduced. For example, if you have a plain TCP connection and then you need to tell that TCP connection to read web sockets frames instead of chunks of TCP data this can cause issues.

One could imagine a custom framer that could be used to handle both scenarios and then just alter it's behavior based upon how the frame headers tell the connection to process the data being sent to the receiver. This would again take quite a bit of interaction with the application layer to handle the manual upgrade, and this then goes back to my point about having to build the application layer from scratch on top of the connection.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com

Replies

There are two barriers that you will immediately have to solve for here; first, is adding the WebSocket frame protocol to an existing TCP connection after it has been started. For example:

Code Block swift
let webSocketOptions = NWProtocolWebSocket.Options()
NWParameters.defaultProtocolStack.applicationProtocols.insert(webSocketOptions, at: 0)


The next is building an HTTP stack on top of a TCP connection. NWConnection / NWListener are low level network APIs that typically talk TCP or UDP at the transport layer. If you wanted to talk HTTP on top of TCP, you would essentially be in charge of building your own HTTP stack on top of NWConnection. Knowning that, your HTTP stack will need to process the Upgrade headers for WebSocket connections, to make HTTP/TCP connections talk WebSockets; essentially adding the frame protocol I included above while simultaneously not restarting the connection. This is a difficult process. For more on this see RFC 7230, 6.7 - Connection Upgrade. If you run completely out of options here, I know SwiftNIO has functionality that supports this.


Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
Thank you Matt for your detailed answer ! I tried the upgrade negotiation approach (we used to do that with GCDAsyncSocket anyway), but I was stopped by the fact that I did not know how to make my NWConnection "restart" using the new added WS protocol in its stack. I could add it, but then nothing would happen...

The problem we are having is that, under 10.15, GCDAsyncSocket seems to randomly crash after a few days (I love it when it takes days to reproduce a problem) with dispatch queue overcommit exception. So far we have not been able to determine what causes these crashes, so we wanted to take advantage of the new Network framework which, apart from that problem, works great.
OK, so making progress. I have been able to implement the negotiation to upgrade the connection and I also added the ws protocol to the stack.

It seems to work ok except that every packet I send on the connection has to be formatted as a websocket packet manually (header and all) and then the client is happy and the connection is kept open. It also looks like the ping/pong works.

Could it be that the problem is that the nw_connection is only partially upgraded and does not properly format the incoming and outgoing packets ?

Could it be that the problem is that the nw_connection is only partially upgraded and
does not properly format the incoming and outgoing packets ?

From nw_connection_t's perspective using NWProtocolWebSocket is a pretty drastic change from just reading TCP data at maximum length. This is usually where issues can be introduced. For example, if you have a plain TCP connection and then you need to tell that TCP connection to read web sockets frames instead of chunks of TCP data this can cause issues.

One could imagine a custom framer that could be used to handle both scenarios and then just alter it's behavior based upon how the frame headers tell the connection to process the data being sent to the receiver. This would again take quite a bit of interaction with the application layer to handle the manual upgrade, and this then goes back to my point about having to build the application layer from scratch on top of the connection.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
Makes total sense. Thank you for the clarification Matt !

Best regards,

Daniel