如何使用 URLSessionStreamTask 和 URLSession 进行分块编码传输 [英] How to use URLSessionStreamTask with URLSession for chunked-encoding transfer

查看:28
本文介绍了如何使用 URLSessionStreamTask 和 URLSession 进行分块编码传输的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to connect to the Twitter streaming API endpoint. It looks like URLSession supports streaming via URLSessionStreamTask, however I can't figure out how to use the API. I have not been able to find any sample code either.

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

I tried testing the following, but there is no network traffic recorded:

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),但它们没有被调用.

I've also implemented the delegate methods (including URLSessionStreamDelegate), but they do not get called.

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

It would be really helpful if someone code post a sample of how to open a persistent connection for chunked responses from a streaming endpoint. Also, I am seeking solutions which don't involve third party libraries. A response similar to https://stackoverflow.com/a/9473787/5897233 but updated with the URLSession equivalent would be ideal.

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

推荐答案

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

Received lots of info courtesy of Quinn "The Eskimo" at Apple.

唉,你在这里搞错了.URLSessionStreamTask 用于处理裸 TCP(或 TLS over TCP)连接,顶部没有 HTTP 框架.您可以将其视为高级别的 BSD 套接字 API.

Alas, you have the wrong end of the stick here. URLSessionStreamTask is for wrangling a naked TCP (or TLS over TCP) connection, without the HTTP framing on top. You can think of it as a high-level equivalent to the BSD Sockets API.

分块传输编码是 HTTP 的一部分,因此所有其他 URLSession 任务类型(数据任务、上传任务、下载任务)都支持.你不需要做任何特别的事情来启用它.分块传输编码是 HTTP 1.1 标准的强制性部分,因此始终处于启用状态.

The chunked transfer encoding is part of HTTP, and is thus supported by all of the other URLSession task types (data task, upload task, download task). You don’t need to do anything special to enable this. The chunked transfer encoding is a mandatory part of the HTTP 1.1 standard, and is thus is always enabled.

但是,您可以选择如何接收返回的数据.如果您使用 URLSession 便利 API(dataTask(with:completionHandler:) 等),URLSession 将缓冲所有传入的数据,然后将其传递给一个大型 Data值.这在许多情况下很方便,但不适用于流式资源.在这种情况下,您需要使用基于 URLSession 委托的 API(dataTask(with:) 等),它将调用 urlSession(_:dataTask:didReceive:) 带有数据块的会话委托方法.

You do, however, have an option as to how you receive the returned data. If you use the URLSession convenience APIs (dataTask(with:completionHandler:) and so on), URLSession will buffer all the incoming data and then pass it to your completion handler in one large Data value. That’s convenient in many situations but it doesn’t work well with a streamed resource. In that case you need to use the URLSession delegate-based APIs (dataTask(with:) and so on), which will call the urlSession(_:dataTask:didReceive:) session delegate method with chunks of data as they arrive.

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

As for the specific endpoint I was testing, the following was uncovered: It seems that the server only enables its streaming response (the chunked transfer encoding) if the client sends it a streaming request. That’s kinda weird, and definitely not required by the HTTP spec.

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

Fortunately, it is possible to force URLSession to send a streaming request:

  1. 使用 uploadTask(withStreamedRequest:)

实现 urlSession(_:task:needNewBodyStream:) 委托方法以返回输入流,读取时返回请求正文

Implement the urlSession(_:task:needNewBodyStream:) delegate method to return an input stream that, when read, returns the request body

利润!

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

I’ve attached some test code that shows this in action. In this case it uses a bound pair of streams, passing the input stream to the request (per step 2 above) and holding on to the output stream.

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

If you want to actually send data as part of the request body you can do so by writing to the output stream.

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")
    }
}
}

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

And you can call the networking code from anywhere such as:

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)
}
}

希望这会有所帮助.

这篇关于如何使用 URLSessionStreamTask 和 URLSession 进行分块编码传输的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆