Web开发中的文件下载
前端下载
静态服务器配置
WEB开发中,对静态资源的访问一般会通过Nginx,Apache等服务器进行路由配置。
server {
listen 80;
server_name localhost;
location /static {
alias /home/linqi/static;
}
}
可以浏览器访问 http://localhost/static/文件名 获取到 /home/linqi/static 目录下的文件。
前端下载静态资源
在前端的开发中,对于静态资源一般会采用a标签连接资源
<a href="/static/文件">下载</a>
但对于一些图片和文本浏览器可以浏览,因此点击下载后会在新窗口直接浏览图片。
有什么方式可以直接让浏览器进行下载能? 那就是download属性。
download 属性指定在用户单击超链接时将下载目标。仅在设置href属性时才使用此属性,该属性的值将被设置为下载文件的文件名,当缺省该值,文件名为原文件名。
<a href="/static/test.jpg" download>下载</a>
这时候浏览器对于静态资源test.jpg的访问就会直接进行下载。test.jpg被下载,文件名image.jpg。
<a href="/static/test.jpg" download='image.jpg'>下载</a>
主流的浏览器都已经支持download 属性,但仍可能存在部分浏览器及版本不支持该数据。
以下主流浏览器以下版本开始支持该属性。
可以通过
var isSupportDownload = 'download' in document.createElement('a');
判断浏览器是否支持该属性。
需要注意的是download 生效不仅需要浏览器的支持,还需要href属性,并且只有当href属性指向同源的资源才可以正确的使用成功,如果需要下载的资源是跨域的,包括跨子域,在Chrome浏览器下,使用download属性是可以下载的,但是,并不能重置下载的文件的命名;而FireFox浏览器下,则download属性是无效的,也就是FireFox浏览器无论如何都不支持跨域资源的download属性下载。
前端下载 Blob 类文件对象
当后端接口传输的数据为二进制的文件数据或者想直接将文件从前端下载不走后端逻辑,可以使用Blob对象处理。
Blob 是类文件对象,前端中用户存储二进制的文件数据,前端中File对象就是继承自Blob对象,可以通过Blob对象控制文件的上传下载。
var blob = new Blob()
// 括号内二进制文件流内容
var a = document.createElement('a');
var url = window.URL.createObjectURL(blob);
var filename = 'myfile.zip';
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
需要注意的是不同浏览器对Blob对象由不同的限制,主要限制在内容的大小,而且对大文件构建Blog对象可能会花费较长的时间。
后端下载
现在前后端分离的开发模式越来越普遍,资源与前端服务器跨域的情况导致download属性失效,该如何处理呢?一般为了解决download属性失效,需要修改下载文件名称及文件下载需要权限等经过后端逻辑判断才可以进行文件下载的情况可以使用添加header信息告诉浏览器该如何处理显示内容。
Content-Type 是HTTP协议中Header中的重要参数,它用户标识发送或接收到的数据类型,浏览器根据该参数决定数据打开显示的方式。其参数为MIME类型,一般text/* 表示普通文本,image/* 表示图像, application/* 表示某种二进制数据,浏览器一般会认为text/,image/ 可以直接展示在浏览器上,而application/octet-stream 表示未知的应用程序文件,浏览器一般不会自动执行或询问执行。浏览器会像对待 设置了HTTP头Content-Disposition 值为 attachment 的文件一样来对待这类文件。
Content-Disposition 消息头指示回复的内容该以何种形式展示,是以内联的形式(即网页或者页面的一部分),还是以附件的形式下载并保存到本地。inline(默认值) 表示消息体以页面的形式展示,attachment 表示消息体应该被下载, attachment;filename=“filename”; attachement 可以附加filename 参数,表示下载后的文件名。
注意:attachment不仅可以附加filename参数,也可以附加filename*参数,“filename” 和 “filename*” 两个参数的唯一区别在于,“filename*“采用了 RFC 5987 中规定的编码方式。当"filename” 和 “filename*” 同时出现的时候,应该优先采用"filename*”,假如二者都支持的话。filename*使用可以解决中文文件名乱码等问题。
Nginx服务器处理
可以通过服务器添加Contetn-Type Header头告诉浏览器访问的内容为文件,需要进行下载,可以通过Content-Disposition 设置文件名
server {
listen 80;
server_name localhost;
location /static {
add_header Content-Type "application/octet-stream";
if ($arg_filename){
add_header Content-Disposition "attachment; filename={arg_filename};";
# 当url存在filename参数时,根据filename参数设置文件名。
}
alias /home/linqi/static;
}
}
Django 应用处理
Django文件下载
Header头信息可以在应用逻辑中进行添加
from django.shortcuts import HttpResponse
def download(request):
file = open('crm/models.py', 'rb')
response = HttpResponse(file)
response['Content-Type'] = 'application/octet-stream'
response['Content-Disposition'] = 'attachment;filename="models.py"'
return response
Django应用中一般回应消息通过 HttpResponse类处理,但该类对文件下载时会一次性把文件内容合成字符串存储到内存中,文件内容过大时会非常消耗时间和内存。对于下载时需要较长响应时间或者文件内容较大可以采用文件流方式发送文件内容给浏览器。
优化方案
优化方案一
Django中的StreamingHttpResponse 对象可以接收一个迭代器,将文件内容进行流式传输,这是一种非常省时省内存的方法。但是因为StreamingHttpResponse的文件传输过程持续在整个response的过程中,所以这有可能会降低服务器的性能。
from django.http import StreamingHttpResponse
def big_file_download(request):
def file_iterator(file_name, chunk_size=512):
with open(file_name) as f:
while True:
c = f.read(chunk_size)
if c:
yield c
else:
break
the_file_name = "file_name.txt"
response = StreamingHttpResponse(file_iterator(the_file_name))
response['Content-Type'] = 'application/octet-stream'
response['Content-Disposition'] = 'attachment;filename="models.py"'
return response
优化方案二
FileResponse是StreamingHttpResponse针对二进制文件优化的子类。如果由wsgi服务器提供,它使用wsgi.file_wrapper,否则它将文件以小块的形式流出。
from django.http import FileResponse
def download(request):
file = open('crm/models.py', 'rb')
response = FileResponse(file)
response['Content-Type'] = 'application/octet-stream'
response['Content-Disposition'] = 'attachment;filename="models.py"'
return response
优化方案三
使用Django处理文件下载,总是需要读入文件到应用程序内存,再把内存的数据发送到客户端,这种方式应付大负载会消耗更多服务器资源,所以应当采用静态服务器处理下载,Nginx可以通过X-Accel-Redirect的特性来实现内部url资源的重定向。即可以向Django发起请求,由Django 负责对用户权限进行判断,通过Nginx转发url请求,用nginx服务器负责文件的下载。
location /protected_files {
# internal;
alias /var/www/files;
}
from django.shortcuts import HttpResponse
def download(request):
response = HttpResponse()
response['Content-Type'] = 'application/octet-stream'
response['Content-Disposition'] = 'attachment;filename="models.py"'
response['X-Accel-Redirect'] = "/protected_files/models.py"
return response
中文名称乱码及兼容性处理
上文有说到Content-Disposition可以通过filename参数指定文件名,但如果文件名是中文就需要编码,因为其内容规定编码为ASCII。这里可以通过filename*属性来指定对应文件名的编码格式,使文件名能正确的显示。注意到的是该参数在不同的浏览器的限制可能存在不一致,需要做好兼容处理。
def packresponse(f, name,url_path):
# 处理一下返回的头信息
response = HttpResponse(content_type="application/force-download")
name = quote(name.encode('utf-8')) #会将名称一些特殊字符编码
if request.META['HTTP_USER_AGENT'].find('Chrome') == -1 and request.META['HTTP_USER_AGENT'].find('Safari'):
#适配Safari浏览器
response['Content-Disposition'] = "attachment;filename*=utf-8''{}".format(
name)
else:
#适配Chrome,火狐等主流浏览器
response['Content-Disposition'] = "attachment;filename*=utf-8'zh_cn'{}".format(
name)
response['Content-Type'] = "application/octet-stream; charset=UTF-8"
response['X-Accel-Redirect'] = smart_str(url_path)
return response