如何使用 PHP 下载大文件?

Posted

技术标签:

【中文标题】如何使用 PHP 下载大文件?【英文标题】:How to download large files with PHP? 【发布时间】:2017-12-15 07:37:51 【问题描述】:

我花了一周的时间来找到这个问题的正确答案。 '正确' 我的意思是绝对符合现有的网络标准,可靠且性能有效。终于找到了解决办法。

我在 *** 上找到的所有内容(Downloading large files reliably in php、How to download large files through PHP script)都不适合我:

    两种解决方案都不支持范围请求。这使它们无法用于视频和音频流和下载恢复;

    所有示例都与缓存和性能无关;

PHP 7.0 代码在桌面版 Chrome、Safari、Opera 和 Firefox 上进行了测试。维瓦尔第测试不成功。

【问题讨论】:

如果你想要一个正确的答案,解释一下你所说的大是什么意思? 100MB、1GB、10GB、100GB、1TB、1PB? 大意味着超过 PHP 内存大小。我已经用最大 800Mb 对其进行了测试。 How to download large files through PHP script的可能重复 不是重复的。我尝试使用您参考中的代码。它不适用于音频和视频。 我真的不明白这个问题。您要求一种在您链接的 19 个答案中未描述的用 PHP 发送文件的方法?有什么意义?相反,您为什么不分享您拥有的代码并描述您所面临的具体问题,以便解决它? 【参考方案1】:
const STDOUT_CHUNK_SIZE = 128 * 1024; // Buffer size to send data to browser. MUST be less then 1/3 of PHP memory size
const CACHE_EXP_SEC = 1800;  // Cache expire time is 30 min.

$fileName = "large_video.mp4";
$contentSize = filesize($fileName);
$isAttachment = false;  // false allows to use a file as inline element of web page 

// Parse range request. Browser asks for part of file
if (isset($_SERVER["HTTP_RANGE"])) 
  list($units, $range) = explode("=", $_SERVER["HTTP_RANGE"], 2);
  if ($units !== "bytes") 
    http_response_code(416); // Requested Range Not Satisfiable
    exit;
  
  $range = explode(",", $range, 2)[0]; // Get only first range. You can improve this ;)
  list($from, $to) = explode("-", $range, 2);
  $to = empty($to) ? ($contentSize - 1) : min(abs((int)$to), ($contentSize - 1));
  $from = (empty($from) || $to < abs((int)$from)) ? 0 : max(abs((int)$from), 0);

else 
  // Request for whole content
  $from = 0;
  $to = $contentSize - 1;


// Set response headers
if ($from > 0 || $to < ($contentSize - 1))

  http_response_code(206); // Partial Content
  header("Content-Type: video/mp4"));
  header("Content-Range: bytes $from-$to/$contentSize");
  header("Content-Length: " . ($from - $to + 1));

else 
  $etag = md5($file);  // Content is immutable but file name can be changed
  if (isset($_SERVER["HTTP_IF_NONE_MATCH"]) && trim($_SERVER["HTTP_IF_NONE_MATCH"]) === $etag) 
    http_response_code(304); // Not Modified
    setCacheHeaders($etag);
    exit;
  

  http_response_code(200);  // Ok
  header("Content-Type: video/mp4"));
  header("Content-Length: $contentSize");
  if ($isAttachment) header("Content-Disposition: attachment; filename=\"$fileName\"");
  else header("Content-Disposition: inline");

  header("Accept-Ranges: bytes");
  setCacheHeaders($etag);


// Send response to client
if ($file = fopen($fileName, "rb")) 
  fseek($file, $from);
  $counter = $from;
  set_time_limit(0);
  while (!feof($file) && $counter <= $to) 
    $bytesToRead = STDOUT_CHUNK_SIZE;
    if ($counter + $bytesToRead > $to) $bytesToRead = $to - $counter + 1;
    $data = fread($file, $bytesToRead);
    $counter += $bytesToRead;
    echo $data;
    flush();
  
fclose($file);

function setCacheHeaders(string $etag, bool $cacheEnabled = true, bool $public = true)

  if ($cacheEnabled) 
    header("ETag: $etag");
    $scope = $public ? "public" : "private";
    $sec = CACHE_EXP_SEC;
    $age = ($sec >= 0) ? ", max-age=$sec, s-maxage=$sec" : "";
    header("Cache-Control: $scope$age, no-transform");
  
  else header("Cache-Control: no-cache, no-store, must-revalidate");

【讨论】:

为什么都是@? 只是我的风格))) 你可以省略它。 我又添加了一些 cmets。 使用@不仅仅是一种风格,而是一种隐藏错误 好的。我看到我关于风格的笑话有点无关紧要。我确实从代码中删除了所有“@”。不幸的是,PHP 并不是构建真正可靠代码的更好工具。一些标准函数的产生甚至与文档不同。就我而言,我只是试图防止任何不可预测的行为破坏输出。 Chrome 浏览器对来自服务器的任何意外响应非常宽容,但 Safari 则不然。我同意,@s 不是好的解决方案。最好使用 try-catch 或自定义错误处理程序,但我的目标是展示简单的工作代码。

以上是关于如何使用 PHP 下载大文件?的主要内容,如果未能解决你的问题,请参考以下文章

php 如何下载远程文件到本地重命名

PHP curl 上传大文件非常大慢,导致超时,小文件(10M以下)的还可以,请问如何解决啊!

如何使用 JavaScript 下载大文件

如何使用 PHP 单击文件名下载文件?

如何使用 Go 有效地下载大文件?

如何使用 PHP 代码从服务器下载文件