PHP实现断点续传
解释
业务上要求对资源文件进行加密,遂实现通过php接口调用,修改header头,传输流的方式。
测试中,在苹果手机上,如果文件过大(大概10M以上),会主动调用多次接口。此时如果不使用断点续传的方式,会导致资源无法加载。
- 苹果设备对于 HTTP Range 请求的处理可能会更加严格和敏感。它可能更倾向于通过多次请求来获取完整的大文件,以确保稳定的下载和播放体验。
- 安卓设备在处理相同的 HTTP Range 请求时可能更加灵活,可以更有效地处理单次或少量请求来获取大文件。
进一步进行抓包发现,windows-chrome,以及安卓手机,在获取视频时,使用的是Range: bytes=0-,也就是有多少要多少的方式,这时服务器如果可以一次性全部传输文件,将不会有问题。
但苹果手机会先调用Range: bytes=0-1获取到文件总大小,再每次请求固定大小的分片,这会导致即便服务器和网络可以一次性全部传输,未实现断点续传时,也会发生客户端无法获取的情况。
Range原理会在后面讲到。
此文仅从服务端角度讲解(实现文件切片并发送)。客户端接收断点续传,按照相同原理,拼接http头,根据第一次请求返回的文件总大小,多次请求获取分片,再使用fopen,fwirte,即可实现文件合并下载。
先放代码
服务端发送
// 最大执行时间 s
ini_set('max_execution_time', '300');
// 内存限制
ini_set('memory_limit', '512M');
// 文件名
$file_name = $_REQUEST['file_name'];
// 文件路径
$file_path = $DIR . $file_name;
$file_size = filesize($file_path);
$mime_type = mime_content_type($file_path);
// 添加适当的缓存控制头信息,以确保文件不会被缓存,特别是在内容频繁更新的情况下。
header('Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
header('Pragma: no-cache');
header('Expires: Thu, 19 Nov 1981 08:52:00 GMT');
header('Content-Type: ' . $mime_type);
header('Content-Disposition: inline; filename="' . $file_name . '"');
// 清理缓冲区
while (ob_get_level() > 0) {
ob_end_flush();
}
// 断点续传
if (isset($_SERVER['HTTP_RANGE'])) {
$fp = fopen($file_path, 'rb');
$range = $_SERVER['HTTP_RANGE'];
list(, $range) = explode('=', $range, 2);
list($start, $end) = explode('-', $range);
$start = intval($start);
$end = ($end === '') ? ($file_size - 1) : intval($end);
$length = $end - $start + 1;
header('HTTP/1.1 206 Partial Content');
header("Content-Range: bytes {$start}-{$end}/{$file_size}");
header('Content-Length: ' . $length);
fseek($fp, $start);
echo fread($fp, $length);
fclose($fp);
} else {
header('Content-Length: ' . $file_size);
readfile($file_path);
}
客户端接收 - 例
function downloadFile($url, $outputFile)
{
$chunkSize = 1024 * 1024; // 1MB per chunk
$handle = fopen($outputFile, 'wb');
if (!$handle) {
die('Cannot open output file for writing');
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_NOPROGRESS, false);
// Set a callback function to write the received data to the file
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($curl, $data) use ($handle) {
fwrite($handle, $data);
return strlen($data);
});
$fileSize = 0;
$headers = get_headers($url, 1);
if (isset($headers['Content-Length'])) {
$fileSize = (int)$headers['Content-Length'];
} else {
die('Unable to determine file size');
}
for ($start = 0; $start < $fileSize; $start += $chunkSize) {
$end = min($start + $chunkSize - 1, $fileSize - 1);
$range = "$start-$end";
// Set the Range header for partial download
curl_setopt($ch, CURLOPT_RANGE, $range);
// Execute the request
curl_exec($ch);
if (curl_errno($ch)) {
die('Curl error: ' . curl_error($ch));
}
echo "Downloaded range: $range\n";
}
curl_close($ch);
fclose($handle);
}
$url = "https://example.com/path/to/large/file";
$outputFile = "downloaded_file";
downloadFile($url, $outputFile);
echo "File downloaded successfully!";
再谈原理
一、php输出文件
在 PHP 中,通过设置 HTTP 头部信息(例如Content-Type、Content-Disposition 等)可以控制如何处理和显示响应内容。这些头部信息告诉浏览器或客户端如何解释接收到的数据,从而影响到数据的显示和处理方式。具体来说:
1. 设置 HTTP 头部的作用:
当 PHP 脚本输出内容时,如果在输出内容之前设置了适当的 HTTP 头部,这些头部信息会一同发送给客户端(浏览器)。
头部信息中的Content-Type告诉浏览器返回的内容的类型(如文本、图片、视频等),浏览器会据此决定如何处理这些数据。
Content-Disposition头部可以告诉浏览器如何处理输出的内容,例如是否作为附件下载或者直接显示。
2. 没有设置头部信息的情况:
如果在 PHP 输出内容时没有设置任何头部信息,PHP 将默认使用 text/html 类型来发送内容。
如果 PHP 脚本输出的是纯文本内容,浏览器可能会将其解释为 HTML,并尝试直接在页面上显示。
如果输出的是二进制数据(如文件内容),浏览器可能会尝试根据文件内容的特征进行解释,但这种行为是不确定的,可能导致意外的结果或者乱码。
3. 为什么设置头部后可以直接输入到浏览器:
设置正确的 Content-Type 和 Content-Disposition 头部告诉浏览器如何处理和显示响应内容。
例如,如果设置了 Content-Disposition: attachment; filename="example.txt",浏览器会提示用户下载一个名为 example.txt 的文件,而不是尝试在浏览器窗口中显示它。
代码中体现
使用mime_content_type()获取文件类型,作为Content-Type传输,告诉浏览器流的格式。
当无法确认时,会返回application/octet-stream,代表二进制。
常见的mime还包括image/jepg、video/mp4等。
二、断点续传
断点续传(Range Requests)是 HTTP/1.1 协议支持的一项功能,它允许客户端在下载文件时,可以请求文件的某个部分,而不是从文件的开头开始下载。这对于大文件或在网络中断时重新开始下载非常有用。
1. 影响
断点续传可以减少对带宽和服务器资源的压力,特别是在下载大文件时,能够更高效地管理和传输数据。
对于移动设备而言,特别是在网络不稳定或者文件较大时,断点续传可以确保用户体验更加流畅,避免因中断而导致下载失败或重试。
2. 工作原理 - HTTP 头部参数
- 请求头部:Range
客户端通过 Range 头部指定所请求的文件部分。
例如,Range: bytes=0-999 表示请求文件的前 1000 个字节。
- 响应头部:Content-Range
服务器通过 Content-Range 头部指明所响应的文件部分。
例如,Content-Range: bytes 0-999/12345 表示文件的前 1000 个字节,总文件大小为 12345 字节。
- 响应头部:Accept-Ranges(服务端是否支持)
服务器通过 Accept-Ranges:<支持的格式> 头部告知客户端它支持范围请求。
例如,Accept-Ranges: bytes 表示服务器支持按字节范围请求。
3. PHP 中的实现
在 PHP 中实现断点续传通常涉及以下步骤:
- 识别 Range 请求
在 PHP 脚本中,通过检查 $_SERVER['HTTP_RANGE'] 可以获取客户端请求的 Range 信息。
- 处理部分内容请求
根据客户端请求的 Range,打开文件并使用 fseek() 定位到指定的位置,然后读取并发送对应的文件部分。
Range: <数据格式>=<数据开始的索引位置>-<数据结束的索引位置>
可以逗号隔开,请求多个分段,此处不做讨论处理。
- 返回部分内容和状态码
设置适当的 HTTP 头部,如 Content-Range 和 Content-Length,并返回状态码 206 Partial Content(206-部分内容)。
当请求文件的范围服务器无法满足时,返回416 Range Not Satisfiable (416-请求的范围无法满足)
- 支持多次请求
如果客户端继续请求下一个文件部分(通常是通过用户操作或者自动化的策略),PHP 脚本需要能够处理这些连续的 Range 请求。
代码片段中的体现
对 $_SERVER['HTTP_RANGE'] 进行字符串解析。
拼接设置三个头 HTTP/1.1 ...、Content-Range、Content-Lenght。
用 fseek()、fread(); 获取文件流。
三、缓冲
代码片段中的体现
header('Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
header('Pragma: no-cache');
header('Expires: Thu, 19 Nov 1981 08:52:00 GMT');
这些头部信息用于控制客户端(通常是浏览器)的缓存行为。确保浏览器不会缓存响应内容,强制每次请求都从服务器获取最新的数据。
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0:
no-store:指示浏览器不要在任何地方存储响应内容,不论是内存中还是磁盘上。no-cache:强制所有缓存的副本在使用前重新验证,即浏览器每次请求都要向服务器验证内容是否已更新。must-revalidate:一旦缓存过期,必须重新验证缓存内容,不允许使用陈旧的缓存内容。post-check=0,pre-check=0:这些参数在 HTTP/1.1 中较少使用,旨在控制缓存的验证和过期行为。这两个参数在大多数现代浏览器中已被忽略,但可以确保旧的缓存机制被覆盖。
Pragma: no-cache:
Pragma 是 HTTP/1.0 标准中的一个头部,no-cache 指示浏览器不要缓存响应内容。这在与旧版 HTTP/1.0 兼容时非常有用。
Expires: Thu, 19 Nov 1981 08:52:00 GMT:
Expires 头部设置一个过去的时间,指示缓存内容已经过期。选择这个日期是为了确保浏览器认为响应内容已经过期,从而不会缓存。
while (ob_get_level() > 0) {
ob_end_flush();
}
这段代码用于处理输出缓冲区:
ob_get_level():
返回当前的输出缓冲区级别。如果输出缓冲区是嵌套的,每次 ob_start() 调用都会增加一个级别。
ob_end_flush():
发送(输出)缓冲区的内容并关闭当前的输出缓冲区。这相当于 ob_flush() 和 ob_end_clean() 的组合操作。
通过循环调用 ob_end_flush() 直到 ob_get_level() 返回 0(即没有更多的输出缓冲区),确保所有嵌套的输出缓冲区都被清空,并将其内容发送到客户端。
四、HTTP头:下载/展示/文件名
代码中体现
header('Content-Type: ' . $mime_type);
header('Content-Disposition: inline; filename="' . $file_name . '"');
Content-Type 告诉浏览器文件类型
Content-Disposition 告诉浏览器如何处理文件。有两个可选参数inline(直接展示)和attachment(下载)。
实际场景中遇到,Chrome在处理video/quicktime,也就是苹果.MOV格式的时候,即使设置了inline,也会直接下载。这是因为Chrome本身不支持对应mime的直接展示。如果发现类似情况,可作参考。
扩展:
Content-Disposition在Form表单请求体中的运用
Content-Dispostion中文乱码问题
PHP实现断点续传的更多相关文章
- HTML5实现文件断点续传
HTML5的FILE api,有一个slice方法,可以将BLOB对象进行分割.前端通过FileList对象获取到相应的文件,按照指定的分割方式将大文件分段,然后一段一段地传给后端,后端再按顺序一段段 ...
- 总结iOS开发中的断点续传那些事儿
前言 断点续传概述 断点续传就是从文件赏赐中断的地方重新开始下载或者上传数据,而不是从头文件开始.当下载大文件的时候,如果没有实现断点续传功能,那么每次出现异常或者用户主动的暂停,都会从头下载,这样很 ...
- (实例篇)PHP实现HTTP断点续传的方法
PHP实现HTTP断点续传的方法. <?php /** * PHP-HTTP断点续传实现 * @param string $path: 文件所在路径 * @param string $file: ...
- C# 文件下载之断点续传
注意,本文所说的断点续传特指 HTTP 协议中的断点续传.本文主要聊聊思路和关键代码,更多细节请参考本文附带的 demo. 工作原理 HTTP 协议中定义了一些请求/响应头,通过组合使用这些头信息.我 ...
- chunkupload文件上传断点续传组件(java)
chunkupload简介 chunkupload是一款基于java语言的断点续传组件,针对文件上传,非文件下载,集成方便,使用简单. 从整体上讲,chunkupload会对文件进行切片处理,每个切片 ...
- wget 断点续传 & nginx文件服务器
nginx默认支持断点续传: 测试方法: wget -S http://httpd.apache.org/images/httpd_logo_wide_new.png 2>&1 | gr ...
- chunkupload 文件上传断点续传组件(java) - 正式发布
chunkupload简介 chunkupload是一款基于java语言的断点续传组件,针对文件上传,非文件下载,集成方便,使用简单. chunkupload实现如下功能: · 实现断点续传 · ...
- ASP.NET WebAPi之断点续传下载(下)
前言 上一篇我们穿插了C#的内容,本篇我们继续来讲讲webapi中断点续传的其他情况以及利用webclient来实现断点续传,至此关于webapi断点续传下载以及上传内容都已经全部完结,一直嚷嚷着把S ...
- ASP.NET WebAPi之断点续传下载(中)
前言 前情回顾:上一篇我们遗留了两个问题,一个是未完全实现断点续传,另外则是在响应时是返回StreamContent还是PushStreamContent呢?这一节我们重点来解决这两个问题,同时就在此 ...
- ASP.NET WebAPi之断点续传下载(上)
前言 之前一直感觉断点续传比较神秘,于是想去一探究竟,不知从何入手,以为就写写逻辑就行,结果搜索一番,还得了解相关http协议知识,又花了许久功夫去看http协议中有关断点续传知识,有时候发觉东西只有 ...
随机推荐
- 强化学习baseline论文—— rainbow算法中给出实验结果的54个atari2600游戏名称列表
alien amidar assault asterix asteroids atlantis bank_heist battle_zone beam_rider berzerk bowling bo ...
- 惠普暗影精灵2pro挑电池和电源适配器
自己几年前买的暗影2pro前几个月坏掉了,无法充电,而且偶发性掉电,经过长时间研究发现该款电脑存在挑电池和电源适配器的问题. 相关资料: https://www.chinafix.com/thread ...
- 【转载】 miniImageNet数据集介绍
版权声明:本文为CSDN博主「miguemath」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn.net/wangkai ...
- vue之父组件与子组件的通信
1.背景 参数传递(多理解) 1.父传子<c-parent :messagevue="message"></c-parent>,请看下面具体的截图描述 2. ...
- 从数据到洞察:DataOps加速AI模型开发的秘密实践大公开!
作者 | 代立冬,白鲸开源科技联合创始人&CTO 引言 在AI驱动的商业世界中,DataOps作为连接数据与洞察的桥梁,正迅速成为企业数据战略的核心. 在WOT全球技术创新大会2024·北京站 ...
- 生态兼容性进一步提升!白鲸开源 WhaleStudio 与火山引擎ByteHouse完成产品互认
数据作为新型生产要素,已快速融入生产.分配.流通.消费和社会服务管理等各环节,深刻改变着生产方式.生活方式和治理方式.越来越多企业也在尝试充分利用数据要素,开辟全新发展路径,进一步实现业务价值提升. ...
- Kotlin 控制流和数组操作详解
Kotlin when 与编写许多 if..else 表达式相比,您可以使用 when 表达式,它更易读. 它用于选择要执行的多个代码块中的一个: 示例 使用星期几的编号来计算星期几的名称: val ...
- SenseCraft 部署模型到Grove Vision AI V2图像处理模块
Grove Vision AI V2 图像处理模块开箱测评 摘要 今天教大家快速上手 Grove Vision AI V2 图像处理模块,我们将一起探讨如何利用 SenseCraft 部署 AI 模型 ...
- Go 进程在容器中无 coredump 产生问题分析
Go 进程在容器中无 coredump 产生问题分析 0x01 起因 coredump 作为一种非常重要的高度手段,在日常开发中经常用到,切换到容器环境后一直没关注.最近测试了下,发现出不了 core ...
- Docker简单使用总结
写在前面 最近在部署前后端分离的SpringBoot项目,发现使用Docker技术很方便,特此记录一下Docker常用命令 1.常用网站 Docker docs :https://docs.docke ...