Django 大文件下載

django提供文件下載時,若果文件較小,解決辦法是先將要傳送的內容全生成在內存中,而後再一次性傳入Response對象中:python

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

若是文件很是大時,最簡單的辦法就是使用靜態文件服務器,好比Apache或者Nginx服務器來處理下載。不過有時候,咱們須要對用戶的權限作一下限定,或者不想向用戶暴露文件的真實地址,或者這個大內容是臨時生成的(好比臨時將多個文件合併而成的),這時就不能使用靜態文件服務器了。nginx

django文檔中提到,能夠向HttpResponse傳遞一個迭代器,流式的向客戶端傳遞數據。django

要本身寫迭代器的話,能夠用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

使用時:app

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
相關文章
相關標籤/搜索