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

PHP 文件下載完整指南:Header 控制、大文件支持、安全防範

     編輯:PHP 相關知識

為什麼需要 PHP 實現下載?

直接 `下載` 遇到瀏覽器能識別的類型(txt、pdf、jpg、mp4)會**直接在頁面打開**而非彈出下載框。PHP 通過發送特定 HTTP Header,強制瀏覽器觸發「另存為」對話框,同時支持權限控制、防盜鏈、大文件斷點續傳、下載統計等高級能力。

最小可用示例

<?php
// download.php?file=report.pdf
$filename = basename($_GET['file'] ?? ''); // 基礎防目錄遍歷
$filepath = "/var/www/uploads/{$filename}";

if (!file_exists($filepath) || !is_file($filepath)) {
    http_response_code(404);
    exit('文件不存在');
}

// 核心 Header 三件套
header('Content-Type: application/octet-stream'); // 通用二進制流,強制下載
header('Content-Disposition: attachment; filename="' . rawurlencode($filename) . '"'); // 關鍵:attachment + 編碼文件名
header('Content-Length: ' . filesize($filepath)); // 支持進度條、斷點續傳基礎
header('Content-Transfer-Encoding: binary'); // 聲明二進制傳輸
header('Accept-Ranges: bytes'); // 支持 Range 請求(斷點續傳)
header('Cache-Control: private, must-revalidate'); // 私有緩存,不進 CDN

readfile($filepath); // 直接輸出文件內容(內存友好,PHP 內部流式讀取)
?>

Content-Type 精準匹配表

雖然 `application/octet-stream` 通用,但設置正確 MIME 類型有助於瀏覽器識別、殺毒掃描、預覽插件:

最佳實踐:使用 `finfo` 自動檢測,回退到 `application/octet-stream`:

$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $filepath) ?: 'application/octet-stream';
finfo_close($finfo);
header("Content-Type: {$mime}");

大文件下載:內存安全與斷點續傳

1. readfile() vs fread() 循環

  • readfile($path):PHP 內部使用流式讀取,**不會將整個文件載入內存**,適合 100MB-1GB 文件。但無法處理 Range 請求。
  • fread() 循環:可精確控制輸出位置,支持 **HTTP Range 斷點續傳**,必須手動發送 `206 Partial Content`。

2. 斷點續傳完整實現(支持多線程下載器)

<?php
function sendFileWithRangeSupport(string $filepath, string $filename): void {
    if (!is_file($filepath)) { http_response_code(404); exit; }

    $size = filesize($filepath);
    $mime = mime_content_type($filepath) ?: 'application/octet-stream';

    // 解析 Range 頭
    $range = $_SERVER['HTTP_RANGE'] ?? '';
    $start = 0;
    $end = $size - 1;
    $statusCode = 200;

    if (preg_match('/bytes=(\d+)-(\d*)/', $range, $matches)) {
        $start = (int)$matches[1];
        $end = $matches[2] !== '' ? (int)$matches[2] : $size - 1;
        if ($start > $end || $start >= $size) {
            header('HTTP/1.1 416 Range Not Satisfiable');
            header("Content-Range: bytes */{$size}");
            exit;
        }
        $statusCode = 206; // Partial Content
    }

    $length = $end - $start + 1;

    // 發送 Header
    header("HTTP/1.1 {$statusCode} " . ($statusCode === 206 ? 'Partial Content' : 'OK'));
    header("Content-Type: {$mime}");
    header('Content-Disposition: attachment; filename="' . rawurlencode($filename) . '"');
    header("Content-Length: {$length}");
    header("Content-Range: bytes {$start}-{$end}/{$size}");
    header('Accept-Ranges: bytes');
    header('Cache-Control: private, must-revalidate');

    // 流式輸出
    $fp = fopen($filepath, 'rb');
    fseek($fp, $start);
    $buffer = 1024 * 8; // 8KB 緩衝
    while (!feof($fp) && $length > 0) {
        $read = min($buffer, $length);
        echo fread($fp, $read);
        $length -= $read;
        // 可選:ob_flush(); flush(); // 實時推送到瀏覽器
    }
    fclose($fp);
}

// 調用
sendFileWithRangeSupport('/data/large_video.mp4', 'video.mp4');

安全防範清單

| 風險 | 攻擊向量 | 防禦代碼 | |---|---|---| | **目錄遍歷** | `download.php?file=../../etc/passwd` | `basename()` + `realpath()` 對比允許根目錄 | | **任意文件讀取** | 直接訪問 `.env`、`config.php` | 文件存放在 **Web 根目錄之外**,PHP 讀取後輸出 | | **中文文件名亂碼** | `filename=中文.pdf` | `rawurlencode($filename)` + `filename*=UTF-8''` (RFC 5987) | | **內存耗盡** | 10GB 文件 `readfile` | 流式輸出、Nginx `X-Accel-Redirect` 卸載 | | **盜鏈/未授權下載** | 直接訪問下載鏈接 | 驗證 Session/JWT、簽名 URL 過期時間、Referer 檢查 |

企業級最佳實踐:Nginx X-Accel-Redirect (X-Sendfile)

**核心思想**:PHP 只負責權限驗證、生成簽名 URL,實際文件傳輸交給 **Nginx/Apache** 高性能靜態文件服務,PHP 進程瞬間釋放,支持萬級併發下載。

<?php
// PHP 控制器
public function download(Request $request) {
    $file = File::findOrFail($request->id);
    // 1. 權限檢查
    if (!$this->canDownload($file)) abort(403);
    // 2. 記錄下載日志
    DownloadLog::create(['file_id' => $file->id, 'user_id' => auth()->id()]);
    // 3. 返回特殊 Header,Nginx 攔截並發送文件
    return response('', 200, [
        'X-Accel-Redirect' => "/protected-files/{$file->stored_path}", // 內部路徑
        'Content-Disposition' => 'attachment; filename="' . rawurlencode($file->original_name) . '"',
        'Content-Type' => $file->mime_type,
    ]);
}

# Nginx 配置
location /protected-files/ {
    internal;                    # 只允許內部重定向,外部直接訪問 404
    alias /data/uploads/;        # 真實文件存儲路徑
    # 可選:限速、斷點續傳、Token 驗證模塊
}

總結

PHP 文件下載看似簡單,實則包含 MIME 類型識別、Content-Disposition 編碼、Range 斷點續傳、內存安全流式輸出、權限防盜鏈、Nginx 卸載傳輸 等工程細節。按需選型:小文件用 `readfile()`,大文件/斷點續傳用 `fread` 流式 + Range,高併發生產環境必上 `X-Accel-Redirect`。記住:**文件存 Web 目錄外,權限在 PHP 層驗,傳輸交 Web Server 層跑**,這是現代架構的鐵律。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
副檔名MIME 類型PHP 獲取方式
.pdfapplication/pdfmime_content_type($path) 或 finfo_file(finfo_open(), $path)
.jpg .jpegimage/jpeg同上
.pngimage/png同上
.zipapplication/zip同上
.rarapplication/x-rar-compressed同上
.docxapplication/vnd.openxmlformats-officedocument.wordprocessingml.document同上
.xlsxapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet同上
.mp4video/mp4同上
.apkapplication/vnd.android.package-archive同上