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数据缓存技术
Feb 14 PHP
php gzip压缩输出的实现方法
Apr 27 PHP
关于尾递归的使用详解
May 02 PHP
PHP产生不重复随机数的5个方法总结
Nov 12 PHP
在Mac OS上编译安装Nginx+PHP+MariaDB开发环境的教程
Feb 23 PHP
PHP文件缓存smarty模板应用实例分析
Feb 26 PHP
PHP 实现字符串翻转(包含中文汉字)的实现代码
Apr 01 PHP
PHP实现数组向任意位置插入,删除,替换数据操作示例
Apr 05 PHP
PHP面向对象程序设计子类扩展父类(子类重新载入父类)操作详解
Jun 14 PHP
php反射学习之依赖注入示例
Jun 14 PHP
laravel dingo API返回自定义错误信息的实例
Sep 29 PHP
Thinkphp 在api开发中异常返回依然是html的解决方式
Oct 16 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基于GD库画五星红旗的方法
2015/02/24 PHP
php中mysql操作buffer用法详解
2015/03/19 PHP
Yii2.0 Basic代码中路由链接被转义的处理方法
2016/09/21 PHP
[原创]php实现数组按拼音顺序排序的方法
2017/05/03 PHP
修改好的jquery滚动字幕效果实现代码
2011/06/22 Javascript
Chosen 基于jquery的选择框插件使用方法
2012/05/30 Javascript
window.open关于浏览器拦截问题分析及解决方法
2013/02/05 Javascript
jquery购物车实时结算特效实现思路
2013/09/23 Javascript
Node.js实现的简易网页抓取功能示例
2014/12/05 Javascript
《JavaScript DOM 编程艺术》读书笔记之DOM基础
2015/01/09 Javascript
js改变embed标签src值的方法
2015/04/10 Javascript
javascript HTML5 Canvas实现圆盘抽奖功能
2016/04/11 Javascript
vue与bootstrap实现时间选择器的示例代码
2017/08/26 Javascript
JS声明对象时属性名加引号与不加引号的问题及解决方法
2018/02/16 Javascript
vue2.0在没有dev-server.js下的本地数据配置方法
2018/02/23 Javascript
详解npm 配置项registry修改为淘宝镜像
2018/09/07 Javascript
微信小程序自定义可滑动日历界面
2018/12/28 Javascript
详解mpvue开发微信小程序基础知识
2019/09/23 Javascript
js常用方法、检查是否有特殊字符串、倒序截取字符串操作完整示例
2020/01/26 Javascript
解决vue项目,npm run build后,报路径错的问题
2020/08/13 Javascript
Nuxt.js 静态资源和打包的操作
2020/11/06 Javascript
python登陆asp网站页面的实现代码
2015/01/14 Python
Python实现针对给定字符串寻找最长非重复子串的方法
2018/04/21 Python
python Spyder界面无法打开的解决方法
2018/04/27 Python
世界上第一个创建了罩杯系统的美国内衣品牌:Maidenform
2019/03/23 全球购物
德国二手设计师时装和复古时装跳蚤市场:Mädchenflohmarkt
2020/11/09 全球购物
Burt’s Bees英国官网:世界领先的天然个人护理品牌
2020/08/17 全球购物
如何判断计算机可能已经中马
2013/03/22 面试题
普通院校学生的自荐信
2013/11/27 职场文书
政工例会汇报材料
2014/08/26 职场文书
企业法人授权委托书范本
2014/09/23 职场文书
大学生求职自荐信
2015/03/24 职场文书
请病假条范文
2015/08/17 职场文书
决心书格式及范文
2019/06/24 职场文书
Java面试题冲刺第十七天--基础篇3
2021/08/07 面试题
Redis配置外网可访问(redis远程连接不上)的方法
2022/12/24 Redis