在 Web 開發中,提供檔案下載是常見需求。PHP 透過 `header()` 函式控制 HTTP 回應標頭,即可實現瀏覽器「另存新檔」提示。本文介紹兩種主流實作方式,並說明關鍵標頭用途。
適用於小型檔案、不需斷點續傳的簡單場景。
<?php
$file = '/path/to/your/file.pdf'; // 檔案絕對路徑
$filename = 'download.pdf'; // 瀏覽器顯示的檔名
if (!file_exists($file)) {
header('HTTP/1.1 404 Not Found');
exit('檔案不存在');
}
// 關鍵標頭設定
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Transfer-Encoding: binary');
header('Content-Length: ' . filesize($file));
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Expires: 0');
// 輸出檔案內容
readfile($file);
exit;
?>
大型檔案下載建議支援 HTTP Range 標頭,允許使用者暫停/繼續下載。
<?php
$file = '/path/to/large/video.mp4';
$filename = 'video.mp4';
if (!file_exists($file)) {
header('HTTP/1.1 404 Not Found');
exit;
}
$size = filesize($file);
$ext = pathinfo($file, PATHINFO_EXTENSION);
$mime = 'application/octet-stream'; // 或使用 mime_content_type($file)
// 處理 Range 標頭
$start = 0;
$end = $size - 1;
header('Accept-Ranges: bytes');
if (isset($_SERVER['HTTP_RANGE'])) {
$range = $_SERVER['HTTP_RANGE'];
if (preg_match('/bytes=(\d+)-(\d*)/', $range, $matches)) {
$start = intval($matches[1]);
$end = isset($matches[2]) && $matches[2] ? intval($matches[2]) : $size - 1;
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes ' . $start . '-' . $end . '/' . $size);
}
}
$length = $end - $start + 1;
header('Content-Type: ' . $mime);
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Length: ' . $length);
header('Content-Transfer-Encoding: binary');
// 以區塊讀取輸出,避免記憶體耗盡
$fp = fopen($file, 'rb');
fseek($fp, $start);
while (!feof($fp) && $length > 0) {
$chunk = min(8192, $length);
echo fread($fp, $chunk);
$length -= $chunk;
flush();
}
fclose($fp);
exit;
?>