上传与下载在web开发中,是极为常见的功能,flask提供了两个可进行下载的函数, send_from_directory 和 send_file, 本文将向你介绍这两个函数的使用细节
send_from_directory函数内部调用了send_file,你可以认为,真正执行下载操作的其实是send_file,那么send_from_directory存在的意义是什么呢?
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。
下载文件的名字,不出意外应该是用户发请求时传给后端的,那么如果你直接使用客户端传给你的文件名字,就存在安全隐患,关于这个安全隐患,在flask上传文件的文章里有过介绍,其本质都是利用地址拼接这个动作修改实际操作文件的地址,为了防止黑客恶意下载,函数第一行代码便是使用safe_join函数,防止黑客通过修改filename的值达到下载关键文件的目的。
实际生产环境下,推荐使用send_from_directory函数
下面是一个简单的使用send_file的例子
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
Content-Disposition: attachment; filename=3.xlsx
这样,浏览器就知道该用什么名字保存文件了,如果你希望浏览器以其他的名字保存该文件,则可以设置attachment_filename 参数
@app.route('/download')
def download():
return send_file('./data/3.xlsx',
as_attachment=True,
attachment_filename='test.xlsx')
这样,浏览器就会使用test.xlsx来保存文件。
当attachment_filename参数设置为中文文件名时,flask会报错误
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
我这里直接给出解决办法
@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
QQ交流群: 211426309