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

标签 ios swift network-programming nsurlsession chunked

我正在尝试连接到 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()

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

如果有人代码发布了如何为来自流端点的 chunked 响应打开持久连接的示例,那将非常有帮助。另外,我正在寻找不涉及第三方库的解决方案。类似于 https://stackoverflow.com/a/9473787/5897233 的响应但用等效的 URLSession 更新将是理想的。

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

最佳答案

从 Apple 的“爱斯基摩人”Quinn 处获得了大量信息。

唉,你在这里的错误一端。 URLSessionStreamTask 用于处理裸 TCP(或 TLS over TCP)连接,顶部没有 HTTP 框架。您可以将其视为 BSD 套接字 API 的高级等价物。

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

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

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

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

  1. 使用 uploadTask(withStreamedRequest:)

    创建您的任务
  2. 实现 urlSession(_:task:needNewBodyStream:) 委托(delegate)方法以返回输入流,读取时返回请求正文

  3. 利润!

我附上了一些测试代码,展示了这一点。在这种情况下,它使用一对绑定(bind)流,将输入流传递给请求(按照上面的第 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)
}
}

希望这对您有所帮助。

关于ios - 如何将 URLSessionStreamTask 与 URLSession 一起用于分块编码传输,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44602192/

相关文章:

ios - 重力属性在 SpriteKit 场景中不起作用

ios - 在 iOS 8.3 上使用 CoreSpotlight (9.0+) 启动应用程序时 Xcode 崩溃

c++ - 两个应用程序如何监听一个端口?

ios - 在iTunes Connect上提交隐私政策

iphone - Xcode 调试器(适用于 iPhone)中的 "out of scope"是什么意思?

ios - 如何将 Magicalvoxel 文件导入 Xcode 并使用它

ios - 10 以下的小时/分钟加 0

networking - 有没有办法检测 Wifi 网络上的事件连接数?

Java:在不使用 Web 服务的情况下以编程方式获取一个人的外部 IP 地址

ios - 通过代码定位 Storyboard View