flask 下载文件

上传与下载在web开发中,是极为常见的功能,flask提供了两个可进行下载的函数, send_from_directory 和 send_file, 本文将向你介绍这两个函数的使用细节

send_from_directory

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

下面是一个简单的使用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

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案