目录

    1. 直接返回文件

    如果静态文件在工程根目录的 media/test.zip,需要先将文件读入内存,再进行传输。代码如下:

    settings.py 配置

    PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
    MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'media/')
    

    yourapp/views.py

    from django.conf import settings
    from django.http import HttpResponse
    from django.core.files.storage import FileSystemStorage
    
    def download_file_direct_from_file(request):
      file_system = FileSystemStorage(settings.MEDIA_ROOT)
      file_name = 'test.zip'
      with file_system.open(file_name) as f:
          response = HttpResponse(f)
          response['Content-Type'] = 'application/%s' % file_name.split('.')[-1]
          response['Content-Disposition'] = 'attachment; filename="%s"'% file_name
      return response
    

    如果是从 API 调用,第三方获取的文件,那么只需要将 with file_system.open(file_name) as f 中的变量,f,赋值为请求到的文件体即可,如:f = requests.get(url).content。下面的代码示例,就不一一说明了,仅以下载本地文件为例。

    2. 返回流式的文件

    Django 的 HttpResponse 对象支持以迭代器作为初始化参数,将文件对象替换为一个迭代器或生成器,可以优化 Django 对大文件的处理。同时,Django 提供了 StreamingHttpResponse 对象取代 HttpResponse 对象,以支持以流的形式发送文件给浏览器。

    可以自己实现一个迭代器

    from django.conf import settings
    from django.http import StreamingHttpResponse
    
    def download_file_direct(request):
        file_name = 'test.zip'
        full_path_file_name = ''.join([settings.MEDIA_ROOT, file_name])
    
        def file_read(full_path_name, chunk_size=512):
            with open(full_path_name, 'rb') as f:
                while True:
                    chunks = f.read(chunk_size)
                    if chunks:
                        yield chunks
                    else:
                        break
    
        response = StreamingHttpResponse(file_read(full_path_file_name))
        response['Content-Type'] = 'application/octet-stream'
        response['Content-Disposition'] = 'attachment;filename=%s' % (file_name)
        return response
    

    也可以使用 Django 提供的 FileWrapper 类来迭代器化文件对象。

    from django.conf import settings
    from django.core.files.storage import FileSystemStorage
    from django.core.servers.basehttp import FileWrapper
    from django.http import StreamingHttpResponse
    
    def download_file_chunk_from_file(request):
        file_system = FileSystemStorage(settings.MEDIA_ROOT)
        file_name = 'test.zip'
        response = StreamingHttpResponse(FileWrapper(file_system.open(file_name, 'rb'), 512))
        response['Content_Type'] = 'application/octet-stream'
        response['Content-Disposition'] = 'attachment; filename=%s' % file_name
        return response
    

    3. 利用 sendfile 机制

    Django 处理文件时,需要将文件内容读入到内存,再将内存中的内容发送给浏览器。Django 对文件的转发处理能力远比不上 Nginx。比较好的方法是,使用 Django 做权限判断,然后设置 Nginx 转发文件。

    sendfile 是一种高性能网络 IO 的方式,利用操作系统内核的 sendfile 调用,可以将文件内容直接推送到网卡的 buffer,避免了应用层读写文件的开销。而在 Nginx 中,可以通过 X-Accel-Redirect 的特性来实现 sendfile 机制的文件处理。

    Nginx 配置

    location /media {
        internal;
        alias /var/www/my_django_project_root/media;
    }
    

    views.py

    from django.conf import settings
    from django.http import HttpResponse
    
    def download_file_from_nginx(request):
        file_name = 'test.zip'
        response['Content_Type']='application/octet-stream'
        response['Content-Disposition'] = 'attachment;filename=%s' % (file_name)
        response['X-Accel-Redirect'] = '/media/%s' % file_name
        return response
    

    4. 参考