如何使用PHP下载大文件? [英] How to download large files with PHP?

查看:220
本文介绍了如何使用PHP下载大文件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我花了一个星期的时间找到这个问题的正确答案. "正确"是指完全符合现有的网络标准,可靠且性能有效.终于,我找到了解决方案.

I spent a week to find right answer on this question. 'Right' I mean absolutely conform to existing web-standards, reliable and performance effective. Finally, I've found the solution.

我在StackOverflow上找到的所有内容(在PHP中可靠地下载大文件如何通过PHP脚本下载大文件)对我不起作用:

All what I've found on StackOverflow (Downloading large files reliably in PHP, How to download large files through PHP script) is not works for me:

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

  1. Both solutions are not support of range requests. It makes them not working for video and audio streaming and download resuming;

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

All examples have nothing about caching and performance;

PHP 7.0代码已在桌面版本的Chrome,Safari,Opera和Firefox上进行了测试. Vivaldi测试不成功.

PHP 7.0 code tested with desktop versions of Chrome, Safari, Opera and Firefox. Vivaldi test was not successful.

推荐答案

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");
}

这篇关于如何使用PHP下载大文件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆