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

PHP 檔案下載完整實作:從基礎 Header 到中文檔名、大檔案分塊傳輸

     編輯:PHP 綜合知識

核心原理:HTTP Header 控制瀏覽器行為

瀏覽器預設會嘗試直接顯示圖片、HTML、PDF 等已知 MIME 類型。透過發送特定 Header 強制「另存新檔」對話框:

header('Content-Type: application/octet-stream');        // 通用二進位流
header('Content-Disposition: attachment; filename="test.gif"'); // 附件模式+檔名
header('Content-Length: ' . filesize($file));            // 檔案大小(支援進度條)
header('Accept-Ranges: bytes');                          // 支援斷點續傳

基本實作:readfile() 一行搞定小檔案

function downloadFile(string $path, string $filename = ''): void
{
    if (!file_exists($path)) {
        http_response_code(404);
        exit('檔案不存在');
    }

    $filename = $filename ?: basename($path);
    $size = filesize($path);

    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="' . $filename . '"');
    header('Content-Length: ' . $size);
    header('Cache-Control: private, must-revalidate'); // 避免代理快取干擾
    header('Pragma: public');

    readfile($path); // 直接輸出檔案內容
    exit;
}

// 使用
downloadFile('/data/uploads/report.pdf', '2024_Q1_Report.pdf');

進階:中文檔名跨瀏覽器相容

RFC 5987 標準支援 UTF-8 編碼檔名,舊版 IE 需要 fallback:

function setDownloadFilename(string $filename): void
{
    $encoded = rawurlencode($filename);
    // 現代瀏覽器:UTF-8 編碼
    // 舊版 IE:不支援 encoding,需用 urlencode
    header("Content-Disposition: attachment; filename*=UTF-8''{$encoded}; filename=\"{$encoded}\"");
}

// 測試
setDownloadFilename('測試報告_2024.pdf');

大檔案分塊傳輸:避免記憶體耗盡

readfile() 會將整個檔案讀入記憶體,大檔案需分塊輸出:

function downloadLargeFile(string $path, string $filename = ''): void
{
    if (!file_exists($path)) { http_response_code(404); exit('檔案不存在'); }

    $filename = $filename ?: basename($path);
    $size = filesize($path);
    $handle = fopen($path, 'rb');

    if (!$handle) { http_response_code(500); exit('無法開啟檔案'); }

    // Header
    header('Content-Type: application/octet-stream');
    setDownloadFilename($filename);
    header('Content-Length: ' . $size);
    header('Accept-Ranges: bytes');

    // 斷點續傳支援
    if (isset($_SERVER['HTTP_RANGE'])) {
        $range = $_SERVER['HTTP_RANGE'];
        if (preg_match('/bytes=(\d+)-(\d*)/', $range, $matches)) {
            $start = (int)$matches[1];
            $end = $matches[2] ? (int)$matches[2] : $size - 1;
            $length = $end - $start + 1;
            
            fseek($handle, $start);
            header('HTTP/1.1 206 Partial Content');
            header("Content-Range: bytes $start-$end/$size");
            header('Content-Length: ' . $length);
        }
    }

    // 分塊輸出 (8KB per chunk)
    while (!feof($handle) && connection_status() === CONNECTION_NORMAL) {
        echo fread($handle, 8192);
        @ob_flush();
        @flush();
    }

    fclose($handle);
    exit;
}

X-Sendfile / X-Accel-Redirect:Web Server 層級加速

生產環境建議讓 Nginx/Apache 直接傳檔,PHP 僅驗證權限:

// Nginx 設定 (nginx.conf)
location /protected/ {
    internal;           // 僅允許內部重導向
    alias /data/files/; // 實體檔案路徑
}

// PHP 端
function sendFileViaNginx(string $internalPath, string $filename): void
{
    // 權限檢查...
    
    header('X-Accel-Redirect: /protected/' . $internalPath);
    header('Content-Disposition: attachment; filename="' . rawurlencode($filename) . '"');
    header('Content-Type: application/octet-stream');
    exit;
}

完整檢查清單

  • [ ] 檔案存在性檢查
  • [ ] 權限驗證(用戶是否有權下載)
  • [ ] MIME 類型正確設定(或用 application/octet-stream)
  • [ ] 中文檔名 RFC 5987 編碼
  • [ ] Content-Length 正確(支援進度條、斷點續傳)
  • [ ] 大檔案分塊輸出 + connection_status 檢測
  • [ ] 生產環境啟用 X-Sendfile/X-Accel-Redirect
  • [ ] HTTPS 環境下設定 Cache-Control: private 避免代理快取敏感檔案
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved