PHP实现文件下载断点续传详解


Posted in PHP onOctober 15, 2014

如果我们的网站提供文件下载的服务,那么通常我们都希望下载可以断点续传(Resumable Download),也就是说用户可以暂停下载,并在未来的某个时间从暂停处继续下载,而不必重新下载整个文件。

通常情况下,Web服务器(如Apache)会默认开启对断点续传的支持。因此,如果直接通过Web服务器来提供文件的下载,可以不必做特别的配置,即可享受到断点续传的好处。由于这些文件直接通过Web服务器来提供下载,后端脚本无法对这个下载过程进行控制。这对于仅提供公开、静态文件的网站来说不是问题,但对于需要提供私有、动态文件的网站来说,直接通过Web服务器来提供下载就无法满足需求了。这时,就需要在编写后台脚本程序时,加入对断点续传的支持。

本文将以PHP为例,简要介绍实现文件下载断点续传的方法。

原理

断点续传的原理还是比较直观的。

HTTP协议规定了如何传输某个资源的一部分,而不是全部。比如,有一个文件的大小是1000字节,浏览器可以只请求该文件的前300个字节,或者只请求第500到第1000个字节。通过这种方式,就可以不必在一次请求中传输某个资源的全部内容,而是发起多次请求,每次仅请求其中的一部分内容。等所有这些请求都返回之后,再把得到的内容一块一块的拼接起来得到完整的资源。

实现断点续传就是要利用HTTP协议的上述特性。当用户暂停下载的时候,浏览器会记录已经下载到什么位置,当用户在未来某一时间恢复下载时,就可以从上次暂停的位置继续下载,而不必从头开始。

实现

由于部分传输不是强制的,服务器可以支持也可以不支持,所以,我们需要在程序中告诉浏览器,它请求的资源是否支持部分传输。这可以通过设置HTTP的 Accept-Ranges 响应头信息来实现。PHP代码如下:

header('Accept-Ranges: bytes');

Accept-Ranges: bytes 告诉浏览器,该资源支持以字节为单位的部分传输。这个响应头需要附加在支持部分传输的所有资源上。

当接受到一个请求时,我们需要从浏览器的请求中提取浏览器具体是在请求资源的哪一个部分。这个信息是通过 Range 请求头来传递的。在PHP中,它被存储在$_SERVER['HTTP_RANGE']中。我们需要检查这个变量是否定义了,如果定义了,则使用该值,否则,就将range设为整个资源。

$range = "0-". ($content_length-1);

if(isset($_SERVER['HTTP_RANGE'])){

    $range = $_SERVER['HTTP_RANGE'];

}

接下来,就需要分析 $range 的值,来决定返回资源的哪一部分内容。可能的取值示例:
100-200 // 第100到第200字节

500-    // 第500字节到文件末尾

-1000   // 最后的1000个字节

这里需要注意,得到一个Range之后,你需要对它的取值进行检验,包括:

1.开始位置非负
2.结束位置需要大于开始位置
3.开始位置需要小于文件长度减一 (因为这里的位置索引是从0开始的)
4.若结束位置大于文件长度减一,则需要把它的值设置为文件长度减一

如果Range的取值不合法,则需要终止程序并告知浏览器:

header('HTTP/1.1 416 Requested Range Not Satisfiable');

为了保持文章简洁,具体的校验代码这里就不提供了。下面假定你已经校验了Range的取值,并得到了 $start 和 $end 两个变量,分别表示开始位置和结束位置。

接下来要做的就是把文件的对应部分的内容发送给浏览器。不过要注意的是,这里涉及到需要发送多个HTTP响应头信息,具体如下:

header('HTTP/1.1 206 Partial Content');

header('Accept-Ranges: bytes');

header("Content-Range: bytes $start-$end/$filesize");

$length = $end - $start + 1;

header("Content-Length: $length");
/* 输出文件的指定部分 */

这里的$length需要注意一下,它的取值是本次传输的内容的长度,而不是整个文件的长度。另外需要注意的一点是,这里的HTTP状态码是206,不是200。

总结

文件下载的断点续传实际上是利用了HTTP协议中对传输部分文件的支持。而HTTP协议的这一特性不仅可以用于实现断点续传,客户端程序也可以利用它来实现多线程下载。

在实现断点续传的过程中,需要注意正确设置各种HTTP头信息。错误的头信息将导致用户下载到的文件损坏,无法使用。

PHP 相关文章推荐
PHP伪造referer实例代码
Sep 20 PHP
PHP 加密与解密的斗争
Apr 17 PHP
PHP var_dump遍历对象属性的函数与应用代码
Jun 04 PHP
基于flush()不能按顺序输出时的解决办法
Jun 29 PHP
PHP实现一维数组转二维数组的方法
Feb 25 PHP
php好代码风格的阶段性总结
Jun 25 PHP
PHP 极验验证码实例讲解
Sep 29 PHP
php与python实现的线程池多线程爬虫功能示例
Oct 12 PHP
PHP SFTP实现上传下载功能
Jul 26 PHP
PHP实现广度优先搜索算法(BFS,Broad First Search)详解
Sep 16 PHP
php unlink()函数使用教程
Jul 12 PHP
PHP使用PDO实现mysql防注入功能详解
Dec 20 PHP
PHP多进程编程实例
Oct 15 #PHP
PHP实现采集中国天气网未来7天天气
Oct 15 #PHP
跟我学Laravel之视图 & Response
Oct 15 #PHP
跟我学Laravel之请求与输入
Oct 15 #PHP
跟我学Laravel之路由
Oct 15 #PHP
跟我学Laravel之请求(Request)的生命周期
Oct 15 #PHP
跟我学Laravel之配置Laravel
Oct 15 #PHP
You might like
用PHP为SHOPEX增加日志功能代码
2010/07/02 PHP
php进程daemon化的正确实现方法
2018/09/06 PHP
PHP lcfirst()函数定义与用法
2019/03/08 PHP
laravel-admin 管理平台获取当前登陆用户信息的例子
2019/10/08 PHP
jQuery使用手册之一
2007/03/24 Javascript
各种效果的jquery ui(接口)介绍
2008/09/17 Javascript
jquery 子窗口操作父窗口的代码
2009/09/21 Javascript
用jQuery实现一些导航条切换,显示隐藏的实例代码
2013/06/08 Javascript
高效的获取当前元素是父元素的第几个子元素
2013/10/15 Javascript
js事件绑定快捷键以ctrl+k为例
2014/09/30 Javascript
浅谈jQuery为哪般去掉了浏览器检测
2016/08/29 Javascript
canvas绘制七巧板
2017/02/03 Javascript
Bootstrap 设置datetimepicker在屏幕上面弹出设置方法
2017/03/21 Javascript
jQuery中过滤器的基本用法示例
2017/10/11 jQuery
使用Node.js实现ORM的一种思路详解(图文)
2017/10/24 Javascript
使用vue-cli打包过程中的步骤以及问题的解决
2018/05/08 Javascript
Koa日志中间件封装开发详解
2019/03/09 Javascript
layUI实现三级导航菜单效果
2019/07/26 Javascript
js实现随机点名器精简版
2020/06/29 Javascript
javascript中导出与导入实现模块化管理教程
2020/12/03 Javascript
pytyon 带有重复的全排列
2013/08/13 Python
python爬取w3shcool的JQuery课程并且保存到本地
2017/04/06 Python
Python上下文管理器和with块详解
2017/09/09 Python
python实现键盘控制鼠标移动
2020/11/27 Python
Python数据结构之栈、队列及二叉树定义与用法浅析
2018/12/27 Python
django使用django-apscheduler 实现定时任务的例子
2019/07/20 Python
python 浅谈serial与stm32通信的编码问题
2019/12/18 Python
Python字符串函数strip()原理及用法详解
2020/07/23 Python
关于django python manage.py startapp 应用名出错异常原因解析
2020/12/15 Python
移动端Web页面的CSS3 flex布局快速上手指南
2016/05/31 HTML / CSS
CSS3使用transition属性实现过渡效果
2018/04/18 HTML / CSS
CSS3 实现穿梭星空动画
2020/11/13 HTML / CSS
英国领先的在线高尔夫设备零售商:Golfgeardirect
2020/12/11 全球购物
递归计算如下递归函数的值(斐波拉契)
2012/02/04 面试题
《彩色世界》教学反思
2014/04/12 职场文书
2015大学生入党个人自传
2015/06/26 职场文书