APIs to set up TCP connection that starts with HTTP exchange

We have a protocol between our client and our server which
  1. Creates a TLS connection

  2. Does an HTTP get/response on the connection, where the HTTP headers are specifying characteristics of the session

  3. Sends and receives arbitrary amounts of streaming data on the connection

Some characteristics of the connection code are
  • None of the data after the initial get/response has any HTTP framing at all.

  • Data has to be using the same TCP connection as the initial HTTP exchange

  • Common code for iOS/macOS.

  • Usable from a Network Extension

The original code uses a whole bunch of CF*, including CFHttpMessage, and BSD sockets for the raw connection. It was from quite a while back. For backwards compatibility reasons we couldn't switch to the Network framework or to the NSURLConnection/Session APIs.

It looks like we have a chance to update our networking, but from my initial reading it looks like the NSURLSessionStreamTask doesn't handle the initial HTTP exchange we need, and the NSURLSession variants that *do* do an initial HTTP exchange aren't really streaming data without framing later.

Q1: Are there APIs that can handle the initial HTTP exchange without assuming they control the whole connection? I.e., handle the HTTP parsing & logic, but over a connection that we've set up separately?

Q2: Is there a way to make the upload/download tasks act as a raw data stream?

Q3: Any other ideas about how to address this particular problem?

WebSockets, or any other solution that requires a server-side change is a non-starter for us...

Using the Network framework as a BSD sockets replacement gets us part way there, but we're still relying on CF functions for some HTTP functionality.

Replies

You may be able to make this work by starting an NSURLSessionDataTask and then converting it to a stream task in your -URLSession:dataTask:didReceiveResponse:completionHandler: delegate callback.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
Thanks Quinn. That looks like what I needed. I'll check it out.



There appear to be some problems with that. Is there any sample code that demonstrates the transition from URLSessionDataTask to URLSessionStreamTask with reads & writes afterwards?

What I'm seeing is that in
Code Block
    func urlSession(_ session: URLSession,
                    dataTask: URLSessionDataTask,
                    didBecome streamTask: URLSessionStreamTask)

everything looks fine.
  • Stream task is suspended initially.

  • I call resume() on the stream task. That seems to be Ok.

But, when I try to call

Code Block
func readData(ofMinLength minBytes: Int,
maxLength maxBytes: Int,
timeout: TimeInterval,
completionHandler: @escaping (Data?, Bool, Error?) -> Void)

on that task I immediately see a call to

Code Block
func urlSession(_ session: URLSession, readClosedFor streamTask: URLSessionStreamTask)

In that delegate call I see that the task is now in the
Code Block
NSURLSessionTaskStateRunning
state, with nothing pending, but according to the docs it looks like that call means no more reads can be done.

Similarly when I call:
Code Block
func write(_ data: Data,
timeout: TimeInterval,
completionHandler: @escaping (Error?) -> Void)

it never returns, and no data is written. Looking at network traces on the peer, everything looks normal--it just doesn't see any more traffic.


Is there any sample code that demonstrates the transition from
URLSessionDataTask to URLSessionStreamTask with reads & writes
afterwards?

Not that I’ve seen.

If you get completely stuck then my advice is that you open a DTS tech support incident and Matt or I can help you out in that context.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"