Please enable Javascript to view the contents

Django 大文件传输

 ·  ☕ 2 分钟

1. 直接返回文件

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

settings.py 配置

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

yourapp/views.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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 对象,以支持以流的形式发送文件给浏览器。

可以自己实现一个迭代器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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 类来迭代器化文件对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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 配置

1
2
3
4
location /media {
    internal;
    alias /var/www/my_django_project_root/media;
}

views.py

1
2
3
4
5
6
7
8
9
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. 参考


微信公众号
作者
微信公众号