大檔案下載常因網路中斷而失敗。FileDownload 類別實作 HTTP/1.1 Range 與 Content-Range 標頭,支援 wget -c、瀏覽器下載工具續傳功能。
Range: bytes=200-300 指定欲下載位元組範圍Content-Range: bytes 200-300/1000 回應實際範圍與總大小<?php
/** PHP 下載類,支援斷點續傳
* Date: 2013-06-30
* Author: test
* Ver: 1.0
*/
class FileDownload {
private $_speed = 512; // KB/s
/** 下載檔案
* @param String $file 檔案路徑
* @param String $name 下載檔名(空則用原檔名)
* @param Boolean $reload 是否啟用斷點續傳
* @return Boolean
*/
public function download($file, $name = '', $reload = false) {
if (!file_exists($file)) return false;
if ($name === '') $name = basename($file);
$fp = fopen($file, 'rb');
$file_size = filesize($file);
$ranges = $this->getRange($file_size);
header('Cache-Control: public');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $name . '"');
if ($reload && $ranges !== null) {
// 續傳模式
header('HTTP/1.1 206 Partial Content');
header('Accept-Ranges: bytes');
header(sprintf('Content-Length: %u', $ranges['end'] - $ranges['start']));
header(sprintf('Content-Range: bytes %s-%s/%s', $ranges['start'], $ranges['end'], $file_size));
fseek($fp, $ranges['start']);
} else {
// 完整下載
header('HTTP/1.1 200 OK');
header('Content-Length: ' . $file_size);
}
// 分塊輸出
while (!feof($fp)) {
echo fread($fp, round($this->_speed * 1024));
ob_flush();
flush();
}
fclose($fp);
return true;
}
/** 設定下載速度限制 (KB/s) */
public function setSpeed($speed) {
if (is_numeric($speed) && $speed > 16 && $speed < 4096) {
$this->_speed = $speed;
}
}
/** 解析 HTTP Range 標頭 */
private function getRange($file_size) {
if (!isset($_SERVER['HTTP_RANGE']) || empty($_SERVER['HTTP_RANGE'])) return null;
$range = $_SERVER['HTTP_RANGE'];
$range = preg_replace('/[\s|,].*/', '', $range); // 取第一個範圍
$range = explode('-', substr($range, 6)); // 移除 "bytes="
if (count($range) < 2) $range[1] = $file_size;
$range = array_combine(array('start', 'end'), $range);
$range['start'] = $range['start'] === '' ? 0 : (int)$range['start'];
$range['end'] = $range['end'] === '' ? $file_size : (int)$range['end'];
// 邊界檢查
$range['start'] = max(0, min($range['start'], $file_size - 1));
$range['end'] = max($range['start'] + 1, min($range['end'], $file_size));
return $range;
}
}
?>
<?php
require('FileDownload.class.php');
$file = 'book.zip';
$name = time() . '.zip';
$obj = new FileDownload();
// 一般下載
// $flag = $obj->download($file, $name);
// 斷點續傳下載
$flag = $obj->download($file, $name, true);
if (!$flag) echo '檔案不存在';
?>
# 1. 不支援續傳時 wget -O test.rar https://demo.test.com/demo.php # 中斷後重試 wget -c -O test.rar https://demo.test.com/demo.php # 回應 200 OK,從頭下載 # 2. 支援續傳時 wget -O test.rar https://demo.test.com/demo.php # 中斷後重試 wget -c -O test.rar https://demo.test.com/demo.php # 回應 206 Partial Content,從中斷處續傳 Content-Range: bytes 2097720-10445120/10445121
set_time_limit(0) 避免大檔案中斷