URLSession download looping indefinitely until it times out

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?

Answered by Frameworks Engineer in 839999022

Having both Content-Length: 0 and Transfer-Encoding: chunked is invalid. Please remove the Content-Length header field from the response.

Accepted Answer

Having both Content-Length: 0 and Transfer-Encoding: chunked is invalid. Please remove the Content-Length header field from the response.

URLSession download looping indefinitely until it times out
 
 
Q