本文详解Flask后端如何通过send_file返回二进制文件流,以及前端利用Ajax POST请求配合URL.createObjectURL实现文件下载。重点解决中文文件名在HTTP Header传输中的乱码问题,提供Python urllib.parse.quote与前端decodeURI的编码解码方案,并对比Content-Disposition头部的兼容性差异。
Flask文件流返回与前端Ajax下载实现
前端通过Ajax POST请求触发文件下载。
后端需返回文件二进制流,在Flask框架中可调用 return send_file 加载指定路径的文件并返回二进制数据,同时在响应头(headers)中附加文件元数据,例如文件名。
前端获取响应后,利用 URL.createObjectURL() 生成一个指向该二进制数据的 DOMString URL。接着创建一个临时的 a 标签节点,将该 URL 赋值给 a 标签的 href 属性,最后触发 a 标签的 click() 事件,即可唤起浏览器的默认下载对话框。
图片展示方案。
若需在前端直接展示图片而非下载,逻辑与上述类似:将创建的临时元素由 a 标签改为 img 标签,将 href 属性替换为 src 属性,并将生成的 URL 对象赋值给目标 img 标签的 src 即可直接渲染图片。
另一种展示图片的方式是后端将图片转换为 base64 编码字符串返回,前端将 src 设置为形如 "data:image/svg+xml;base64,${base字符串}" 的格式。注意其中的 svg+xml 需根据实际图片格式调整,例如 PNG 图片应替换为 png。
中文文件名乱码处理与编码方案
中文文件名乱码问题。
在 HTTP 响应头中直接传输中文文件名时,推荐后端使用 URL 编码处理。具体做法是使用 Python 的 urllib.parse.quote 对文件名进行编码,前端则通过 decodeURI() 方法进行解码,从而避免乱码。
此外还可以设置headers的 Content-Disposition: attachment; filename*=UTF-8''xxxxx ,不过兼容性嘛……麻烦还不如直接urlcode算了,而且也懒得设置 Content-Disposition 了,前端从 Content-Disposition 中取 filename 也是够麻烦的,会取到一长串字符串然后自己再想办法取出来 filename= 后面的信息。
具体实现代码如下。
Flask 后端代码。
Flask后端代码示例
from urllib.parse import quote
@file.route('/download', methods=["POST"])
def download_file():
filename='xx' #文件名
filepath='xx/xx' #文件路径
res = make_response(send_file(filepath))
#自定义的一个header,方便前端取到名字
res.headers['filename'] = quote(filename.encode('utf-8'))
return res
javascript——以async异步fetch为例:
async function download() {
const res = await fetch(`http://xxx/file/download`, {
method: "POST",
body: JSON.stringify({}), //body里面是要发送的数据
headers: { "Content-Type": "application/json" },
responseType: 'blob'
})
if (res.ok) {
const blData = await res.blob() //拿到blob数据
const urlObjData = window.URL.createObjectURL(new Blob([blData])) //创建url对象
//获取文件 进行下转码
const fileName = decodeURI(fileNameres.headers.get('filename'))
//创建a标签 点击a标签 达到下载目的
const link = document.createElement('a')
link.href = urlObjData
link.download = fileName //下载文件的名字
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(urlObjData);
//展示图片
//xxx.src=urlObjData
}
}
补充说明:Flask 下载文件时返回文件流的实现方式。
前端 HTML 代码。
<a name="downloadbtn" class="btn btn-success pull-right" href="/downloadfile/?filename=/root/allfile/123.txt">下载</a>
Python 后端代码。
前端与后端完整交互代码
@app.route('/downloadfile/', methods=['GET', 'POST'])
def downloadfile():
if request.method == 'GET':
fullfilename = request.args.get('filename')
# fullfilename = '/root/allfile/123.txt'
fullfilenamelist = fullfilename.split('/')
filename = fullfilenamelist[-1]
filepath = fullfilename.replace('/%s'%filename, '')
#普通下载
# response = make_response(send_from_directory(filepath, filename, as_attachment=True))
# response.headers["Content-Disposition"] = "attachment; filename={}".format(filepath.encode().decode('latin-1'))
#return send_from_directory(filepath, filename, as_attachment=True)
#流式读取
def send_file():
store_path = fullfilename
with open(store_path, 'rb') as targetfile:
while 1:
data = targetfile.read(20 * 1024 * 1024) # 每次读取20M
if not data:
break
yield data
response = Response(send_file(), content_type='application/octet-stream')
response.headers["Content-disposition"] = 'attachment; filename=%s' % filename # 如果不加上这行代码,导致下图的问题
return response
若下载的文件没有文件名或文件格式信息,可打开浏览器开发者工具(F12),对比查看 response.headers 与正常情况下的响应头差异,以排查问题。
总结。
本文介绍了基于 Flask 文件流与 Ajax POST 的文件下载及中文文件名处理方案。更多相关内容可参考本站过往文章或继续浏览后续内容,感谢支持。