Range: bytes=200-300 指定位元組區間。Content-Range: bytes 200-300/801 表明回應區間與總大小。206 Partial Content 表示部分內容;200 OK 表示完整檔案。Accept-Ranges: bytes 告知客戶端支援斷點續傳。<?php
class FileDownload {
private $_speed = 512; // KB/s
public function download($file, $name = '', $reload = false) {
if (!file_exists($file)) return;
$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();
// sleep(1); // 測試用:模擬限速
}
fclose($fp);
}
public function setSpeed($speed) {
if (is_numeric($speed) && $speed > 16 && $speed < 4096) $this->_speed = $speed;
}
private function getRange($file_size) {
if (!isset($_SERVER['HTTP_RANGE'])) return null;
$range = $_SERVER['HTTP_RANGE'];
// Range: bytes=start-end
if (preg_match('/bytes=\s*(\d+)\s*-\s*(\d*)/i', $range, $matches)) {
$start = (int)$matches[1];
$end = $matches[2] !== '' ? (int)$matches[2] : $file_size - 1;
if ($start > $end || $start >= $file_size) return null;
return ['start' => $start, 'end' => min($end, $file_size - 1)];
}
return null;
}
}
$downloader = new FileDownload();
$downloader->setSpeed(1024); // 限速 1MB/s
$downloader->download('/data/files/large.zip', 'large.zip', true); // true 啟用續傳
bytes=start- (尾部)、bytes=-end (頭部) 等變體,正則需完整覆蓋。start <= end < file_size,否則回傳 416 Range Not Satisfiable。fread 區塊大小與 ob_flush() 實現應用層流控;生產環境建議用 Web Server 模組 (Nginx limit_rate) 更高效。fread 逐塊輸出,避免 file_get_contents 佔滿記憶體。output_buffering 不干擾 ob_flush(),或呼叫 ob_end_clean() 關閉 PHP 緩衝。Range: bytes=0-500,600-1000 需回傳 multipart/byteranges,複雜度較高,多數下載工具僅用單範圍。FileDownload 類別以純 PHP 實現標準 HTTP 斷點續傳協議,核心在於正確解析 Range、回應 206 與 Content-Range、精確 fseek 定位。結合限速、大檔案分塊輸出,即可構建生產級下載服務。