Flask Web应用流式生成ZIP文件时浏览器下载进度条的Content-Length不匹配问题
Flask Web应用流式生成ZIP文件时浏览器下载进度条的Content-Length不匹配问题
我正在开发一个Flask Python Web应用,核心功能是根据用户设置的筛选条件,把一批文件打包成ZIP后提供下载。但遇到了一个头疼的问题:用户点击下载按钮后,后端需要拉取所有目标文件并开始创建ZIP,这个过程可能要耗时好几分钟,用户完全没法判断应用是在正常处理还是已经挂掉了。
我想到的解决方案是流式传输生成中的ZIP文件——这样既能更快地把文件内容发送给用户,也能让用户感知到应用正在工作。但这里有个关键问题:要让浏览器自带的下载栏(就是那个弹出的小窗口或者带进度条的下载页面)正常显示进度,必须在响应头里提供Content-Length,可ZIP文件还没生成完成,我根本没法知道最终的文件大小。
我本来以为估算大小会很简单,因为我用的是ZIP_STORED(无压缩)模式,只需要把所有文件的大小加起来,再加上ZIP的结构开销就行。于是我写了个计算总大小的函数,给每个文件加了22字节的开销,再给中央目录加了22字节,但实际测试下来,浏览器还是会返回ERR_CONTENT_LENGTH_MISMATCH错误,直接拒绝下载。看来ZIP的内部结构开销比我预估的要复杂。
目前我有几个备选方案:
- 做一个基于Server Sent Event(SSE)的自定义进度条:开一个单独的
/progress路由,前端通过轮询这个接口获取已发送的字节数,从而更新进度。但我真的特别想用浏览器自带的下载栏,这算是我的一个小执念了。 - 放弃流式传输:先在后台完整生成ZIP文件,用SSE给用户实时更新生成进度,等文件完全生成后,再带着准确的
Content-Length头把文件发送给用户。但这个方案的体验不如流式传输那么流畅。
附上我用来估算大小和生成ZIP条目的代码:
import os from datetime import datetime from zipfile import ZIP_64, ZIP_STORED def calculate_total_size(filenames): total_size = 0 for file in filenames: matching_filepath, _ = get_file_info(file) if matching_filepath: total_size += os.path.getsize(matching_filepath) # Add overhead for ZIP file structure (22 bytes per file + 22 bytes for the central directory) total_size += 22 * (len(filenames) + 1) return total_size def generate_file_entries(filenames): for file in filenames: matching_filepath, filename = get_file_info(file) if matching_filepath: file_stat = os.stat(matching_filepath) modified_at = datetime.utcfromtimestamp(file_stat.st_mtime) with open(matching_filepath, 'rb') as f: chunk = f.read() if isinstance(chunk, bytes): # Ensure only bytes are yielded yield filename, modified_at, 0o600, ZIP_64, [chunk] else: print(f"Unexpected data type for file contents: {type(chunk)}")
备注:内容来源于stack exchange,提问作者john stamos




