首页 > 编程语言 >PHP实现断点续传

PHP实现断点续传

时间:2024-07-11 13:52:22浏览次数:15  
标签:断点续传 浏览器 请求 实现 Content Range 头部 HTTP PHP

解释

业务上要求对资源文件进行加密,遂实现通过php接口调用,修改header头,传输流的方式。

测试中,在苹果手机上,如果文件过大(大概10M以上),会主动调用多次接口。此时如果不使用断点续传的方式,会导致资源无法加载。

  • 苹果设备对于 HTTP Range 请求的处理可能会更加严格和敏感。它可能更倾向于通过多次请求来获取完整的大文件,以确保稳定的下载和播放体验。
  • 安卓设备在处理相同的 HTTP 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);
$fp = fopen($file_path, 'rb');

// 添加适当的缓存控制头信息,以确保文件不会被缓存,特别是在内容频繁更新的情况下。
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'])) {
	$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);
} else {
	header('Content-Length: ' . $file_size);
	readfile($file_path);
}

客户端接收 - 例

<?php

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 等)可以控制如何处理和显示响应内容。这些头部信息告诉浏览器或客户端如何解释接收到的数据,从而影响到数据的显示和处理方式。具体来说:

设置 HTTP 头部的作用:

当 PHP 脚本输出内容时,如果在输出内容之前设置了适当的 HTTP 头部,这些头部信息会一同发送给客户端(浏览器)。
头部信息中的Content-Type告诉浏览器返回的内容的类型(如文本、图片、视频等),浏览器会据此决定如何处理这些数据。
Content-Disposition头部可以告诉浏览器如何处理输出的内容,例如是否作为附件下载或者直接显示。

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

如果在 PHP 输出内容时没有设置任何头部信息,PHP 将默认使用 text/html 类型来发送内容。
如果 PHP 脚本输出的是纯文本内容,浏览器可能会将其解释为 HTML,并尝试直接在页面上显示。
如果输出的是二进制数据(如文件内容),浏览器可能会尝试根据文件内容的特征进行解释,但这种行为是不确定的,可能导致意外的结果或者乱码。

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

设置正确的 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 协议支持的一项功能,它允许客户端在下载文件时,可以请求文件的某个部分,而不是从文件的开头开始下载。这对于大文件或在网络中断时重新开始下载非常有用。

影响:

断点续传可以减少对带宽和服务器资源的压力,特别是在下载大文件时,能够更高效地管理和传输数据。
对于移动设备而言,特别是在网络不稳定或者文件较大时,断点续传可以确保用户体验更加流畅,避免因中断而导致下载失败或重试。

工作原理 - 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 表示服务器支持按字节范围请求。

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中文乱码问题

标签:断点续传,浏览器,请求,实现,Content,Range,头部,HTTP,PHP
From: https://www.cnblogs.com/cyamazing/p/18294651

相关文章

  • 入门PHP就来我这(高级)22 ~ 七天免登录案例
    有胆量你就来跟着路老师卷起来! --纯干货,技术知识分享路老师给大家分享PHP语言的知识了,旨在想让大家入门PHP,并深入了解PHP语言。  上文讲述了cookie的概念,创建,获取,销毁以及生命周期后,我们利用本文来实现一个小的案例,实现七天免登录的案例。 七天免登录功能案例1......
  • Python机器学习实战:推荐系统的原理与实现方法
    Python机器学习实战:推荐系统的原理与实现方法1.背景介绍1.1问题的由来在当今数字化时代,推荐系统已成为电子商务、媒体流媒体平台、社交媒体以及在线购物网站的核心组件之一。推荐系统旨在根据用户的历史行为、偏好以及社会关系等因素,为用户提供个性化的内容或商品建议,......
  • 基于javaweb jsp ssm汽车服务商城系统设计与实现+vue录像(源码+lw+部署文档+讲解等)
    前言......
  • three.js 光墙效果实现
    需求:要实现一个围栏效果的光墙方案:当前只介绍贴图实现方案,其他的材质,uv什么的咱确实玩不来. 实现:1.就是创建一个缓冲几何,然后通过计算哪个面贴图哪个面不贴图,(这个是我直接拷贝的别人的,只是实现了一个矩形效果)LightWallModel(data){//光墙效果......
  • react hooks实现对元素拖拽及鼠标滚轮缩放
    page.jsximport'./index.less';import{useDrag,useZoom}from'./hooks';constDragZoom=()=>{const{handleMouseDown,handleMouseMove,handleMouseUp}=useDrag();const{handleWheel,scale}=useZoom();re......
  • WPF/C#:在WPF中如何实现依赖注入
    前言本文通过WPFGallery这个项目学习依赖注入的相关概念与如何在WPF中进行依赖注入。什么是依赖注入依赖注入(DependencyInjection,简称DI)是一种设计模式,用于实现控制反转(InversionofControl,简称IoC)原则。依赖注入的主要目的是将对象的创建和对象之间的依赖关系的管......
  • (免费领取源码)计算机毕业设计项目:宠物店管理系统 19849(开题答辩+程序定制+全套文案 )上
    目 录摘要1绪论1.1背景及意义1.2研究现状1.3springboot框架介绍2 宠物店管理系统系统分析2.1可行性分析2.2系统流程分析2.2.1数据流程3.3.2业务流程2.3系统功能分析2.3.1功能性分析2.3.2非功能性分析2.4系统用例分析2.5本章小结......
  • Rust中为外部类型实现外部trait
    由于孤儿规则(orphanrule)的限制,在Rust中无法直接为外部类型实现外部trait。但是我们可以通过构造一个外部类型的wrapper来间接实现这个目的。一个比较常见的使用情形是,外部类型并没有实现Displaytrait,而我们想为其实现。这里,我们以标准库中的String为例进行介绍。externcr......
  • 基于java+springboot+vue实现的学生网上请假系统(文末源码+Lw)104
    系统功能:本学生网上请假系统管理员,教师,学生。管理员功能有个人中心,学生管理,教师管理,班级信息管理,请假表格管理,提交请假表管理,学生考勤管理,缺课记录管理。教师功能有个人中心,学生管理,班级信息管理,请假表格管理,提交请假表管理,学生考勤管理,缺课记录管理。学生功能有班级信息管......
  • 基于java+springboot+vue实现的音乐网站(文末源码+Lw)102
    功能介绍:本音乐网站管理员功能有个人中心,用户管理,歌曲分类管理,歌曲信息管理,管理员管理,系统管理等。用户可以注册登录,试听歌曲,可以下载歌曲。因而具有一定的实用性。本站是一个B/S模式系统,采用SpringBoot框架,MYSQL数据库设计开发,充分保证系统的稳定性。系统具有界面清晰、操......