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

PHP 簡易檔案下載實作:斷點續傳、大檔案記憶體控制

     編輯:PHP 相關知識

PHP 簡易檔案下載實作:斷點續傳、大檔案記憶體控制

直接 readfile() 會將整個檔案載入記憶體,大檔案易導致 OOM。正確做法:分塊讀取輸出、支援 Range 請求 (斷點續傳/多執行緒下載)、正確設定 HTTP 標頭。

基礎分塊下載 (無斷點續傳)

<?php
$filePath = '/data/files/report.pdf';
$fileName = 'report.pdf';

if (!is_file($filePath)) { http_response_code(404); exit('File not found'); }

$fileSize = filesize($filePath);
$chunkSize = 1024 * 1024; // 1MB

header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . rawurlencode($fileName) . '"');
header('Content-Length: ' . $fileSize);
header('Accept-Ranges: bytes'); // 宣告支援範圍請求
header('Cache-Control: no-cache, must-revalidate');
header('Pragma: public');

$handle = fopen($filePath, 'rb');
while (!feof($handle) && connection_status() === CONNECTION_NORMAL) {
    echo fread($handle, $chunkSize);
    ob_flush(); flush(); // 立即推送給客戶端
}
fclose($handle);

完整支援 Range (斷點續傳/多線程)

<?php
function sendFile($filePath, $fileName = null) {
    if (!is_file($filePath)) { http_response_code(404); return false; }
    $fileName ??= basename($filePath);
    $fileSize = filesize($filePath);
    $chunkSize = 1024 * 1024;

    // 處理 Range 標頭
    $rangeHeader = $_SERVER['HTTP_RANGE'] ?? '';
    $start = 0;
    $end = $fileSize - 1;
    $partial = false;

    if (preg_match('/bytes=(\d+)-(\d*)/', $rangeHeader, $matches)) {
        $start = (int)$matches[1];
        $end = $matches[2] !== '' ? (int)$matches[2] : $fileSize - 1;
        if ($start > $end || $start >= $fileSize) {
            header('HTTP/1.1 416 Range Not Satisfiable');
            header('Content-Range: bytes */' . $fileSize);
            return false;
        }
        $partial = true;
    }

    $length = $end - $start + 1;

    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="' . rawurlencode($fileName) . '"');
    header('Content-Length: ' . $length);
    header('Accept-Ranges: bytes');
    if ($partial) {
        header('HTTP/1.1 206 Partial Content');
        header('Content-Range: bytes ' . $start . '-' . $end . '/' . $fileSize);
    }

    $handle = fopen($filePath, 'rb');
    fseek($handle, $start);
    $remaining = $length;
    while ($remaining > 0 && connection_status() === CONNECTION_NORMAL) {
        $readSize = min($chunkSize, $remaining);
        echo fread($handle, $readSize);
        ob_flush(); flush();
        $remaining -= $readSize;
    }
    fclose($handle);
    return true;
}

// 使用
sendFile('/data/files/large.zip', 'large.zip');

關鍵標頭說明

標頭作用
Content-Disposition: attachment強制瀏覽器彈出下載對話框,而非預覽
Content-Length告知瀏覽器總大小,支援進度條
Accept-Ranges: bytes宣告支援斷點續傳
Content-Range206 回應必須,指示本次傳輸區間
Cache-Control: no-cache避免代理快取導致下載損壞

生產環境建議

  • **Nginx X-Accel-Redirect / Apache X-Sendfile**:PHP 僅驗證權限,返回 X-Accel-Redirect: /protected/files/xyz,由 Web Server 高效發送靜態檔案,零 PHP 記憶體佔用、支援 sendfile 系統調用、自動處理 Range。
  • 大檔案 (>100MB) 必須用 Web Server 發送或對象儲存 (S3/OSS) 簽名直連。
  • 權限驗證:下載前檢查用戶身份、下載次數、頻率限制、防盜鏈 (Referer/Token)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved