Does anyone have experience opening HTTP stream on iOS? I have tried multiple solutions without any luck (examples bellow).
For better context, here’s example of endpoint that will stream values (as ndjson) upon opening connection:
GET /v2/path/{id}
Accept: application/x-ndjson
Attempt #1:
Issue: The completion handler is never called
let keyID = try keyAdapter.getKeyID(for: .signHash)
let url = baseURL.appendingPathComponent("/v2/path/(keyID)")
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "GET"
urlRequest.setValue("application/x-ndjson", forHTTPHeaderField: "Accept")
session.dataTask(with: urlRequest) { data, response, error in
// This never gets called.
// I would expect that the completion is called every time backend emits new value.
}.resume()
Attempt #2:
Issue: Debugger displays this message: Connection 0: encountered error(12:1)
private var stream: URLSessionStreamTask? = nil
func startStream() {
let keyID = try keyAdapter.getKeyID(for: .signHash)
let url = baseURL.appendingPathComponent("/v2/path/(keyID)")
let stream = session.streamTask(withHostName: url, port: 443)
// Not sure how to set headers.
// Header needs to be set so backend knows client wants to connect a stream.
self.stream = stream
stream.startSecureConnection()
startRead(stream: stream)
}
private func startRead(stream: URLSessionStreamTask) {
stream.readData(ofMinLength: 1, maxLength: 4096, timeout: 120.0) { data, endOfFile, error in
if let error = error {
Logger.shared.log(level: .error, "Reading data from stream failed with error: (error.localizedDescription)")
} else if let data = data {
Logger.shared.log(level: .error, "Received data from stream ((data.count)B)")
if !endOfFile {
self.startRead(stream: stream)
} else {
Logger.shared.log(level: .info, "End of file")
}
} else {
Logger.shared.log(level: .error, "Reading stream endup in unspecified state (both data and error are nil).")
}
}
}
Does anyone have experience with this? How can I keep HTTP connection open and listen to a new values that backend is streaming?
2
Answers
iOS can connect to HTTP stream using now deprecated API
URLConnection
. The API was deprecated in iOS 9, however it's still available for use (and will be in iOS 16 - tested).First you need to create
URLRequest
and setup theNSURLConnection
:Notice that the argument for delegate in the code above is of type
Any
which doesn't help to figure out what protocol(s) to implement. There are two -NSURLConnectionDelegate
andNSURLConnectionDataDelegate
.Let's receive data:
Then implement a method for catching errors:
And if you have custom SSL pinning, then:
There is not much info on the internet, so hopefully it will save someone days of trial and error.
I was looking for the same solution today. At first, I tried to use
session.streamTask
, but I didn’t know how to use it. It’s a low-level task for TCP, but what I wanted was an HTTP-level solution. I also didn’t want to useURLConnection
, which has been deprecated.After some research, I finally figured it out: In the documentation for
URLSessionDataDelegate
,https://developer.apple.com/documentation/foundation/urlsessiondatadelegate
The key is to not set a completion handler block in
dataTask()
, and implement the 2 delegate methods ofURLSessionDataDelegate
:Another key is to set the delegate to the
URLSessionDataTask
, notURLSession
. The problem with Larme’s code is that he set the delegate toURLSession
, so the functionurlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive: Data)
will not be called.The full code demonstration: