在Django中提供大文件(高负载)

时间:2011-12-22 07:52:19

标签: python django permissions download

我一直在使用一种方法来提供下载,但由于它不安全,我决定改变它。 (该方法是存储中原始文件的链接,但风险是每个有链接的人都可以下载文件!)所以我现在通过我的视图提供文件,这样只有有权限的用户才能下载文件,但我注意到服务器上的负载很高,同时有很多文件的同时下载请求。这是我处理用户下载的代码的一部分(考虑文件是图像)

    image = Image.open ("the path to file")
    response = HttpResponse(mimetype = 'image/png' )
    response['Content-Disposition'] = 'attachment: filename=%s.png' % filename
    image.save(response , "png")
    return response  

有没有更好的方法来提供文件,同时保持安全性并降低服务器端负载? 提前谢谢:)

5 个答案:

答案 0 :(得分:54)

您打开图像会将其加载到内存中,这是导致大量使用时负载增加的原因。由Martin发布,真正的解决方案是直接提供文件。

这是另一种方法,它将以块的形式传输文件而不将其加载到内存中。

import os
import mimetypes
from django.http import StreamingHttpResponse
from django.core.servers.basehttp import FileWrapper


def download_file(request):
   the_file = '/some/file/name.png'
   filename = os.path.basename(the_file)
   chunk_size = 8192
   response = StreamingHttpResponse(FileWrapper(open(the_file, 'rb'), chunk_size),
                           content_type=mimetypes.guess_type(the_file)[0])
   response['Content-Length'] = os.path.getsize(the_file)    
   response['Content-Disposition'] = "attachment; filename=%s" % filename
   return response

答案 1 :(得分:14)

您可以使用此answer中所述的'sendfile'方法。

实际上你需要这个(c& p):

response = HttpResponse(mimetype='application/force-download')
response['Content-Disposition'] = 'attachment; filename=%s' % smart_str(file_name)
response['X-Sendfile'] = smart_str(path_to_file)
# It's usually a good idea to set the 'Content-Length' header too.
# You can also set any other required headers: Cache-Control, etc.
return response

这需要mod_xsendfilenginxlighty也支持<)

答案 2 :(得分:3)

安装GZipMiddleware时,FileWrapper将无法工作(Django 1.4及以下版本): https://code.djangoproject.com/ticket/6027

如果使用GZipMiddleware,一个实用的解决方案是编写FileWrapper的子类,如下所示:

from wsgiref.util import FileWrapper
class FixedFileWrapper(FileWrapper):
    def __iter__(self):
        self.filelike.seek(0)
        return self

import mimetypes, os
my_file = '/some/path/xy.ext'
response = HttpResponse(FixedFileWrapper(open(my_file, 'rb')), content_type=mimetypes.guess_type(my_file)[0])
response['Content-Length'] = os.path.getsize(my_file)
response['Content-Disposition'] = "attachment; filename=%s" % os.path.basename(my_file)
return response

从Python 2.5开始,不需要从Django导入FileWrapper。

答案 3 :(得分:2)

除非您要提供非常少量的此类请求,否则任何需要通过django提供内容的解决方案都不具备可扩展性。对于将来可扩展的任何内容,您可能希望将内容存储和服务转移到单独的服务器,然后这将无法正常工作。

推荐的方法是保持通过较轻的服务器(例如nginx)提供的静态内容。要添加安全性,请通过设置cookie或通过get参数从django向静态服务器传递令牌。

令牌应具有以下值:timestamp,filename,userid。它应该通过django应用程序通过一些密钥签名。

接下来,编写一个小的nginx模块,用于检查令牌并确保用户确实可以访问该文件。它还应通过检查时间戳来检查令牌是否足够老。

答案 4 :(得分:2)

使用FileRespose更好,是针对二进制文件优化的StreamingHttpResponse的子类。如果由wsgi服务器提供,它使用wsgi.file_wrapper,否则它将文件以小块的形式流出。

import os
from django.http import FileResponse
from django.core.servers.basehttp import FileWrapper


def download_file(request):
    _file = '/folder/my_file.zip'
    filename = os.path.basename(_file)
    response = FileResponse(FileWrapper(file(filename, 'rb')), content_type='application/x-zip-compressed')
    response['Content-Disposition'] = "attachment; filename=%s" % _file
    return response