Django 4 异步 StreamingHTTPResponse 和发送大型生成的 zipfile

3

我正在尝试在Django中压缩一些大文件并将它们作为zipfile流式传输到客户端。问题是它倾向于首先在内存中创建zipfile,然后再发送。这会导致内存耗尽并使Django崩溃。由于存储空间有限,事先将zipfile写入磁盘也不可行。

我使用zipfly包成功实现了这一点,并且在WSGI同步运行Django时工作正常。但是,我需要实现WebSockets并且必须使Django在ASGI异步运行,而流式传输的zipfly实现就出现了问题。

因此,我开始编写自己的实现,并且几乎让它工作了,但仍存在问题。它目前正在尝试即时创建zip文件,但仍在使用相当多的内存。此外,我的代码没有向客户端发送数据流已结束的指示,导致浏览器最终中止传输。

有人能帮助我吗?以下是代码:

async def create_zip_streaming_response(files_to_compress: list, filename: str):
# Create a generator function to yield chunks of data
async def generate():
    # Create an in-memory byte buffer to store the compressed data
    buffer = BytesIO()

    # Create a zip file object
    with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
        # Iterate over the files and add them to the zip file
        for file_path in files_to_compress:
            # Get the file name from the file path
            file_name = os.path.basename(file_path)

            # Add the file to the zip file
            zip_file.write(file_path, file_name)

            # Flush the buffer to ensure data is written
            buffer.flush()

            # Move the buffer's read position to the beginning
            buffer.seek(0)

            # Read the data from the buffer
            data = buffer.read()

            # Yield the generated data in chunks
            yield data

            # Reset the buffer for the next file
            buffer.truncate(0)

    # Create a streaming response using the generator function
    response = StreamingHttpResponse(generate(), content_type='application/zip')
    response['Content-Disposition'] = f'attachment; filename="{filename}"'

    return response

我非常感谢您的帮助。这是一个非常重要的业余项目,我不知道该如何解决这个问题。该项目已经在 Docker 中部署,并且在生产环境中与 Nginx 和 Uvicorn 结合使用。


你最后弄清楚怎么做了吗? - undefined
1
没,从来没弄好过。 - undefined
啊,太糟糕了,谢谢你的回复。我决定使用aiohttp来完成我的项目,因为django似乎有点过于复杂了。 - undefined
我在那个Django项目上有太多事情要处理,不想放弃它。但是我在考虑使用同步的FastAPI创建一个独立的下载应用程序。也许这样可以兼顾两全。 - undefined
1个回答

0
我遇到了同样的问题,通过修改你的实现方式,我解决了这个问题,我添加了一个最终的“flush”并使用了自己的缓冲实现。在测试过程中,我没有发现任何提到的问题。
class Buffer:
    def __init__(self):
        self.buf = bytearray()

    def write(self, data):
        self.buf.extend(data)
        return len(data)
    
    def flush(self):
        pass

    def take(self):
        buf = self.buf
        self.buf = bytearray()
        return bytes(buf)

    def end(self):
        buf = self.buf
        self.buf = None
        return bytes(buf)

def create_zip_streaming_response(*args):
    files_to_compress = ['a.txt', 'b.txt', 'c.csv']

    def generate():
        buffer = Buffer()

        # Create a zip file object
        with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
            for file_path in files_to_compress:
                zip_file.writestr(file_path, "This is file " + file_path)

                yield buffer.take()

        yield buffer.end()

    response = StreamingHttpResponse(generate(), content_type='application/zip')
    response['Content-Disposition'] = f'attachment; filename="test.zip"'
    return response

工作得非常好。不过缺点是它只能在WSGI同步的Django中使用。我的最终目标是ASGI异步,这样我就可以在一个应用程序中同时使用流式传输大型zip文件和WebSockets。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接