创建并流式传输大型存档,而不将其存储在内存或磁盘上

Nic*_*ack 14 python http

我想允许用户一次下载多个大文件的存档.但是,文件和存档可能太大而无法存储在我的服务器的内存或磁盘上(它们可以动态地从其他服务器流入).当我将其传输给用户时,我想生成存档.

我可以使用Tar或Zip或其他最简单的东西.我正在使用django,它允许我在我的响应中返回一个生成器或类文件对象.该对象可用于泵送过程.但是,我无法弄清楚如何围绕zipfile或tarfile库构建这种东西,我担心它们可能不支持读取文件,或者在构建时读取存档.

将迭代器转换为类文件对象的答案可能有所帮助. tarfile#addfile采用迭代,但它似乎立即传递给shutil.copyfileobj,所以这可能不像我希望的生成器友好.

Nic*_*ack 8

我最终使用了SpiderOak ZipStream.


Ped*_*eck 7

你可以通过生成和流式传输没有压缩的zip文件来实现,这基本上只是在每个文件的内容之前添加标题.你是对的,图书馆不支持这个,但是你可以破解它们以使它工作.

此代码将zipfile.ZipFile与管理流的类包装在一起,并为文件的zipfile.ZipInfo创建实例.CRC和大小可以在最后设置.您可以使用put_file(),write()和flush()将输入流中的数据推入其中,并使用read()将数据读取到输出流中.

import struct      
import zipfile
import time

from StringIO import StringIO

class ZipStreamer(object):
    def __init__(self):
        self.out_stream = StringIO()

        # write to the stringIO with no compression
        self.zipfile = zipfile.ZipFile(self.out_stream, 'w', zipfile.ZIP_STORED)

        self.current_file = None

        self._last_streamed = 0

    def put_file(self, name, date_time=None):
        if date_time is None:
            date_time = time.localtime(time.time())[:6]

        zinfo = zipfile.ZipInfo(name, date_time)
        zinfo.compress_type = zipfile.ZIP_STORED
        zinfo.flag_bits = 0x08
        zinfo.external_attr = 0600 << 16
        zinfo.header_offset = self.out_stream.pos

        # write right values later
        zinfo.CRC = 0
        zinfo.file_size = 0
        zinfo.compress_size = 0

        self.zipfile._writecheck(zinfo)

        # write header to stream
        self.out_stream.write(zinfo.FileHeader())

        self.current_file = zinfo

    def flush(self):
        zinfo = self.current_file
        self.out_stream.write(struct.pack("<LLL", zinfo.CRC, zinfo.compress_size, zinfo.file_size))
        self.zipfile.filelist.append(zinfo)
        self.zipfile.NameToInfo[zinfo.filename] = zinfo
        self.current_file = None

    def write(self, bytes):
        self.out_stream.write(bytes)
        self.out_stream.flush()
        zinfo = self.current_file
        # update these...
        zinfo.CRC = zipfile.crc32(bytes, zinfo.CRC) & 0xffffffff
        zinfo.file_size += len(bytes)
        zinfo.compress_size += len(bytes)

    def read(self):
        i = self.out_stream.pos

        self.out_stream.seek(self._last_streamed)
        bytes = self.out_stream.read()

        self.out_stream.seek(i)
        self._last_streamed = i

        return bytes

    def close(self):
        self.zipfile.close()
Run Code Online (Sandbox Code Playgroud)

请记住,这段代码只是一个概念的快速证明,一旦我决定让http服务器本身处理这个问题,我没有进行进一步的开发或测试.如果你决定使用它,你应该考虑的一些事情是检查嵌套文件夹是否正确存档,以及文件名编码(无论如何,这总是令人痛苦的zip文件).


rec*_*gic 7

您可以通过将fileobj包装在类似于实现的文件中,将ZipFile流式传输到Pylons或Django响应文件框架tell().这将缓冲内存中zip的每个单独文件,但流式传输zip本身.我们使用它来流式下载一个充满图像的zip文件,因此我们永远不会在内存中缓冲多个图像.

这个例子流到了sys.stdout.对于Pylons使用response.body_file,对于Django,您可以将HttpResponse自身用作文件.

import zipfile
import sys


class StreamFile(object):
    def __init__(self, fileobj):
        self.fileobj = fileobj
        self.pos = 0

    def write(self, str):
        self.fileobj.write(str)
        self.pos += len(str)

    def tell(self):
        return self.pos

    def flush(self):
        self.fileobj.flush()


# Wrap a stream so ZipFile can use it
out = StreamFile(sys.stdout)
z = zipfile.ZipFile(out, 'w', zipfile.ZIP_DEFLATED)

for i in range(5):
    z.writestr("hello{0}.txt".format(i), "this is hello{0} contents\n".format(i) * 3)

z.close()
Run Code Online (Sandbox Code Playgroud)