Я пытаюсь реализовать то, что лучше всего можно описать как «интерфейс 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.
Тем не менее, я может делатьвсе не так.