Twisted, FTP и потоковые большие файлы - PullRequest
3 голосов
/ 19 ноября 2010

Я пытаюсь реализовать то, что лучше всего можно описать как «интерфейс FTP к HTTP API».По сути, существует существующий API REST, который можно использовать для управления файлами пользователя для сайта, и я создаю сервер-посредник, который повторно представляет этот API в качестве FTP-сервера.Таким образом, вы можете войти, скажем, с помощью Filezilla и перечислить ваши файлы, загрузить новые, удалить старые и т. Д.

Я пытаюсь сделать это с помощью twisted.protocols.ftp для сервера (FTP) и twisted.web.clientдля (HTTP) клиента.

Я сталкиваюсь с тем, что когда пользователь пытается загрузить файл, «потоковая передача» этого файла из HTTP-ответа на мой FTP-ответ.Аналогично для загрузки.

Самый простой способ - загрузить весь файл с HTTP-сервера, а затем развернуться и отправить содержимое пользователю.Проблема заключается в том, что любой данный файл может иметь размер в несколько гигабайт (например, образы дисков, файлы ISO и т. Д.).Однако при таком подходе содержимое файла будет храниться в памяти между моментом, когда я загружаю его из API, и временем, когда я отправляю его пользователю, - не очень хорошо.

Поэтому мое решение состоит в том, чтобы попробоватьчтобы «передать» его - так как я получаю куски данных из HTTP-ответа API, я просто хочу развернуться и отправить эти куски пользователю FTP. Кажется простым.

Для моих «пользовательских функций FTP» я использую подкласс ftp.FTPShell.Метод чтения этого, openForReading, возвращает Deferred, который запускается с реализацией IReadFile.

Ниже приведена моя (начальная, простая) реализация для «потокового HTTP».Я использую функцию fetch для настройки HTTP-запроса, и обратный вызов, который я передаю, вызывается с каждым чанком, полученным из ответа.

Я думал, что мог бы использовать какой-то объект буфера с двумя концами длятранспортировать порции между HTTP и FTP, используя буферный объект в качестве файлового объекта, требуемого ftp._FileReader, но это быстро оказывается неэффективным, так как потребитель из вызова send почтинемедленно закрывает буфер (потому что он возвращает пустую строку, потому что еще нет данных для чтения и т. д.).Таким образом, я «отправляю» пустые файлы еще до того, как начинаю получать фрагменты ответа HTTP.

Я близок, но что-то упустил?Я вообще не на том пути?Действительно ли то, что я хочу сделать действительно невозможно (я очень сомневаюсь в этом)?

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)

В качестве примечания, это всего лишь мой второй день с Twisted - я провел большую часть вчерашнего чтениячерез Twisted Введение Дейва Петиколаса , которое стало отличной отправной точкой, даже если оно основано на более старой версии Twisted.

Тем не менее, я может делатьвсе не так.

1 Ответ

1 голос
/ 19 ноября 2010

Я подумал, что мог бы использовать некоторый вид буферного объекта с двумя концами для передачи кусков между HTTP и FTP, используя буферный объект в качестве файлового объекта, требуемого для ftp._FileReader, но это быстро доказываетне работать, так как потребитель от вызова send почти сразу закрывает буфер (потому что он возвращает пустую строку, потому что еще нет данных для чтения и т. д.).Таким образом, я «отправляю» пустые файлы еще до того, как начинаю получать фрагменты HTTP-ответа.

Вместо использования ftp._FileReader вам нужно что-то, что будет выполнять запись всякий раз, когда от вашего чанка поступает чанкHTTPStreamer на обратный звонок, который он поставляет.Вам никогда не нужно / не нужно выполнять чтение из буфера по HTTP, потому что нет причины даже иметь такой буфер.Как только поступят байты HTTP, напишите их потребителю.Что-то вроде ...

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

Вы также можете использовать интерфейс производителя / потребителя Twisted, чтобы разрешить регулирование передачи, что может потребоваться, если ваше соединение с HTTP-сервером быстрее, чем FTP вашего пользователя.связь с вами.

...