在Django中服务大型文件(高负载)

39 投票
6 回答
35509 浏览
提问于 2025-04-17 08:50

我之前用了一种方法来提供文件下载,但因为这个方法不安全,所以我决定换一种方式。(之前的方法是直接给出存储中原始文件的链接,但这样一来,任何有链接的人都可以下载这个文件!)所以现在我通过我的视图来提供文件,这样只有有权限的用户才能下载文件。不过,我注意到当有很多人同时请求下载文件时,服务器的负担很重。以下是我处理用户下载的部分代码(假设文件是一个图片)

    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  

有没有更好的方法可以在保证安全的同时,减少服务器的负担呢?提前谢谢大家 :)

6 个回答

5

使用FileResponse会更好,它是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
15

你可以使用'sendfile'这个方法,具体的做法可以参考这个回答

实际上,你需要这样做(复制粘贴):

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_xsendfile,这个功能也被nginxlighty支持。

69

你打开图片的时候,它会被加载到内存里,这就是在大量使用时导致负载增加的原因。正如马丁所说,真正的解决办法是直接提供文件。

这里还有另一种方法,可以分块传输你的文件,而不需要把它全部加载到内存里。

import os
import mimetypes

from wsgiref.util import FileWrapper

from django.http import StreamingHttpResponse


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"] = f"attachment; filename={filename}"
    return response

撰写回答