Hi,
I’m trying to download a remote file in the background, but I keep getting a strange behaviour where URLSession download my file indefinitely during a few minutes, without calling urlSession(_:downloadTask:didFinishDownloadingTo:)
until the download eventually times out.
To find out that it’s looping, I’ve observed the total bytes written on disk by implementing urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)
.
Note that I can't know the size of the file. The server is not able to calculate the size.
Below is my implementation.
I create an instance of URLSession like this:
private lazy var session: URLSession = {
let configuration = URLSessionConfiguration.background(withIdentifier: backgroundIdentifier)
configuration.isDiscretionary = false
configuration.sessionSendsLaunchEvents = true
return URLSession(configuration: configuration,
delegate: self,
delegateQueue: nil)
}()
My service is using async/await so I have implemented an AsyncThrowingStream :
private var downloadTask: URLSessionDownloadTask?
private var continuation: AsyncThrowingStream<(URL, URLResponse), Error>.Continuation?
private var stream: AsyncThrowingStream<(URL, URLResponse), Error> {
AsyncThrowingStream<(URL, URLResponse), Error> { continuation in
self.continuation = continuation
self.continuation?.onTermination = { @Sendable [weak self] data in
self?.downloadTask?.cancel()
}
downloadTask?.resume()
}
}
Then to start the download, I do :
private func download(with request: URLRequest) async throws -> (URL, URLResponse) {
do {
downloadTask = session.downloadTask(with: request)
for try await (url, response) in stream {
return (url, response)
}
throw NetworkingError.couldNotBuildRequest
} catch {
throw error
}
}
Then in the delegate :
public func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL) {
guard let response = downloadTask.response,
downloadTask.error == nil,
(response as? HTTPURLResponse)?.statusCode == 200 else {
continuation?.finish(throwing: downloadTask.error)
return
}
do {
let documentsURL = try FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false)
let savedURL = documentsURL.appendingPathComponent(location.lastPathComponent)
try FileManager.default.moveItem(at: location, to: savedURL)
continuation?.yield((savedURL, response))
continuation?.finish()
} catch {
continuation?.finish(throwing: error)
}
}
I also tried to replace let configuration = URLSessionConfiguration.background(withIdentifier: backgroundIdentifier)
by let configuration = URLSessionConfiguration.default
and this time I get a different error at the end of the download:
Task <0457F755-9C52-4CFB-BDB2-F378D0C94912>.<1> failed strict content length check - expected: 0, received: 530692, received (uncompressed): 0
Task <0457F755-9C52-4CFB-BDB2-F378D0C94912>.<1> finished with error [-1005] Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSLocalizedDescription=The network connection was lost., NSErrorFailingURLStringKey=https:/<host>:8190/proxy?Func=downloadVideoByUrl&SessionId=slufzwrMadvyJad8Lkmi9RUNAeqeq, NSErrorFailingURLKey=https://<host>:8190/proxy?Func=downloadVideoByUrl&SessionId=slufzwrMadvyJad8Lkmi9RUNAeqeq, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDownloadTask <0457F755-9C52-4CFB-BDB2-F378D0C94912>.<1>"
), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDownloadTask <0457F755-9C52-4CFB-BDB2-F378D0C94912>.<1>, NSUnderlyingError=0x300d9a7c0 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={NSErrorPeerAddressKey=<CFData 0x302139db0 [0x1fcb1f598]>{length = 16, capacity = 16, bytes = 0x10021ffe91e227500000000000000000}}}}
The log "failed strict content length check” made me look into the response header, which has the following:
content-length: 0
Content-Type: application/force-download
Transfer-encoding: chunked
Connection: KEEP-ALIVE
Content-Transfer-Encoding: binary
So it should be fine the way I setup my URLSession.
The download works fine in Chrome/Safari/Chrome or Postman. My code used to work a couple of weeks before, so I expect something has changed on the server side, but I can’t find what, and I don’t get much help from the guys on the server side.
Has anyone an idea of what’s going on?