Django 4异步StreamingHTTPResponse流式生成大Zip文件问题求助
Django ASGI模式下大文件流式Zip传输解决方案
问题根源
你的核心问题在于:
- 标准
zipfile.ZipFile无法支持流式生成Zip包——它需要先缓存所有文件的压缩数据和元信息,最后写入中央目录,直接导致内存占用过高; - 你的实现没有输出Zip的结束标识,浏览器无法判断流是否完成,最终中断传输;
- 原WSGI环境下的zipfly依赖同步IO,在ASGI异步环境中会阻塞事件循环,导致方案失效。
解决方案
我们需要手动构建Zip格式的各个组成部分(本地文件头、压缩数据、中央目录、结束记录),实现真正的流式输出;同时使用异步文件IO避免阻塞ASGI事件循环,并且禁用Nginx的响应缓冲确保数据流实时传输到客户端。
实现代码
首先安装异步文件依赖:
pip install aiofiles
然后编写流式Zip生成逻辑:
import os import zlib import struct import aiofiles from django.http import StreamingHttpResponse # Zip格式常量定义 ZIP_LOCAL_HEADER_SIGNATURE = 0x04034b50 ZIP_CENTRAL_HEADER_SIGNATURE = 0x02014b50 ZIP_END_CENTRAL_SIGNATURE = 0x06054b50 ZIP_DEFLATED = 8 async def create_zip_streaming_response(files_to_compress: list, filename: str): async def generate(): central_directory = [] central_dir_offset_start = 0 total_bytes_written = 0 for file_path in files_to_compress: file_name = os.path.basename(file_path) file_size = os.path.getsize(file_path) # 计算文件CRC32校验值 crc = 0 async with aiofiles.open(file_path, 'rb') as f: while chunk := await f.read(65536): crc = zlib.crc32(chunk, crc) crc &= 0xFFFFFFFF # 分块压缩文件内容 compressor = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15) compressed_chunks = [] async with aiofiles.open(file_path, 'rb') as f: while chunk := await f.read(65536): compressed_chunk = compressor.compress(chunk) if compressed_chunk: compressed_chunks.append(compressed_chunk) compressed_chunk = compressor.flush() if compressed_chunk: compressed_chunks.append(compressed_chunk) compressed_size = sum(len(c) for c in compressed_chunks) # 构造本地文件头并输出 local_header = struct.pack( '<IHHHHIIIIHH', ZIP_LOCAL_HEADER_SIGNATURE, 20, # 解压所需最低版本 0, # 通用位标记 ZIP_DEFLATED, # 压缩方式 0, # 修改时间(占位) 0, # 修改日期(占位) crc, compressed_size, file_size, len(file_name), 0, # 扩展字段长度 ) + file_name.encode('utf-8') yield local_header total_bytes_written += len(local_header) current_local_offset = total_bytes_written - len(local_header) # 输出压缩后的文件数据块 for chunk in compressed_chunks: yield chunk total_bytes_written += len(chunk) # 记录中央目录条目 central_record = struct.pack( '<IHHHHIIIIHHHHHII', ZIP_CENTRAL_HEADER_SIGNATURE, 20, # 创建版本 20, # 解压所需版本 0, # 通用位标记 ZIP_DEFLATED, # 压缩方式 0, # 修改时间 0, # 修改日期 crc, compressed_size, file_size, len(file_name), 0, # 扩展字段长度 0, # 文件注释长度 0, # 起始磁盘号 0, # 内部文件属性 0o100644 << 16, # 外部文件属性(Unix权限) current_local_offset, # 本地文件头偏移 ) + file_name.encode('utf-8') central_directory.append(central_record) # 输出中央目录 central_dir_offset_start = total_bytes_written central_dir_size = sum(len(r) for r in central_directory) for record in central_directory: yield record total_bytes_written += len(record) # 输出Zip结束记录 end_record = struct.pack( '<IHHHHIIH', ZIP_END_CENTRAL_SIGNATURE, 0, # 当前磁盘号 0, # 中央目录起始磁盘号 len(central_directory), # 当前磁盘的中央目录条目数 len(central_directory), # 总中央目录条目数 central_dir_size, central_dir_offset_start, 0, # Zip注释长度 ) yield end_record response = StreamingHttpResponse(generate(), content_type='application/zip') response['Content-Disposition'] = f'attachment; filename="{filename}"' # 禁用Nginx缓冲,确保流式传输生效 response['X-Accel-Buffering'] = 'no' return response
关键说明
- 真正的流式传输:手动生成Zip的每个结构块,处理完一部分数据就立即yield给客户端,不需要缓存整个Zip包,彻底解决内存占用问题。
- 异步IO兼容:使用
aiofiles异步读取源文件,避免阻塞ASGI事件循环,保证服务的并发能力。 - 完整Zip结构:最后输出中央目录和结束记录,让浏览器能正确识别Zip文件的结束,避免传输中断。
- Nginx适配:添加
X-Accel-Buffering: no响应头,禁止Nginx缓冲整个响应,否则会回到内存耗尽的问题。
注意事项
- 分块大小(示例中为65536字节)可根据文件大小调整,过小会增加IO次数,过大则提升内存占用。
- 如果需要支持中文文件名,需要将通用位标记设置为
0x0800(启用UTF-8编码)。 - Uvicorn部署时,建议调整
--limit-concurrency参数控制并发流式请求数,避免内存过载。
内容的提问来源于stack exchange,提问作者T-101
相关产品推荐
相关产品推荐

