解释

业务上要求对资源文件进行加密,遂实现通过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-TypeContent-Disposition 等)可以控制如何处理和显示响应内容。这些头部信息告诉浏览器或客户端如何解释接收到的数据,从而影响到数据的显示和处理方式。具体来说:

1. 设置 HTTP 头部的作用:

当 PHP 脚本输出内容时,如果在输出内容之前设置了适当的 HTTP 头部,这些头部信息会一同发送给客户端(浏览器)。

头部信息中的Content-Type告诉浏览器返回的内容的类型(如文本、图片、视频等),浏览器会据此决定如何处理这些数据。

Content-Disposition头部可以告诉浏览器如何处理输出的内容,例如是否作为附件下载或者直接显示。

2. 没有设置头部信息的情况:

如果在 PHP 输出内容时没有设置任何头部信息,PHP 将默认使用 text/html 类型来发送内容。

如果 PHP 脚本输出的是纯文本内容,浏览器可能会将其解释为 HTML,并尝试直接在页面上显示。

如果输出的是二进制数据(如文件内容),浏览器可能会尝试根据文件内容的特征进行解释,但这种行为是不确定的,可能导致意外的结果或者乱码。

3. 为什么设置头部后可以直接输入到浏览器:

设置正确的 Content-TypeContent-Disposition 头部告诉浏览器如何处理和显示响应内容。

例如,如果设置了 Content-Disposition: attachment; filename="example.txt",浏览器会提示用户下载一个名为 example.txt 的文件,而不是尝试在浏览器窗口中显示它。

下载/展示/文件名

代码中体现

使用mime_content_type()获取文件类型,作为Content-Type传输,告诉浏览器流的格式。

当无法确认时,会返回application/octet-stream,代表二进制。

常见的mime还包括image/jepgvideo/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-RangeContent-Length,并返回状态码 206 Partial Content(206-部分内容)。

当请求文件的范围服务器无法满足时,返回416 Range Not Satisfiable (416-请求的范围无法满足)

  • 支持多次请求

如果客户端继续请求下一个文件部分(通常是通过用户操作或者自动化的策略),PHP 脚本需要能够处理这些连续的 Range 请求。

代码片段中的体现

$_SERVER['HTTP_RANGE'] 进行字符串解析。

拼接设置三个头 HTTP/1.1 ...Content-RangeContent-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实现断点续传的更多相关文章

  1. HTML5实现文件断点续传

    HTML5的FILE api,有一个slice方法,可以将BLOB对象进行分割.前端通过FileList对象获取到相应的文件,按照指定的分割方式将大文件分段,然后一段一段地传给后端,后端再按顺序一段段 ...

  2. 总结iOS开发中的断点续传那些事儿

    前言 断点续传概述 断点续传就是从文件赏赐中断的地方重新开始下载或者上传数据,而不是从头文件开始.当下载大文件的时候,如果没有实现断点续传功能,那么每次出现异常或者用户主动的暂停,都会从头下载,这样很 ...

  3. (实例篇)PHP实现HTTP断点续传的方法

    PHP实现HTTP断点续传的方法. <?php /** * PHP-HTTP断点续传实现 * @param string $path: 文件所在路径 * @param string $file: ...

  4. C# 文件下载之断点续传

    注意,本文所说的断点续传特指 HTTP 协议中的断点续传.本文主要聊聊思路和关键代码,更多细节请参考本文附带的 demo. 工作原理 HTTP 协议中定义了一些请求/响应头,通过组合使用这些头信息.我 ...

  5. chunkupload文件上传断点续传组件(java)

    chunkupload简介 chunkupload是一款基于java语言的断点续传组件,针对文件上传,非文件下载,集成方便,使用简单. 从整体上讲,chunkupload会对文件进行切片处理,每个切片 ...

  6. wget 断点续传 & nginx文件服务器

    nginx默认支持断点续传: 测试方法: wget -S http://httpd.apache.org/images/httpd_logo_wide_new.png 2>&1 | gr ...

  7. chunkupload 文件上传断点续传组件(java) - 正式发布

    chunkupload简介 chunkupload是一款基于java语言的断点续传组件,针对文件上传,非文件下载,集成方便,使用简单. chunkupload实现如下功能: ·  实现断点续传 ·  ...

  8. ASP.NET WebAPi之断点续传下载(下)

    前言 上一篇我们穿插了C#的内容,本篇我们继续来讲讲webapi中断点续传的其他情况以及利用webclient来实现断点续传,至此关于webapi断点续传下载以及上传内容都已经全部完结,一直嚷嚷着把S ...

  9. ASP.NET WebAPi之断点续传下载(中)

    前言 前情回顾:上一篇我们遗留了两个问题,一个是未完全实现断点续传,另外则是在响应时是返回StreamContent还是PushStreamContent呢?这一节我们重点来解决这两个问题,同时就在此 ...

  10. ASP.NET WebAPi之断点续传下载(上)

    前言 之前一直感觉断点续传比较神秘,于是想去一探究竟,不知从何入手,以为就写写逻辑就行,结果搜索一番,还得了解相关http协议知识,又花了许久功夫去看http协议中有关断点续传知识,有时候发觉东西只有 ...

随机推荐

  1. 强化学习baseline论文—— rainbow算法中给出实验结果的54个atari2600游戏名称列表

    alien amidar assault asterix asteroids atlantis bank_heist battle_zone beam_rider berzerk bowling bo ...

  2. 惠普暗影精灵2pro挑电池和电源适配器

    自己几年前买的暗影2pro前几个月坏掉了,无法充电,而且偶发性掉电,经过长时间研究发现该款电脑存在挑电池和电源适配器的问题. 相关资料: https://www.chinafix.com/thread ...

  3. 【转载】 miniImageNet数据集介绍

    版权声明:本文为CSDN博主「miguemath」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn.net/wangkai ...

  4. vue之父组件与子组件的通信

    1.背景 参数传递(多理解) 1.父传子<c-parent :messagevue="message"></c-parent>,请看下面具体的截图描述 2. ...

  5. 从数据到洞察:DataOps加速AI模型开发的秘密实践大公开!

    作者 | 代立冬,白鲸开源科技联合创始人&CTO 引言 在AI驱动的商业世界中,DataOps作为连接数据与洞察的桥梁,正迅速成为企业数据战略的核心. 在WOT全球技术创新大会2024·北京站 ...

  6. 生态兼容性进一步提升!白鲸开源 WhaleStudio 与火山引擎ByteHouse完成产品互认

    数据作为新型生产要素,已快速融入生产.分配.流通.消费和社会服务管理等各环节,深刻改变着生产方式.生活方式和治理方式.越来越多企业也在尝试充分利用数据要素,开辟全新发展路径,进一步实现业务价值提升. ...

  7. Kotlin 控制流和数组操作详解

    Kotlin when 与编写许多 if..else 表达式相比,您可以使用 when 表达式,它更易读. 它用于选择要执行的多个代码块中的一个: 示例 使用星期几的编号来计算星期几的名称: val ...

  8. SenseCraft 部署模型到Grove Vision AI V2图像处理模块

    Grove Vision AI V2 图像处理模块开箱测评 摘要 今天教大家快速上手 Grove Vision AI V2 图像处理模块,我们将一起探讨如何利用 SenseCraft 部署 AI 模型 ...

  9. Go 进程在容器中无 coredump 产生问题分析

    Go 进程在容器中无 coredump 产生问题分析 0x01 起因 coredump 作为一种非常重要的高度手段,在日常开发中经常用到,切换到容器环境后一直没关注.最近测试了下,发现出不了 core ...

  10. Docker简单使用总结

    写在前面 最近在部署前后端分离的SpringBoot项目,发现使用Docker技术很方便,特此记录一下Docker常用命令 1.常用网站 Docker docs :https://docs.docke ...