【发布时间】:2010-11-19 05:14:57
【问题描述】:
我正在尝试实现最恰当地描述为“HTTP API 的 FTP 接口”。本质上,有一个现有的 REST API 可用于管理站点的用户文件,我正在构建一个中介服务器,将该 API 重新公开为 FTP 服务器。因此,您可以使用 Filezilla 登录并列出您的文件、上传新文件、删除旧文件等。
我正在尝试将 twisted.protocols.ftp 用于 (FTP) 服务器,twisted.web.client 用于 (HTTP) 客户端。
我遇到的问题是,当用户尝试下载文件时,将该文件从 HTTP 响应“流式传输”到我的 FTP 响应。上传类似。
最直接的方法是从 HTTP 服务器下载整个文件,然后转身将内容发送给用户。这样做的问题是任何给定的文件都可能有很多 GB(想想驱动器映像、ISO 文件等)。但是,使用这种方法,文件的内容会在我从 API 下载它到我将它发送给用户的时间之间保存在内存中 - 不好。
所以我的解决方案是尝试“流式传输”它——当我从 API 的 HTTP 响应中获取数据块时,我只想转身将这些数据块发送给 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 的第二天——我昨天大部分时间都在阅读 Dave Peticolas 的Twisted Introduction,这是一个很好的起点,即使是基于旧版本的 twisted。
也就是说,我可能做错了。
【问题讨论】: