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中的try和finally和with方法
May 05 Python
Python实现的堆排序算法原理与用法实例分析
Nov 22 Python
Python使用OpenCV进行标定
May 08 Python
Python Gitlab Api 使用方法
Aug 28 Python
阿里云ECS服务器部署django的方法
Aug 29 Python
python中的RSA加密与解密实例解析
Nov 18 Python
python线程信号量semaphore使用解析
Nov 30 Python
python在不同条件下的输入与输出
Feb 13 Python
numpy库ndarray多维数组的维度变换方法(reshape、resize、swapaxes、flatten)
Apr 28 Python
python 两种方法修改文件的创建时间、修改时间、访问时间
Sep 26 Python
python解包用法详解
Feb 17 Python
实操Python爬取觅知网素材图片示例
Nov 27 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上传文件中文文件名乱码的解决方法
2013/11/01 PHP
[原创]php使用strpos判断字符串中数字类型子字符串出错的解决方法
2017/04/01 PHP
php curl发送请求实例方法
2019/08/01 PHP
javascript学习笔记(十九) 节点的操作实现代码
2012/06/20 Javascript
js实现单一html页面两套css切换代码
2013/04/11 Javascript
基于Unit PNG Fix.js有时候在ie6下不正常的解决办法
2013/06/26 Javascript
JavaScript/Js脚本处理html元素的自定义属性解析(亲测兼容Firefox与IE)
2013/11/25 Javascript
浅析IE10兼容性问题(frameset的cols属性)
2014/01/03 Javascript
ExtJS4给Combobox设置列表中的默认值示例
2014/05/02 Javascript
浅析基于WEB前端页面的页面内容搜索的实现思路
2014/06/10 Javascript
《JavaScript DOM 编程艺术》读书笔记之DOM基础
2015/01/09 Javascript
js闭包实现按秒计数
2015/04/23 Javascript
js限制文本框只能输入整数或者带小数点的数字
2015/04/27 Javascript
使用Angular和Nodejs、socket.io搭建聊天室及多人聊天室
2015/08/21 NodeJs
JS在一定时间内跳转页面及各种刷新页面的实现方法
2016/05/26 Javascript
Javascript的比较汇总
2016/07/25 Javascript
jQuery对checkbox 复选框的全选全不选反选的操作
2016/08/09 Javascript
微信小程序 数据访问实例详解
2016/10/08 Javascript
bootstrap学习使用(导航条、下拉菜单、轮播、栅格布局等)
2016/12/01 Javascript
简单实现js悬浮导航效果
2017/02/05 Javascript
protractor的安装与基本使用教程
2017/07/07 Javascript
jQuery.Ajax()的data参数类型详解
2017/07/23 jQuery
Javascript中this关键字指向问题的测试与详解
2017/08/11 Javascript
vue select二级联动第二级默认选中第一个option值的实例
2018/01/10 Javascript
vue使用Font Awesome的方法步骤
2019/02/26 Javascript
详解vue 自定义marquee无缝滚动组件
2019/04/09 Javascript
微信浏览器左上角返回按钮监听的实现
2020/03/04 Javascript
ant-design-vue中的select选择器,对输入值的进行筛选操作
2020/10/24 Javascript
[02:37]TI8勇士令状不朽珍藏II视频展示
2018/06/23 DOTA
python套接字流重定向实例汇总
2016/03/03 Python
python WindowsError的错误代码详解
2017/07/23 Python
html5 canvas绘制矩形和圆形的实例代码
2016/06/16 HTML / CSS
Ruby中的保护方法和私有方法与一般面向对象程序设计语言的一样吗
2013/05/01 面试题
2014年街道办事处工作总结
2014/12/11 职场文书
幼儿园教师教学反思
2016/03/02 职场文书
python 闭包函数详细介绍
2022/04/19 Python