You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flask创建文件夹压缩包返回响应时出现OSError: [Errno 5]输入输出错误

Flask下载ZIP文件夹后出现OSError: [Errno 5] Input/output error的解决方法

问题重现

我在Flask应用中实现了文件夹下载接口,先将文件夹压缩为ZIP包再返回,代码如下:

def download_data(zip_file_name):
    current_time = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
    shutil.make_archive(zip_file_name + current_time, 'zip', folder_path)

    @after_this_request
    def cleanup(response):
        try:
            os.remove(zip_file_name + current_time + ".zip")
            return response
        except Exception as ex:
            LOG.error(f"Error while removing the zipped dataset after downloading {ex}")

    return send_file(zip_file_name + current_time + ".zip")

API返回200状态码,但日志抛出以下错误:

OSError: [Errno 5] Input/output error

data = self.file.read(self.buffer_size)
File "/usr/local/lib/python3.10/site-packages/werkzeug/wsgi.py", line 365, in __next__

Traceback (most recent call last):

Debugging middleware caught exception in streamed response at a point where response headers were already sent.

注:下载源为OBS存储桶,单个文件下载功能正常。

问题根因

after_this_request回调会在响应头发送后立即执行,但此时send_file的流式传输尚未完成——Werkzeug会分块读取ZIP文件发送给客户端,提前删除文件会导致后续读取操作失败,触发IO错误。

解决方案

方案1:使用响应关闭钩子(最可靠)

Flask的Response对象提供call_on_close方法,该钩子会在响应完全发送、文件句柄关闭后触发,确保文件不再被使用时再删除:

from datetime import datetime
import os
import shutil
from flask import send_file

def download_data(zip_file_name):
    current_time = datetime.now().strftime("%Y%m%d%H%M%S")
    zip_filename = f"{zip_file_name}{current_time}"
    zip_path = f"{zip_filename}.zip"
    
    # 生成ZIP包
    shutil.make_archive(zip_filename, 'zip', folder_path)
    
    # 创建响应对象
    response = send_file(zip_path)
    
    # 定义清理函数
    def cleanup_zip():
        try:
            os.remove(zip_path)
        except Exception as ex:
            LOG.error(f"Failed to delete zip file: {ex}")
    
    # 添加关闭时的清理钩子
    response.call_on_close(cleanup_zip)
    return response

方案2:使用临时文件自动管理

借助tempfile创建临时文件,减少手动文件管理的风险:

import tempfile
import shutil
from datetime import datetime
from flask import send_file

def download_data(zip_file_name):
    # 创建临时文件(delete=False避免自动删除,因为需要先写入ZIP内容)
    with tempfile.NamedTemporaryFile(suffix='.zip', delete=False) as tmp_file:
        temp_zip_path = tmp_file.name
    
    # 生成ZIP到临时文件(注意去掉后缀,make_archive会自动添加.zip)
    temp_zip_base = temp_zip_path.rsplit('.', 1)[0]
    shutil.make_archive(temp_zip_base, 'zip', folder_path)
    
    response = send_file(
        temp_zip_path,
        as_attachment=True,
        download_name=f"{zip_file_name}_{datetime.now().strftime('%Y%m%d%H%M%S')}.zip"
    )
    
    def cleanup_temp_zip():
        try:
            os.remove(temp_zip_path)
        except Exception as ex:
            LOG.error(f"Failed to delete temp zip: {ex}")
    
    response.call_on_close(cleanup_temp_zip)
    return response

方案3:流式生成ZIP(无本地文件)

如果可以直接从OBS读取文件内容,建议跳过本地ZIP文件生成,直接在内存中流式生成ZIP并发送,彻底避免文件IO问题:

from flask import Response
import zipfile
from io import BytesIO
from datetime import datetime

# 需自行实现OBS文件遍历和读取函数
def list_obs_folder_files(obs_folder_path):
    """遍历OBS文件夹下的所有文件,返回文件路径列表"""
    pass

def read_obs_file(obs_file_path):
    """读取OBS文件内容,返回字节数据"""
    pass

def generate_zip_stream(obs_folder_path):
    zip_buffer = BytesIO()
    with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_obj:
        for obs_file_path in list_obs_folder_files(obs_folder_path):
            # 获取ZIP内的相对路径
            relative_path = obs_file_path.replace(obs_folder_path, '')
            # 读取文件内容并写入ZIP
            file_content = read_obs_file(obs_file_path)
            zip_obj.writestr(relative_path, file_content)
    zip_buffer.seek(0)
    return zip_buffer

def download_data(zip_file_name):
    current_time = datetime.now().strftime("%Y%m%d%H%M%S")
    zip_stream = generate_zip_stream(folder_path)
    
    return Response(
        zip_stream,
        mimetype='application/zip',
        headers={
            "Content-Disposition": f"attachment; filename={zip_file_name}{current_time}.zip"
        }
    )

内容的提问来源于stack exchange,提问作者Prakash

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 07:55:54