Python Django断点下载(服务端/客户端)

断点下载

背景

    断点续传/断点下载一直是每个系统最实用的功能,最近公司在复杂的网络环境(国外vps)下载东西遇到问题,有些文件下载的时候很慢,并且可能会下不下来,这种情况对一个系统的稳定性构成很大的威胁,所以必须要采用断点下载,并且需要自定义设置下载大小。
    然而网上都是关于客户端下载的方法,没有关于服务端的实现方法。根据网络环境的复杂及网速设计了一个可以自定义下载大小的服务端/客户端代码。

原理

利用请求头和响应头
Range是一个HTTP请求头,告知服务器要返回文件的哪一部分,即:哪个区间范围(字节)的数据,在 Range 中,可以一次性请求多个部分,服务器会以 multipart 文件的形式将其返回。如果服务器返回的是范围响应,需要使用 206 Partial Content 状态码。假如所请求的范围不合法,那么服务器会返回 416 Range Not Satisfiable 状态码,表示客户端错误。服务器允许忽略 Range 头,从而返回整个文件,状态码用 200 。
当你正在看大片时,网络断了,你需要继续看的时候,文件服务器不支持断点的话,则你需要重新等待下载这个大片,才能继续观看。而Range支持的话,客户端就会记录了之前已经看过的视频文件范围,网络恢复之后,则向服务器发送读取剩余Range的请求,服务端只需要发送客户端请求的那部分内容,而不用整个视频文件发送回客户端,以此节省网络带宽。
Range 格式
Range: bytes=0-32768

交互过程

客户端请求下载时会带上Range头。Range 表示下载的范围区间,0-1024就是下载开始的1024bytes, 1024-就是从1024开始到结束,1024-32768就是下载1024-32768之间的bytes。
服务端会根据HTTP_RANGE来获取客户端需要的文件大小和文件大小的范围,然后以Content-Range=0-1024/32768来返回,/之后代表的是整个文件的大小,之前代表的是当前的文件区间内容大小

废话不多说,上代码

代码实现

Server 端代码如下

import stat

from django.http import FileResponse, JsonResponse

import os
import re
import posixpath
import mimetypes
from django.utils._os import safe_join


# Create your views here.


def down_file_iterator(file_path, start_pos, chunk_size):
    """
    文件生成器,防止文件过大,导致内存溢出
    :param file_path: 文件绝对路径
    :param start_pos: 文件读取的起始位置
    :param chunk_size: 文件读取的块大小
    :return: yield
    """
    with open(file_path, mode='rb') as f:
        f.seek(start_pos, os.SEEK_SET)
        content = f.read(chunk_size)
        yield content


def breakpoint_download(request, filename):
    """
    断点下载
    :param request: request
    :param filename: 文件名
    :return: response
    """
    # 防止目录遍历漏洞
    path = posixpath.normpath(filename).lstrip('/')
    # 拼接文件路径 document_root: 文件根路径(自己设置,例如:/home/files/)
    full_path = safe_join(document_root, path)
    if os.path.exists(full_path):
        stat_obj = os.stat(full_path)
        # 获取文件类型
        content_type, encoding = mimetypes.guess_type(full_path)
        content_type = content_type or 'application/octet-stream'
        # 计算读取文件的起始位置
        start_bytes = re.search(r'bytes=(\d+)-(\d+)', request.META.get('HTTP_RANGE', ''), re.S)
        # 重新计算文件起始位置,切分bytes=0-32768(bytes=temp_size-pos)
        pos = start_bytes.group().split('=')[1] if start_bytes else 0
        # 应该取temp_size为起始位置,不然始终差了32k,导致客户端一直下载不完
        start_bytes = int(pos.split('-')[0]) if pos else 0
        current_pos = int(pos.split('-')[1]) if pos else 0
        
        # 打开文件并移动下标到七十五i之,客户端继续下载时,从上次断开的点继续读取
        the_file = open(full_path, 'rb')
        the_file.seek(start_bytes, os.SEEK_SET)
        # status=200表示下载开始,status=206表示下载暂停后继续,为了兼容火狐浏览器而区分两种状态
        response = FileResponse(the_file, content_type=content_type, status=206 if start_bytes > 0 else 200)
        
        # 修改文件生成器返回指定大小
        content_length = current_pos - start_bytes
        response = FileResponse(down_file_iterator(full_path, start_bytes, content_length), content_type=content_type, 
                                status=206 if start_bytes > 0 else 200)
        # 这里的‘Content-Length’表示剩余待传输的文件字节长度
        if stat.S_ISREG(stat_obj.st_mode):
            # 计算每一包的content-length,如果返回指定大小,每包只能下载指定大小,根据客户端的请求返回指定大小
            response['Content-Length'] = content_length
        if encoding:
            response['Content-Encoding'] = encoding
        # ‘Content-Range’的'/'之前描述响应覆盖的文件字节范围,起始下标为0,'/'之后描述整个文件长度,与'HTTP_RANGE'对应使用
        response['Content-Range'] = f'bytes {start_bytes}-{stat_obj.st_size-1}/{stat_obj.st_size}'
        return response
    else:
        return JsonResponse({'code': 404, 'msg': 'File Not Found'})
        

client 端代码如下:

import os
import sys

import requests


def download_file(url, file_path, file_size):
    """
    请求url将文件保存至file_path
    :param url: 下载路径
    :param file_path: 文件保存路径
    :param file_size: 文件大小(也可以用head或者get去获取大小)
    :return:
    """
    if os.path.exists(file_path):
        os.remove(file_path)

    sys.stdout.write("\r[%s%s]%d%%" % ('█' * 0, '' * 50, 0))
    sys.stdout.flush()
    total_size = int(file_size)
    temp_size = 0

    while True:
        # 请求网址,加入请求头
        if (total_size - temp_size) < 32 * 1024:
            pos = total_size
        else:
        	# 每包下载32k这个可以根据自己的网络环境来设置
            pos = temp_size + 32 * 1024
        headers = {'Range': f'bytes={temp_size}-{pos}'}
        response = requests.get(url, stream=True, verify=False, headers=headers)

        if response.status_code == 200 or response.status_code == 206:
            with open(file_path, 'ab') as f:
                f.write(response.content)
        # 终端显示进度
        temp_size = os.path.getsize(file_path) if os.path.exists(file_path) else 0
        done = int(50 * temp_size / total_size)
        sys.stdout.write("\r[%s%s]%d%%" % ('█' * done, '' * (50 - done), 100 * temp_size / total_size))
        sys.stdout.flush()
        # 下载完成退出
        if temp_size == total_size:
            break

    return file_path

下载测试文件

在这里插入图片描述

测试结果:
下载后暂停继续下载成功,每包下载32k这个可以根据自己的网络环境来设置

在这里插入图片描述

在这里插入图片描述

下载保存路径

在这里插入图片描述

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: Python 本身不会改变服务端接收客户端信息的方式或机制。Python 作为一种编程语言,可以用于编写服务器端的应用程序,同时也可以编写客户端的应用程序。在服务器端,Python 通常使用网络编程库(如 Socket、Twisted、Tornado 等)来监听客户端的连接请求,并接收客户端发送的数据。Python 本身并不会改变服务器端接收客户端信息的方式,而是通过网络编程库提供的接口来实现相应的功能。因此,Python 可以作为一种非常灵活和强大的工具来开发各种类型的网络应用程序。 ### 回答2: Python的socket模块提供了一种实现网络通信的方式,可以在客户端服务端之间进行信息的传输。通常情况下,客户端发送请求并等待服务端的响应,服务端接收请求,处理请求并发送响应给客户端。 在这个过程中,Python作为一种脚本语言,可以被用作客户端或者服务端的编程语言。但是无论是客户端还是服务端Python都是通过socket进行通信,它本身并不会改变服务端接收客户端信息的方式。 具体来说,当客户端发送信息给服务端时,Python作为客户端的一部分,使用socket发送请求信息给服务端服务端使用Python的socket模块监听请求,并接收客户端发送过来的信息。服务端可以通过读取socket对象中的数据,获得客户端发送的信息。 当服务端接收到客户端的信息后,可以对信息进行处理,并根据客户端的请求作出相应的响应。服务端可以使用Python的socket模块发送响应信息给客户端,响应信息会通过socket对象发送到客户端。 总结来说,Python作为一种编程语言可以用于客户端服务端的开发,但它本身并不会直接改变服务端接收客户端信息的方式。而是通过socket模块提供的函数和方法,实现客户端服务端之间的通信和信息的传输。 ### 回答3: Python 是一种非常流行的编程语言,广泛用于网络编程。Python 可以作为客户端服务端使用,具有很强的网络传输能力。在客户端服务端发送信息时,Python 提供了许多方法和库,如 socket 模块,可以通过建立 TCP 或 UDP 连接与服务端进行通信。 Python客户端发送请求时并不会直接改变服务端接收的信息。当客户端发送请求到服务端时,请求会被服务端接收并进行处理。服务端可以根据收到的请求进行相应的操作,并将处理结果返回给客户端Python 提供了丰富的网络编程工具,可以方便地与服务端进行通信。 在客户端发送信息时,Python 可以使用 socket 模块中的方法来建立网络连接,并根据需要发送具体的信息给服务端服务端接收到客户端发送的信息后,可以使用 Python 脚本进行解析和处理。Python 的网络编程库提供了很多功能,如建立连接、发送请求、接收响应、处理数据等。 服务端可以根据客户端发送的不同请求进行不同的处理,并将处理结果发送给客户端Python 提供了丰富的网络编程库和框架,如 Flask、Django 等,可以用于构建高效可靠的服务端应用程序。 因此,Python客户端发送信息时,并不会直接改变服务端接收的信息。客户端服务端通信的过程中,Python 提供了丰富的网络编程工具和库,可以方便地进行通信,实现客户端服务端之间的信息传输和处理。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值