2010-11-19 13 views
3

Ich versuche zu implementieren, was am besten als "eine FTP-Schnittstelle zu einer HTTP-API" beschrieben werden kann. Im Wesentlichen gibt es eine vorhandene REST-API, die zum Verwalten der Dateien eines Benutzers für eine Site verwendet werden kann, und ich erstelle einen Mediator-Server, der diese API als FTP-Server wieder verfügbar macht. So können Sie sich mit sagen, sagen, Filezilla und listet Ihre Dateien, neue hochladen, alte, etc.Twisted, FTP und "Streaming" große Dateien

Ich versuche dies mit twisted.protocols.ftp für den (FTP) -Server und twisted.web.client für den (HTTP) -Client .

Die Sache, gegen die ich stehe, ist, wenn ein Benutzer versucht, eine Datei herunterzuladen, diese Datei von einer HTTP-Antwort zu meiner FTP-Antwort "streaming". Ähnlich zum Hochladen.

Der einfachste Weg wäre, die gesamte Datei vom HTTP-Server herunterzuladen, dann umzudrehen und den Inhalt an den Benutzer zu senden. Das Problem dabei ist, dass jede Datei viele Gigabyte groß sein kann (denke Laufwerkbilder, ISO-Dateien usw.). Bei dieser Vorgehensweise würde der Inhalt der Datei jedoch zwischen dem Zeitpunkt, an dem ich sie von der API herunterlade, und dem Zeitpunkt, zu dem ich sie an den Benutzer sende, im Speicher gehalten - nicht gut.

Also meine Lösung ist es zu versuchen, "streamen" - wie ich Stücke von Daten aus der API-HTTP-Antwort bekomme, möchte ich nur umdrehen und diese Stücke an den FTP-Benutzer senden. Scheint einfach. Für meine "benutzerdefinierte FTP-Funktionalität" verwende ich eine Unterklasse von ftp.FTPShell. Die Lesemethode davon, openForReading, gibt ein Deferred zurück, das mit einer Implementierung von IReadFile ausgelöst wird.

Unten ist meine (erste, einfache) Implementierung für "Streaming HTTP". Ich verwende die fetch Funktion, um eine HTTP-Anfrage einzurichten, und der Callback, den ich übergebe, wird mit jedem Chunk aufgerufen, den ich von der Antwort bekomme.

Ich dachte, ich könnte eine Art zwei Ende Pufferobjekt verwenden, um die Chunks zwischen HTTP und FTP zu transportieren, indem Sie das Pufferobjekt als dateiähnliches Objekt von ftp._FileReader benötigt, aber das erweist sich schnell als nicht funktionierend, wie der Verbraucher von der send Aufruf fast sofort den Puffer schließt (weil es eine leere Zeichenfolge zurückgibt, weil es noch keine Daten zum Lesen usw.). Daher sende ich leere Dateien, bevor ich überhaupt die HTTP-Antwort-Chunks empfange.

Bin ich nah, aber etwas fehlt? Bin ich überhaupt auf dem falschen Weg? Ist das, was ich tun möchte wirklich unmöglich (ich bezweifle das stark)?

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) 

Als Randbemerkung, ist dies mit Verdrehte meinem zweiten Tag nur - ich die meisten gestern verbrachte durch Twisted Introduction Dave Peticolas lesen, die ein guter Ausgangspunkt war, wenn auch auf einer älteren Version von Twisted-Basis .

Das sagte, ich kann Dinge falsch machen.

Antwort

1

Ich dachte, ich irgendeine Art von beidseitigem Puffer-Objekt verwende, könnte die Stücke zwischen den HTTP und FTP, zu transportieren, unter Verwendung des Puffer Objekt als Datei-ähnliche Objekt erforderlich durch ftp._FileReader, aber das ist schnell beweisen nicht funktionieren, da der Konsument vom Sendeaufruf den Puffer fast sofort schließt (weil er eine leere Zeichenfolge zurückgibt, weil noch keine Daten zum Lesen vorhanden sind usw.). Daher sende ich leere Dateien, bevor ich überhaupt die HTTP-Antwort-Chunks empfange.

Statt ftp._FileReader verwenden, möchten Sie etwas, das eine Schreib tun wird, wenn ein Stück von Ihrem HTTPStreamer zu einem Rückruf kommt es liefert. Sie brauchen/wollen nie einen Puffer in HTTP lesen, weil es keinen Grund gibt, einen solchen Puffer zu haben. Sobald HTTP-Bytes ankommen, schreiben Sie sie an den Verbraucher. So etwas wie ...

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 

Sie sollten auch Verdrehte der Erzeuger/Verbraucher-Schnittstelle verwenden, die Übertragung zu ermöglichen gedrosselt werden, die erforderlich sind, wenn die Verbindung zum HTTP-Server ist schneller als Ihr Benutzer-FTP-Verbindung Sie.

+0

Sie hatten Recht, ich musste IPushProducer implementieren. Es funktioniert jetzt ziemlich gut, obwohl ich noch keine Sicherheitsmaßnahmen für das von Ihnen erwähnte Szenario "Fast HTTP" habe. Vielen Dank! – eternicode