首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 斷點續傳下載類實作:支援 HTTP Range 標頭的大檔案下載

     編輯:PHP 相關知識

PHP 斷點續傳下載類實作

大檔案下載常因網路中斷而失敗。FileDownload 類別實作 HTTP/1.1 Range 與 Content-Range 標頭,支援 wget -c、瀏覽器下載工具續傳功能。

HTTP 斷點續傳原理

  • Range(請求標頭):Range: bytes=200-300 指定欲下載位元組範圍
  • Content-Range(回應標頭):Content-Range: bytes 200-300/1000 回應實際範圍與總大小
  • 206 Partial Content:續傳成功狀態碼
  • Accept-Ranges: bytes:宣告支援位元組範圍請求

FileDownload 類別程式碼

<?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 '檔案不存在';
?>

測試驗證 (wget)

# 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

生產環境建議

  • 加入 Token 驗證防止熱連結
  • 大檔案建議使用 X-Sendfile (Apache) 或 X-Accel-Redirect (Nginx) 卸載 PHP 進程
  • 設定合理的 set_time_limit(0) 避免大檔案中斷
  • 記錄下載 Log 供稽核
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved