代码之家  ›  专栏  ›  技术社区  ›  eternicode

Twisted、FTP和“流”大文件

  •  3
  • eternicode  · 技术社区  · 15 年前

    我正在尝试实现可以最好地描述为“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 ,返回与 IReadFile .

    下面是我对“流式HTTP”的(初始的,简单的)实现。我用的是 fetch 函数设置一个HTTP请求,我传入的回调将与从响应中获取的每个块一起调用。

    我想我可以使用某种双端缓冲区对象在HTTP和FTP之间传输数据块,方法是将缓冲区对象用作 ftp._FileReader ,但很快证明这不起作用,因为 send call几乎立即关闭缓冲区(因为它返回一个空字符串,因为还没有要读取的数据,等等)。因此,我甚至在开始接收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 Introduction ,这是一个很好的起点,即使基于旧版本的twisted。

    也就是说,我 可以 做错事。

    1 回复  |  直到 15 年前
        1
  •  2
  •   Jean-Paul Calderone    15 年前

    我想我可以使用某种双端缓冲区对象在HTTP和FTP之间传输数据块,方法是将缓冲区对象用作FTP所需的类似文件的对象。FileReader,但很快证明这不起作用,因为send调用的使用者几乎立即关闭缓冲区(因为它返回一个空字符串,因为还没有要读取的数据等)。因此,我甚至在开始接收HTTP响应块之前“发送”空文件。

    您不需要使用ftp.\u文件读取器,而是需要在块从您的 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
    

    如果到HTTP服务器的连接比用户到您的FTP连接快,则可能需要使用Twisted的生产者/消费者接口来限制传输。