2017-06-17 6 views
1

J'essaie de me connecter au point de terminaison de l'API Twitter en streaming. Il semble que URLSession supporte le streaming via URLSessionStreamTask, mais je n'arrive pas à comprendre comment utiliser l'API. Je n'ai pas pu trouver de code d'exemple non plus.Comment utiliser URLSessionStreamTask avec URLSession pour le transfert par codage en bloc

J'ai essayé de tester ce qui suit, mais il n'y a pas de trafic réseau enregistré:

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

J'ai aussi mis en œuvre les méthodes de délégués (y compris URLSessionStreamDelegate), mais ils ne sont pas appelés.

Il serait vraiment utile si quelqu'un code un exemple de comment ouvrir une connexion persistante pour chunked réponses à partir d'un point de terminaison de diffusion. Aussi, je cherche des solutions qui n'impliquent pas de bibliothèques tierces. Une réponse similaire à https://stackoverflow.com/a/9473787/5897233 mais mise à jour avec l'équivalent URLSession serait idéale.

Remarque: L'information d'autorisation a été omise de l'exemple de code ci-dessus.

+0

Avez-vous déjà résolu cela? J'étais à la recherche d'heures moi-même. La documentation est vraiment clairsemée. – Ryan

+0

@Ryan vient de poster une réponse maintenant! –

Répondre

2

Reçu beaucoup d'informations gracieuseté de Quinn "The Eskimo" chez Apple. Hélas, vous avez ici la mauvaise extrémité de la crosse. URLSessionStreamTask permet de résoudre une connexion TCP (ou TLS sur TCP) sans le cadre HTTP. Vous pouvez le considérer comme un équivalent de haut niveau à l'API BSD Sockets.

Le codage de transfert en bloc fait partie de HTTP et est donc pris en charge par tous les autres types de tâche URLSession (tâche de données, tâche de téléchargement, tâche de téléchargement). Vous n'avez pas besoin de faire quelque chose de spécial pour activer cela. Le codage de transfert en bloc est une partie obligatoire de la norme HTTP 1.1 et est donc toujours activé.

Vous avez cependant une option quant à la façon dont vous recevez les données renvoyées. Si vous utilisez les API de commodité URLSession (dataTask(with:completionHandler:) et ainsi de suite), URLSession mettra en mémoire tampon toutes les données entrantes et les transmettra ensuite à votre gestionnaire de complétion dans une grande valeur Data. C'est pratique dans de nombreuses situations, mais cela ne fonctionne pas bien avec une ressource en streaming. Dans ce cas, vous devez utiliser les API basées sur les délégués URLSession (dataTask(with:) et ainsi de suite), qui appellera la méthode déléguée de session urlSession(_:dataTask:didReceive:) avec des blocs de données à leur arrivée. En ce qui concerne l'extrémité spécifique que je testais, les éléments suivants ont été découverts: Il semble que le serveur active uniquement sa réponse de diffusion en continu (l'encodage de transfert en bloc) si le client lui envoie une requête de diffusion en continu. C'est un peu bizarre, et certainement pas requis par la spécification HTTP.

Heureusement, il est possible de forcer URLSession d'envoyer une demande de diffusion en continu:

  1. Créer votre tâche avec uploadTask(withStreamedRequest:)

  2. Mettre en œuvre la méthode de délégué urlSession(_:task:needNewBodyStream:) pour retourner un flux d'entrée qui, à la lecture, renvoie le corps de la demande

  3. Profit!

J'ai joint un code de test qui montre cela en action.Dans ce cas, il utilise une paire de flux liés, en passant le flux d'entrée à la requête (par l'étape 2 ci-dessus) et en conservant le flux de sortie.

Si vous souhaitez réellement envoyer des données dans le corps de la demande, vous pouvez le faire en écrivant dans le flux de sortie.

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

Et vous pouvez appeler le code de réseau de partout tels que:

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

Hope this helps.