创建和流的大型归档,而不将其存储在内存或磁盘上(Create and stream a large

2019-06-24 03:12发布

我想允许用户下载多个大文件的存档一次。 然而,文件和档案可能太大,存储器或在我的服务器上的磁盘存储(他们是从其他服务器在运行中的流)。 我想,因为我流式传输到用户生成的存档。

我可以用tar或zip或什么是最简单的。 我使用的Django的,这让我在我的响应返回发电机或类似文件的对象。 这个对象可以用来泵处理一起。 然而,我无法搞清楚如何构建这种围绕zip文件或tar文件库的事情,我怕他们可能不支持读取文件,因为他们去,或者读取存档,因为它是建立。

对这个答案的迭代器转换为一个类文件对象可能的帮助。 tarfile#addfile需要一个迭代,但它似乎立即传递到shutil.copyfileobj ,所以这可能不会像发电机友好,因为我所希望的。

Answer 1:

最后我用的SpiderOak ZipStream 。



Answer 2:

您可以通过生成和流zip文件没有压缩,这基本上是每个文件的内容之前,只需添加标题做到这一点。 你说得对,该库不支持这一点,但你可以将它们围绕破解得到它的工作。

此代码包装zipfile.ZipFile与管理数据流,并创建zipfile.ZipInfo的情况下,为这些文件,因为他们来的一类。 CRC和大小可以在年底进行设置。 你可以把从输入流中的数据到它与put_file(),写()和flush(),和在它外面具有读读取的数据输出流()。

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()

请记住,这些代码的概念只是一个快速的证明,我确实没有进一步的开发或测试一次,我决定让http服务器本身处理这个问题。 有几件事情你应该看看,如果你决定使用它来检查,如果嵌套的文件夹正确归档和文件名编码(它总是与zip文件一个痛苦反正)。



Answer 3:

您可以通过在一些类文件,实现包裹FileObj文件流式传输的ZipFile到主塔或者Django的响应FileObj文件tell() 这将缓冲在内存中的zip中的各个文件,但流的zip本身。 我们用它来下载流一个zip文件的完整的图像,所以我们从来没有比缓冲存储器中的单个图像更。

这个例子流到sys.stdout 。 对于塔使用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()


Answer 4:

下面是解距离Pedro韦尔内克(从上面),但具有一个固定,以避免在收集存储器中的所有数据( read方法被固定一点点):

class ZipStreamer(object):
    def __init__(self):
        self.out_stream = StringIO.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 mega_streamer
        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):
        self.out_stream.seek(self._last_streamed)
        bytes = self.out_stream.read()
        self._last_streamed = 0

        # cleaning up memory in each iteration
        self.out_stream.seek(0) 
        self.out_stream.truncate()
        self.out_stream.flush()

        return bytes

    def close(self):
        self.zipfile.close()

那么你可以使用stream_generator功能作为流的zip文件

def stream_generator(files_paths):
    s = ZipStreamer()
    for f in files_paths:
        s.put_file(f)
        with open(f) as _f:
            s.write(_f.read())
        s.flush()
        yield s.read()
    s.close()

例如对于猎鹰 :

class StreamZipEndpoint(object):
    def on_get(self, req, resp):
        files_pathes = [
            '/path/to/file/1',
            '/path/to/file/2',
        ]
        zip_filename = 'output_filename.zip'
        resp.content_type = 'application/zip'
        resp.set_headers([
            ('Content-Disposition', 'attachment; filename="%s"' % (
                zip_filename,))
        ])

        resp.stream = stream_generator(files_pathes)


文章来源: Create and stream a large archive without storing it in memory or on disk
标签: python http