我正在尝试连接到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
),但是它们没有被调用。
如果某个代码发布了一个如何从流端点打开chunked
响应的持久连接的示例,那将非常有用。此外,我正在寻求不涉及第三方库的解决方案。类似于https://stackoverflow.com/a/9473787/5897233但使用URLSession等效更新的响应将是理想的。
注意:上面的示例代码中省略了授权信息。
答案 0 :(得分:10)
收到许多信息由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)
}
}
希望这有帮助。