2017-06-17 289 views
1

我正在尝试连接到Twitter流式API端点。它看起来像URLSession支持通过URLSessionStreamTask流,但我不知道如何使用该API。我还没有找到任何示例代码。如何使用带URLSession的URLSessionStreamTask进行分块编码传输

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

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

你有没有解决这个问题?一直在寻找自己的小时。文档非常稀少。 – Ryan

+0

@Ryan刚刚发布了一个答案! –

回答

2

收到大量的信息提供奎恩“爱斯基摩人”在苹果。

唉,你在这里坚持错误的结局。 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发送流请求:

  1. uploadTask(withStreamedRequest:)

  2. 创建任务落实urlSession(_:task:needNewBodyStream:)委托方法返回一个输入流,当阅读,退货请求正文

  3. 利润!

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

希望这有助于。