收到了 Apple 的 Quinn “The Eskimo” 提供的大量信息。
唉,你在这里搞错了。 URLSessionStreamTask 用于处理裸 TCP(或 TLS over TCP)连接,顶部没有 HTTP 框架。您可以将其视为高级别的 BSD 套接字 API。
分块传输编码是 HTTP 的一部分,因此所有其他 URLSession 任务类型(数据任务、上传任务、下载任务)都支持。你不需要做任何特别的事情来启用它。分块传输编码是 HTTP 1.1 标准的强制性部分,因此始终处于启用状态。
但是,您可以选择如何接收返回的数据。如果您使用 URLSession 便利 API(dataTask(with:completionHandler:) 等),URLSession 将缓冲所有传入的数据,然后将其以一个大的 Data 值传递给您的完成处理程序。这在许多情况下很方便,但不适用于流式资源。在这种情况下,您需要使用基于 URLSession 委托的 API(dataTask(with:) 等),它会在数据块到达时调用 urlSession(_:dataTask:didReceive:) 会话委托方法。
至于我正在测试的特定端点,发现了以下内容:
如果客户端向其发送流式请求,服务器似乎仅启用其流式响应(分块传输编码)。这有点奇怪,而且 HTTP 规范绝对不需要。
幸运的是,可以强制 URLSession 发送流式请求:
使用uploadTask(withStreamedRequest:)创建您的任务
实现 urlSession(_:task:needNewBodyStream:) 委托方法以返回输入流,读取时返回请求正文
利润!
我附上了一些测试代码来展示这一点。在这种情况下,它使用一对绑定的流,将输入流传递给请求(按照上面的步骤 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)
}
}
希望这会有所帮助。