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

查看:209
本文介绍了如何将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.

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

推荐答案

苹果公司的奎因(Quinn)爱斯基摩人"(The Eskimo)收到了很多信息.

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

A,您在这里遇到了错误的情况. URLSessionStreamTask用于处理裸TCP(或TCP上的TLS)连接,而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天全站免登陆