如何将 URLSessionStreamTask 与 URLSession 一起用于分块编码传输

How to use URLSessionStreamTask with URLSession for chunked-encoding transfer

我正在尝试连接到 Twitter 流 API 端点。看起来 URLSession 支持通过 URLSessionStreamTask 进行流式传输,但我不知道如何使用 API。我也没能找到任何示例代码。

我尝试测试以下内容,但没有记录网络流量:

let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
let stream = session.streamTask(withHostName: "https://stream.twitter.com/1.1/statuses/sample.json", port: 22)
stream.startSecureConnection()
stream.readData(ofMinLength: 0, maxLength: 100000, timeout: 60, completionHandler: { (data, bool, error) in
   print("bool = \(bool)")
   print("error = \(String(describing: error))")
})
stream.resume()

我还实现了委托方法(包括 URLSessionStreamDelegate),但它们没有被调用。

如果有人编写了 post 如何为来自流式端点的 chunked 响应打开持久连接的示例,那将非常有帮助。另外,我正在寻找不涉及第三方库的解决方案。类似于 但使用 URLSession 等效更新的响应将是理想的。

注意:上面的示例代码中省略了授权信息。

在 Apple 收到了 Quinn "The Eskimo" 提供的大量信息。

哎呀,你搞错了。 URLSessionStreamTask 用于处理裸 TCP(或 TLS over TCP)连接,顶部没有 HTTP 框架。您可以将其视为 BSD 套接字的高级等价物 API.

分块传输编码是 HTTP 的一部分,因此所有其他 URLSession 任务类型(数据任务、上传任务、下载任务)都支持。您无需执行任何特殊操作即可启用此功能。分块传输编码是 HTTP 1.1 标准的强制性部分,因此始终启用。

但是,您可以选择如何接收 returned 数据。如果您使用 URLSession 便利 APIs(dataTask(with:completionHandler:) 等),URLSession 将缓冲所有传入数据,然后将其以一个大的 Data 值传递给您的完成处理程序。这在很多情况下都很方便,但不适用于流式资源。在这种情况下,您需要使用基于 URLSession 委托的 APIs(dataTask(with:) 等),它将在数据块到达时调用 urlSession(_:dataTask:didReceive:) 会话委托方法。

至于我正在测试的特定端点,发现了以下内容: 如果客户端向服务器发送流请求,服务器似乎只启用其流响应(分块传输编码)。这有点奇怪,HTTP 规范绝对不需要。

幸运的是,可以强制 URLSession 发送流请求:

  1. 使用 uploadTask(withStreamedRequest:)

  2. 创建您的任务
  3. 对 return 一个输入流实施 urlSession(_:task:needNewBodyStream:) 委托方法,该输入流在读取时 return 请求主体

  4. 盈利!

我附上了一些测试代码,展示了这一点。在这种情况下,它使用一对绑定流,将输入流传递给请求(按照上面的第 2 步)并保留输出流。

如果您想将数据作为请求正文的一部分实际发送,您可以通过写入输出流来实现。

class NetworkManager : NSObject, URLSessionDataDelegate {

static var shared = NetworkManager()

private var session: URLSession! = nil

override init() {
    super.init()
    let config = URLSessionConfiguration.default
    config.requestCachePolicy = .reloadIgnoringLocalCacheData
    self.session = URLSession(configuration: config, delegate: self, delegateQueue: .main)
}

private var streamingTask: URLSessionDataTask? = nil

var isStreaming: Bool { return self.streamingTask != nil }

func startStreaming() {
    precondition( !self.isStreaming )

    let url = URL(string: "ENTER STREAMING URL HERE")!
    let request = URLRequest(url: url)
    let task = self.session.uploadTask(withStreamedRequest: request)
    self.streamingTask = task
    task.resume()
}

func stopStreaming() {
    guard let task = self.streamingTask else {
        return
    }
    self.streamingTask = nil
    task.cancel()
    self.closeStream()
}

var outputStream: OutputStream? = nil

private func closeStream() {
    if let stream = self.outputStream {
        stream.close()
        self.outputStream = nil
    }
}

func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) {
    self.closeStream()

    var inStream: InputStream? = nil
    var outStream: OutputStream? = nil
    Stream.getBoundStreams(withBufferSize: 4096, inputStream: &inStream, outputStream: &outStream)
    self.outputStream = outStream

    completionHandler(inStream)
}

func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
    NSLog("task data: %@", data as NSData)
}

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    if let error = error as NSError? {
        NSLog("task error: %@ / %d", error.domain, error.code)
    } else {
        NSLog("task complete")
    }
}
}

并且您可以从任何地方调用网络代码,例如:

class MainViewController : UITableViewController {

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    if NetworkManager.shared.isStreaming {  
       NetworkManager.shared.stopStreaming() 
    } else {
       NetworkManager.shared.startStreaming() 
    }
    self.tableView.deselectRow(at: indexPath, animated: true)
}
}

希望这对您有所帮助。