1. 实现方式

功能基于django.views.static.serve实现,实现的关键点是:

        a. response中增加'Content-Range'、'Cache-Control'的参数

        b. 根据不同的情况为response设置不同的status

        c. 根据HTTP_RANGE对读取文件时的起始位置进行设置

2. 视图函数

文件位置:myproject/views_file.py

import re
import os
import stat
import mimetypes
import posixpath
from django.utils._os import safe_join
from django.utils.http import http_date
from django.views.static import was_modified_since
from django.http import Http404, FileResponse, HttpResponseNotModified


# 基于django.views.static.serve实现,支持大文件的断点续传(暂停/继续下载)
def get_file_response(request, path, document_root=None):
    # 防止目录遍历漏洞
    path = posixpath.normpath(path).lstrip('/')
    fullpath = safe_join(document_root, path)
    if os.path.isdir(fullpath):
        raise Http404('Directory indexes are not allowed here.')
    if not os.path.exists(fullpath):
        raise Http404('"%(path)s" does not exist' % {'path': fullpath})

    statobj = os.stat(fullpath)

    # 判断下载过程中文件是否被修改过
    if not was_modified_since(request.META.get('HTTP_IF_MODIFIED_SINCE'),
                              statobj.st_mtime, statobj.st_size):
        return HttpResponseNotModified()

    # 获取文件的content_type
    content_type, encoding = mimetypes.guess_type(fullpath)
    content_type = content_type or 'application/octet-stream'

    # 计算读取文件的起始位置
    start_bytes = re.search(r'bytes=(\d+)-', request.META.get('HTTP_RANGE', ''), re.S)
    start_bytes = int(start_bytes.group(1)) if start_bytes else 0

    # 打开文件并移动下标到起始位置,客户端点击继续下载时,从上次断开的点继续读取
    the_file = open(fullpath, 'rb')
    the_file.seek(start_bytes, os.SEEK_SET)

    # status=200表示下载开始,status=206表示下载暂停后继续,为了兼容火狐浏览器而区分两种状态
    # 关于django的response对象,参考:https://www.cnblogs.com/scolia/p/5635546.html
    # 关于response的状态码,参考:https://www.cnblogs.com/DeasonGuan/articles/Hanami.html
    # FileResponse默认block_size = 4096,因此迭代器每次读取4KB数据
    response = FileResponse(the_file, content_type=content_type, status=206 if start_bytes > 0 else 200)

    # 'Last-Modified'表示文件修改时间,与'HTTP_IF_MODIFIED_SINCE'对应使用,参考:https://www.jianshu.com/p/b4ecca41bbff
    response['Last-Modified'] = http_date(statobj.st_mtime)

    # 这里'Content-Length'表示剩余待传输的文件字节长度
    if stat.S_ISREG(statobj.st_mode):
        response['Content-Length'] = statobj.st_size - start_bytes
    if encoding:
        response['Content-Encoding'] = encoding

    # 'Content-Range'的'/'之前描述响应覆盖的文件字节范围,起始下标为0,'/'之后描述整个文件长度,与'HTTP_RANGE'对应使用
    # 参考:http://liqwei.com/network/protocol/2011/886.shtml
    response['Content-Range'] = 'bytes %s-%s/%s' % (start_bytes, statobj.st_size - 1, statobj.st_size)

    # 'Cache-Control'控制浏览器缓存行为,此处禁止浏览器缓存,参考:https://blog.csdn.net/cominglately/article/details/77685214
    response['Cache-Control'] = 'no-cache, no-store, must-revalidate'
    return response

3. 路由配置

文件位置:myproject/urls.py


from django.urls import re_path
from django.conf import settings
from myproject import views_file

# MEDIA_ROOT是要下载的文件的存储路径的前半段,下面的配置中'files/.*'匹配到的路径则是后半段,两者合并就是要下载的文件的完整路径
urlpatterns = [re_path(r'^download/(files/.*)$', views_file.get_file_response, {'document_root': settings.MEDIA_ROOT})]

 

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐