Django 大文件下载实现过程解析


Posted in Python onAugust 01, 2019

django提供文件下载时,若果文件较小,解决办法是先将要传送的内容全生成在内存中,然后再一次性传入Response对象中:

def simple_file_download(request):
  # do something...
  content = open("simplefile", "rb").read()

如果文件非常大时,最简单的办法就是使用静态文件服务器,比如Apache或者Nginx服务器来处理下载。不过有时候,我们需要对用户的权限做一下限定,或者不想向用户暴露文件的真实地址,或者这个大内容是临时生成的(比如临时将多个文件合并而成的),这时就不能使用静态文件服务器了。

django文档中提到,可以向HttpResponse传递一个迭代器,流式的向客户端传递数据。

要自己写迭代器的话,可以用yield:

def read_file(filename, buf_size=8192):
  with open(filename, "rb") as f:
    while True:
      content = f.read(buf_size)
      if content:
        yield content
      else:
        break
def big_file_download(request):
  filename = "filename"
  response = HttpResponse(read_file(filename))
  return response

或者使用生成器表达式,下面是django文档中提供csv大文件下载的例子:

import csv
 
from django.utils.six.moves import range
from django.http import StreamingHttpResponse
 
class Echo(object):
  """An object that implements just the write method of the file-like
  interface.
  """
  def write(self, value):
    """Write the value by returning it, instead of storing in a buffer."""
    return value
 
def some_streaming_csv_view(request):
  """A view that streams a large CSV file."""
  # Generate a sequence of rows. The range is based on the maximum number of
  # rows that can be handled by a single sheet in most spreadsheet
  # applications.
  rows = (["Row {0}".format(idx), str(idx)] for idx in range(65536))
  pseudo_buffer = Echo()
  writer = csv.writer(pseudo_buffer)
  response = StreamingHttpResponse((writer.writerow(row) for row in rows),
                   content_type="text/csv")
  response['Content-Disposition'] = 'attachment; filename="somefilename.csv"'
  return response

python也提供一个文件包装器,将类文件对象包装成一个迭代器:

class FileWrapper:
  """Wrapper to convert file-like objects to iterables""" 
  def __init__(self, filelike, blksize=8192):
    self.filelike = filelike
    self.blksize = blksize
    if hasattr(filelike,'close'):
      self.close = filelike.close 
  def __getitem__(self,key):
    data = self.filelike.read(self.blksize)
    if data:
      return data
    raise IndexError 
  def __iter__(self):
    return self 
  def next(self):
    data = self.filelike.read(self.blksize)
    if data:
      return data
    raise StopIteration

使用时:

from django.core.servers.basehttp import FileWrapper
from django.http import HttpResponse
import os
def file_download(request,filename):
 
  wrapper = FileWrapper(open(filename, 'rb'))
  response = HttpResponse(wrapper, content_type='application/octet-stream')
  response['Content-Length'] = os.path.getsize(path)
  response['Content-Disposition'] = 'attachment; filename=%s' % filename
  return response

django也提供了StreamingHttpResponse类来代替HttpResponse对流数据进行处理。

压缩为zip文件下载:

import os, tempfile, zipfile 
from django.http import HttpResponse 
from django.core.servers.basehttp import FileWrapper 
def send_zipfile(request): 
  """                                     
  Create a ZIP file on disk and transmit it in chunks of 8KB,         
  without loading the whole file into memory. A similar approach can     
  be used for large dynamic PDF files.                    
  """ 
  temp = tempfile.TemporaryFile() 
  archive = zipfile.ZipFile(temp, 'w', zipfile.ZIP_DEFLATED) 
  for index in range(10): 
    filename = __file__ # Select your files here.              
    archive.write(filename, 'file%d.txt' % index) 
  archive.close() 
  wrapper = FileWrapper(temp) 
  response = HttpResponse(wrapper, content_type='application/zip') 
  response['Content-Disposition'] = 'attachment; filename=test.zip' 
  response['Content-Length'] = temp.tell() 
  temp.seek(0) 
  return response

不过不管怎么样,使用django来处理大文件下载都不是一个很好的注意,最好的办法是django做权限判断,然后让静态服务器处理下载。

这需要使用sendfile的机制:"传统的Web服务器在处理文件下载的时候,总是先读入文件内容到应用程序内存,然后再把内存当中的内容发送给客户端浏览器。这种方式在应付当今大负载网站会消耗更多的服务器资源。sendfile是现代操作系统支持的一种高性能网络IO方式,操作系统内核的sendfile调用可以将文件内容直接推送到网卡的buffer当中,从而避免了Web服务器读写文件的开销,实现了“零拷贝”模式。 "

Apache服务器里需要mod_xsendfile模块来实现,而Nginx是通过称为X-Accel-Redirect的特性来实现。

nginx配置文件:

# Will serve /var/www/files/myfile.tar.gz
# When passed URI /protected_files/myfile.tar.gz
location /protected_files {
  internal;
  alias /var/www/files;
}

或者

# Will serve /var/www/protected_files/myfile.tar.gz
# When passed URI /protected_files/myfile.tar.gz
location /protected_files {
  internal;
  root /var/www;
}

注意alias和root的区别。

django中:

response['X-Accel-Redirect']='/protected_files/%s'%filename

这样当向django view函数发起request时,django负责对用户权限进行判断或者做些其它事情,然后向nginx转发url为/protected_files/filename的请求,nginx服务器负责文件/var/www/protected_files/filename的下载:

@login_required
def document_view(request, document_id):
  book = Book.objects.get(id=document_id)
  response = HttpResponse()
  name=book.myBook.name.split('/')[-1]
  response['Content_Type']='application/octet-stream'
  response["Content-Disposition"] = "attachment; filename={0}".format(
      name.encode('utf-8'))
  response['Content-Length'] = os.path.getsize(book.myBook.path)
  response['X-Accel-Redirect'] = "/protected/{0}".format(book.myBook.name)
  return response

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python爬虫入门教程之点点美女图片爬虫代码分享
Sep 02 Python
Python操作MongoDB数据库PyMongo库使用方法
Apr 27 Python
深入讲解Python函数中参数的使用及默认参数的陷阱
Mar 13 Python
Python实现PS滤镜特效Marble Filter玻璃条纹扭曲效果示例
Jan 29 Python
python实现微信自动回复功能
Apr 11 Python
Python利用ORM控制MongoDB(MongoEngine)的步骤全纪录
Sep 13 Python
在python中使用requests 模拟浏览器发送请求数据的方法
Dec 26 Python
Python 2/3下处理cjk编码的zip文件的方法
Apr 26 Python
找Python安装目录,设置环境路径以及在命令行运行python脚本实例
Mar 09 Python
python使用梯度下降和牛顿法寻找Rosenbrock函数最小值实例
Apr 02 Python
使用python检查yaml配置文件是否符合要求
Apr 09 Python
关于python3 opencv 图像二值化的问题(cv2.adaptiveThreshold函数)
Apr 04 Python
python爬虫刷访问量 2019 7月
Aug 01 #Python
用Cython加速Python到“起飞”(推荐)
Aug 01 #Python
Python爬取视频(其实是一篇福利)过程解析
Aug 01 #Python
flask框架jinja2模板与模板继承实例分析
Aug 01 #Python
Win10环境python3.7安装dlib模块趟过的坑
Aug 01 #Python
python爬虫解决验证码的思路及示例
Aug 01 #Python
Django多数据库的实现过程详解
Aug 01 #Python
You might like
基础的WordPress插件制作教程
2015/11/24 PHP
PHP实现简易计算器功能
2020/08/28 PHP
tp5.0框架隐藏index.php入口文件及模块和控制器的方法分析
2020/02/11 PHP
php实现的证件照换底色功能示例【人像抠图/换背景图】
2020/05/29 PHP
实现变速回到顶部的JavaScript代码
2011/05/09 Javascript
jquery easyui combox一些实用的小方法
2013/12/25 Javascript
使用js判断当前时区TimeZone是否是夏令时
2014/02/23 Javascript
基于promise.js实现nodejs的promises库
2014/07/06 NodeJs
php利用curl获取远程图片实现方法
2015/10/26 Javascript
Bootstrap每天必学之响应式导航、轮播图
2016/04/25 Javascript
深入理解$.each和$(selector).each
2016/05/15 Javascript
js实现统计字符串中特定字符出现个数的方法
2016/08/02 Javascript
详解XMLHttpRequest(二)响应属性、二进制数据、监测上传下载进度
2016/09/14 Javascript
bootstrap table小案例
2016/10/21 Javascript
socket.io实现在线群聊功能
2017/04/07 Javascript
JavaScript上传文件时不用刷新页面方法总结(推荐)
2017/08/15 Javascript
详解VueJS应用中管理用户权限
2018/02/02 Javascript
npm 更改默认全局路径以及国内镜像的方法
2018/05/16 Javascript
vue-cli项目修改文件热重载失效的解决方法
2018/09/19 Javascript
web页面和微信小程序页面实现瀑布流效果
2018/09/26 Javascript
深入浅析Node.js 事件循环、定时器和process.nextTick()
2018/10/22 Javascript
微信小程序tabBar设置实例解析
2019/11/14 Javascript
TypeScript魔法堂之枚举的超实用手册
2020/10/29 Javascript
vue.js实现点击图标放大离开时缩小的代码
2021/01/27 Vue.js
python开发准备工作之配置虚拟环境(非常重要)
2019/02/11 Python
pandas-resample按时间聚合实例
2019/12/27 Python
详解selenium + chromedriver 被反爬的解决方法
2020/10/28 Python
html5视频媒体标签video的使用方法及完整参数说明详解
2019/09/27 HTML / CSS
澳大利亚自然和有机的健康美容产品一站式商店:Ziani Beauty
2017/12/28 全球购物
Fenty Beauty官网:蕾哈娜创立的美妆品牌
2021/01/07 全球购物
应届毕业生求职信范文
2013/12/18 职场文书
心理健康教育心得体会
2013/12/29 职场文书
泰坦尼克号观后感
2015/06/04 职场文书
高三毕业感言
2015/07/30 职场文书
队名及霸气口号大全
2015/12/25 职场文书
2019垃圾分类宣传口号汇总
2019/08/16 职场文书