2010-11-19 6 views
3

Je tente d'implémenter ce qui peut être décrit comme "une interface FTP à une API HTTP". Essentiellement, il existe une API REST existante qui peut être utilisée pour gérer les fichiers d'un utilisateur pour un site, et je construis un serveur de médiateur qui ré-expose cette API en tant que serveur FTP. Vous pouvez donc vous connecter avec, disons, Filezilla et lister vos fichiers, en télécharger de nouveaux, en supprimer d'anciens, etc.Twisted, FTP, et de "gros" fichiers en streaming

Je tente ceci avec twisted.protocols.ftp pour le serveur (FTP), et twisted.web.client pour le client (HTTP) . La chose contre laquelle je me heurte est, quand un utilisateur essaie de télécharger un fichier, "en streaming" ce fichier d'une réponse HTTP à ma réponse FTP. Similaire pour le téléchargement. L'approche la plus simple serait de télécharger le fichier entier à partir du serveur HTTP, puis de faire demi-tour et d'envoyer le contenu à l'utilisateur. Le problème avec ceci est que n'importe quel fichier donné pourrait avoir plusieurs gigaoctets (pensez à des images de lecteur, des fichiers ISO, etc.). Avec cette approche, cependant, le contenu du fichier serait conservé en mémoire entre le moment où je le télécharge de l'API et le moment où je l'envoie à l'utilisateur - pas bon. Donc, ma solution est d'essayer de le "streamer" - comme je reçois des morceaux de données de la réponse HTTP de l'API, je veux juste faire demi-tour et envoyer ces morceaux à l'utilisateur FTP. Semble simple.

Pour ma "fonctionnalité FTP personnalisée", j'utilise une sous-classe de ftp.FTPShell. La méthode de lecture de ceci, openForReading, renvoie un différé qui se déclenche avec une implémentation de IReadFile. Ci-dessous est ma mise en œuvre (initiale, simple) pour "streaming HTTP". J'utilise la fonction fetch pour configurer une requête HTTP, et le rappel que je passe est appelé avec chaque tronçon de la réponse. Je pensais pouvoir utiliser une sorte d'objet tampon à deux extrémités pour transporter les blocs entre le HTTP et le FTP, en utilisant l'objet tampon comme l'objet de type fichier requis par ftp._FileReader, mais cela ne fonctionne pas rapidement, comme le consommateur de l'appel send ferme presque immédiatement le tampon (parce qu'il retourne une chaîne vide, parce qu'il n'y a pas encore de données à lire, etc). Ainsi, je "envoie" des fichiers vides avant même que je commence à recevoir les morceaux de réponse HTTP. Est-ce que je suis proche, mais que je manque quelque chose? Suis-je sur le mauvais chemin tout à fait? Est ce que je veux faire vraiment impossible (je doute fortement que)?

from twisted.web import client 
import urlparse 

class HTTPStreamer(client.HTTPPageGetter): 
    def __init__(self): 
     self.callbacks = [] 

    def addHandleResponsePartCallback(self, callback): 
     self.callbacks.append(callback) 

    def handleResponsePart(self, data): 
     for cb in self.callbacks: 
      cb(data) 
     client.HTTPPageGetter.handleResponsePart(self, data) 

class HTTPStreamerFactory(client.HTTPClientFactory): 
    protocol = HTTPStreamer 

    def __init__(self, *args, **kwargs): 
     client.HTTPClientFactory.__init__(self, *args, **kwargs) 
     self.callbacks = [] 

    def addChunkCallback(self, callback): 
     self.callbacks.append(callback) 

    def buildProtocol(self, addr): 
     p = client.HTTPClientFactory.buildProtocol(self, addr) 
     for cb in self.callbacks: 
      p.addHandleResponsePartCallback(cb) 
     return p 

def fetch(url, callback): 

    parsed = urlparse.urlsplit(url) 

    f = HTTPStreamerFactory(parsed.path) 
    f.addChunkCallback(callback) 

    from twisted.internet import reactor 
    reactor.connectTCP(parsed.hostname, parsed.port or 80, f) 

Comme une note de côté, ce n'est que mon deuxième jour avec Twisted - j'ai passé la plupart d'entre hier la lecture par Dave Peticolas' Twisted Introduction, qui a été un excellent point de départ, même si elle repose sur une version plus ancienne de torsadée . Cela dit, je peut faire des choses fausses.

Répondre

1

Je pensais que je pouvais utiliser une sorte d'objet tampon à deux extrémités pour transporter les morceaux entre HTTP et FTP, en utilisant l'objet tampon comme l'objet de type fichier requis par ftp._FileReader, mais c'est la preuve rapidement ne pas fonctionner, car le consommateur de l'appel d'envoi ferme presque immédiatement le tampon (parce qu'il renvoie une chaîne vide, parce qu'il n'y a pas encore de données à lire, etc.). Ainsi, je "envoie" des fichiers vides avant même que je commence à recevoir les morceaux de réponse HTTP.

Au lieu d'utiliser ftp._FileReader, vous voulez quelque chose qui va faire une écriture chaque fois qu'un morceau arrive de votre HTTPStreamer à un rappel qu'il fournit. Vous n'avez jamais besoin de lire un tampon sur le HTTP, car il n'y a aucune raison d'avoir un tel tampon. Dès que les octets HTTP arrivent, écrivez-les au consommateur. Quelque chose comme ...

class FTPStreamer(object): 
    implements(IReadFile) 

    def __init__(self, url): 
     self.url = url 

    def send(self, consumer): 
     fetch(url, consumer.write) 
     # You also need a Deferred to return here, so the 
     # FTP implementation knows when you're done. 
     return someDeferred 

Vous pouvez également utiliser l'interface producteur/consommateur de Twisted pour permettre le transfert à être étranglé, comme cela peut être nécessaire si votre connexion au serveur HTTP est plus rapide que la connexion FTP de votre utilisateur toi.

+0

Vous aviez raison, j'avais besoin d'implémenter 'IPushProducer'. Cela fonctionne plutôt bien maintenant, même si je n'ai pas encore de sauvegarde pour le scénario "HTTP rapide" que vous avez mentionné. Merci! – eternicode