Flask[邂逅]

Flask[邂逅]

七月 06, 2023

flask 下载文件

flask提供了两个可进行下载的函数:send_from_directory 和 send_file

send_from_directory

send_from_directory函数内部调用了send_file,可以认为,真正执行下载操作的其实是send_file,那么send_from_directory存在的意义是什么呢?

1
2
3
4
5
6
7
8
9
10
11
def send_from_directory(directory, filename, **options):
filename = safe_join(directory, filename)
if not os.path.isabs(filename):
filename = os.path.join(current_app.root_path, filename)
try:
if not os.path.isfile(filename):
raise NotFound()
except (TypeError, ValueError):
raise BadRequest()
options.setdefault('conditional', True)
return send_file(filename, **options)

上面为send_from_directory函数的全部代码,在调用send_file函数之前,它做的最重要的事情就是获得一个安全的filename。

下载文件的名字,为用户发请求时传给后端的,如果直接使用客户端发送的文件名字,则存在安全隐患。

关于这个安全隐患,其本质都是利用地址拼接这个动作修改实际操作文件的地址,为了防止黑客恶意下载,函数第一行代码便是使用safe_join函数,防止黑客通过修改filename的值达到下载关键文件的目的。

因此,实际生产环境下,推荐使用send_from_directory函数

send_file

使用send_file例子

1
2
3
4
5
6
7
8
9
10
11
12
13
from flask import send_from_directory, send_file
from flask import Flask


app = Flask(__name__)


@app.route('/download')
def download():
return send_file('./data/3.xlsx')


app.run(debug=True)

send_file函数会调用guess_type函数获取文件的类型,设置响应头里的content-type。

在浏览器里打开 http://127.0.0.1:5000/download 这个url,会直接进行下载,但下载的文件名字并不是所期望的3.xlsx,浏览器在保存时用的名字是download.xlsx。

如果希望浏览器下载保存文件时使用的名字是3.xlsx,则需要将参数as_attachment设置为True。

秘密藏在响应头的首部中,由于设置了as_attachment为True,flask会添加Content-Disposition

1
Content-Disposition: attachment; filename=3.xlsx

这样,浏览器便明白文件名是什么。

如果希望浏览器以其他的名字保存该文件,则可以单独设置attachment_filename 参数

1
2
3
4
5
@app.route('/download')
def download():
return send_file('./data/3.xlsx',
as_attachment=True,
attachment_filename='test.xlsx')

这样,浏览器就会使用test.xlsx来保存文件。

处理下载文件中出现中文的乱码问题

当attachment_filename参数设置为中文文件名时,flask会报错误

1
UnicodeEncodeError: 'latin-1' codec can't encode characters in position 43-44: ordinal not in range(256)

引发这个问题的原因,可以一直追溯到http协议,按照协议规定,HTTP Header 中的文本数据必须是 ASCII 编码的,为了解决header出现其他编码的问题,浏览器各显神通,这里的原理与历史可以参考这篇文章 https://blog.csdn.net/u011090495/article/details/18815777

我这里直接给出解决办法 COOLPYTHON NB

1
2
3
4
5
6
7
8
9
@app.route('/download')
def download():
filename = quote("测试表格.xlsx")
rv = send_file('./data/3.xlsx',
as_attachment=True,
attachment_filename=filename)

rv.headers['Content-Disposition'] += "; filename*=utf-8''{}".format(filename)
return rv