从 Flask 下载文件时出现内存错误。 文件大小约为100兆字节。 我该如何解决它?
Flask 下载代码
return send_from_directory(s_trash_path, s_zip_name, mimetype='zip', as_attachment=True)
错误代码
[2018-07-21 16:11:22,328] ERROR in app: Exception on /ec-fileupload/download/select [POST]
Traceback (most recent call last):
File "/home/venv_ec_fileupload/lib/python3.6/site-packages/flask/app.py", line 1982, in wsgi_app
response = self.full_dispatch_request()
File "/home/venv_ec_fileupload/lib/python3.6/site-packages/flask/app.py", line 1615, in full_dispatch_request
return self.finalize_request(rv)
File "/home/venv_ec_fileupload/lib/python3.6/site-packages/flask/app.py", line 1632, in finalize_request
response = self.process_response(response)
File "/home/venv_ec_fileupload/lib/python3.6/site-packages/flask/app.py", line 1856, in process_response
response = handler(response)
File "./app/__init__.py", line 170, in after_request
s_data = resp.get_data()
File "/home/venv_ec_fileupload/lib/python3.6/site-packages/werkzeug/wrappers.py", line 987, in get_data
rv = b''.join(self.iter_encoded())
MemoryError
如果您提供二进制文件,则不应遍历行,因为它基本上只包含一个“行”,这意味着您仍然将整个文件一次性加载到 RAM 中。
读取大文件的唯一正确方法是通过块:
CHUNK_SIZE = 8192
def read_file_chunks(path):
with open(path, 'rb') as fd:
while 1:
buf = fd.read(CHUNK_SIZE)
if buf:
yield buf
else:
break
然后就可以安全地在此块读取器上调用
stream_with_context
,例如如果您提供视频文件:
@app.route('/videos/<name>')
def serve_video(name):
fp = resource_path_for(name)
if fp.exists():
return Response(
stream_with_context(read_file_chunks(fp)),
headers={
'Content-Disposition': f'attachment; filename={name}'
}
)
else:
raise exc.NotFound()
在底层,Flask 响应过程获取每个块(来自生成器
read_file_chunks(fp)
)并在加载下一个块之前将其刷新到连接。刷新后,块数据不再被引用并被垃圾收集器清理,因此不会有太多块同时停留在 RAM 中。
由于您的文件很大并且是动态生成的,我建议您不要使用
send_from_directory()
来发送文件。
查看 Flask 流文档,了解如何流式传输文件(发送小块数据而不是完整文件):http://flask.pocoo.org/docs/1.0/patterns/streaming/
from flask import Response
@app.route('/large.csv')
def generate_large_csv():
def generate():
for row in iter_all_rows():
yield ','.join(row) + '\n'
return Response(generate(), mimetype='text/csv')
上面的代码是如何使用 Flask 流式传输 csv 文件的片段。
但是,如果您的文件是静态的,那么 Flask 建议使用
nginx
进行部署。
在使用Gunicorn时,我在这里找到了一个非常好的解决方案:gunicorn:如何解决“WORKER TIMEOUT”? 默认超时时间约为 30,因此每个连接都会在 30 秒后关闭,我只是将该值设置为 3600,因此每次下载文件有 1 小时的时间。在命令行中,您只需添加即可做到这一点
--timeout 3600