在 Web 開發中,最直觀的下載方式是直接提供檔案連結(`下載`)。然而,當瀏覽器能識別並直接開啟特定檔案類型(如圖片、HTML、PDF、文字檔)時,點擊連結會導致瀏覽器在新分頁預覽,而非彈出「另存新檔」對話框。這對於需要受控下載(如權限驗證、下載統計、防盜鏈)的場景顯然不符合需求。
透過 PHP 發送特定的 HTTP Header,指示瀏覽器將回應內容視為附件處理,關鍵在於 Content-Disposition: attachment 標頭。搭配 Content-Type: application/octet-stream(通用二進制流)與 Content-Length,即可實現跨瀏覽器的強制下載行為。
<?php
// 1. 參數驗證:防止目錄遍歷與非法存取
$fileName = isset($_GET['file']) ? basename($_GET['file']) : '';
$fileDir = '/var/www/downloads/'; // 固定儲存目錄,不可由用戶控制
if (empty($fileName) || !preg_match('/^[\w\-\.]+$/', $fileName)) {
http_response_code(400);
exit('非法檔案名稱');
}
$filePath = $fileDir . $fileName;
// 2. 檔案存在性與可讀性檢查
if (!is_file($filePath) || !is_readable($filePath)) {
http_response_code(404);
exit('檔案不存在或不可讀');
}
// 3. 發送下載標頭
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . rawurlencode($fileName) . '"');
header('Content-Length: ' . filesize($filePath));
header('Accept-Ranges: bytes'); // 支援斷點續傳
header('Cache-Control: private, must-revalidate'); // 避免 Proxy 快取干擾
header('Pragma: public'); // HTTP/1.0 相容
// 4. 分塊輸出:避免大檔案佔用過多記憶體
$handle = fopen($filePath, 'rb');
if ($handle) {
while (!feof($handle)) {
echo fread($handle, 8192); // 8KB 緩衝區
@ob_flush();
@flush();
}
fclose($handle);
}
exit;
?>
若檔案儲存於 CDN 或物件存儲(S3/OSS),可使用 `fopen($remoteUrl, 'rb')` 串流轉發,但需注意:
程式化下載不僅解決了「瀏覽器直接開啟」的問題,更是實現下載權限控管、防盜鏈、統計分析的基礎。掌握 HTTP 標頭機制與串流輸出技巧,是 PHP 後端工程師的必備技能。