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 相关文章推荐
Linux环境下MySQL-python安装过程分享
Feb 02 Python
Python和GO语言实现的消息摘要算法示例
Mar 10 Python
Python获取当前路径实现代码
May 08 Python
Python3.6安装及引入Requests库的实现方法
Jan 24 Python
Python安装图文教程 Pycharm安装教程
Mar 27 Python
python实现逆序输出一个数字的示例讲解
Jun 25 Python
Python实现的txt文件去重功能示例
Jul 07 Python
Django框架用户注销功能实现方法分析
May 28 Python
python之生产者消费者模型实现详解
Jul 27 Python
Python爬虫使用浏览器cookies:browsercookie过程解析
Oct 22 Python
Python魔法方法 容器部方法详解
Jan 02 Python
一文读懂Python 枚举
Aug 25 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
php+xml实现在线英文词典之添加词条的方法
2015/01/23 PHP
js 面向对象的技术创建高级 Web 应用程序
2010/02/25 Javascript
基于jQuery的360图片展示实现代码
2012/06/14 Javascript
JavaScript跨域方法汇总
2014/10/16 Javascript
JS+CSS实现的经典tab选项卡效果代码
2015/09/16 Javascript
Node.js操作mysql数据库增删改查
2016/03/30 Javascript
jQuery基本选择器(实例及表单域value的获取方法)
2016/05/20 Javascript
js拼接html字符串的注意事项
2016/10/13 Javascript
表单元素值获取方式js及java方式的简单实例
2016/10/15 Javascript
微信小程序组件 marquee实例详解
2017/06/23 Javascript
解决JQuery全选/反选第二次失效的问题
2017/10/11 jQuery
Vue-router路由判断页面未登录跳转到登录页面的实例
2017/10/26 Javascript
在angular 6中使用 less 的实例代码
2018/05/13 Javascript
使用layer弹窗提交表单时判断表单是否输入为空的例子
2019/09/26 Javascript
使用vue实现通过变量动态拼接url
2020/07/22 Javascript
vue中解决chrome浏览器自动播放音频和MP3语音打包到线上的实现方法
2020/10/09 Javascript
使用Python制作自动推送微信消息提醒的备忘录功能
2018/09/06 Python
python格式化输出保留2位小数的实现方法
2019/07/02 Python
python自动化unittest yaml使用过程解析
2020/02/03 Python
python GUI库图形界面开发之PyQt5控件QTableWidget详细使用方法与属性
2020/02/25 Python
Django自定义列表 models字段显示方式
2020/04/03 Python
python 写一个文件分发小程序
2020/12/05 Python
Python的信号库Blinker用法详解
2020/12/31 Python
详解pandas apply 并行处理的几种方法
2021/02/24 Python
英国排名第一的餐具品牌:Denby Pottery
2019/11/01 全球购物
九年级家长会邀请函
2014/01/15 职场文书
2014年酒店年度工作总结
2014/12/10 职场文书
幼儿园教师个人总结
2015/02/05 职场文书
2015年信息宣传工作总结
2015/05/26 职场文书
2015年暑期实践报告范文
2015/07/13 职场文书
网络舆情信息简报
2015/07/21 职场文书
2016年员工政治思想表现评语
2015/12/02 职场文书
中秋节作文(五年级)之关于月亮
2019/09/11 职场文书
解决mysql问题:由于找不到MSVCR120.dll,无法继续执行代码
2021/06/26 MySQL
tomcat的catalina.out日志按自定义时间格式进行分割的操作方法
2022/04/02 Servers
使用JS前端技术实现静态图片局部流动效果
2022/08/05 Javascript