在 PHP 開發中,header() 函數是控制 HTTP 回應標頭的核心工具。無論是實現頁面重定向、設定快取策略、觸發檔案下載,還是回傳特定 HTTP 狀態碼,都離不開它。本文將系統整理 header() 的進階用法、常見陷阱與最佳實踐,幫助開發者精準掌握 HTTP 標頭控制的藝術。
許多開發者知道「多個相同 header() 以最後一個為準」,但忽略了前置條件:前面的標頭必須尚未被實際發送。一旦有任何輸出(包含空白字元、BOM、echo 等),標頭即鎖定無法修改。
<?php
// 情況 A:無輸出干擾,最終跳轉至 baidu.com
header('Location: https://www.jb51.net');
header('Location: https://www.g.cn');
header('Location: https://www.baidu.com');
// 情況 B:第一行輸出空字串,導致標頭提前發送,最終跳轉至 g.cn
header('Location: https://www.jb51.net');
echo ''; // 關鍵:任何輸出都會刷新標頭緩衝區
header('Location: https://www.g.cn');
header('Location: https://www.baidu.com');
?>
最佳實踐:在所有 header() 呼叫前確保零輸出,並在重定向後立即執行 exit; 防止後續程式碼執行。
HTTP 採用請求/回應範式:客戶端(瀏覽器/爬蟲)建立連線 → 發送請求行 + 標頭 + 主體 → 伺服器回應狀態行 + 標頭 + 主體 → 連線關閉(HTTP/1.1 預設 Keep-Alive)。
根據 RFC 2616,以下標頭在傳統規範中只能出現一次:
Location: <URI> — 重定向目標Content-Type: <MIME-type> — 實體內容類型Status: <code> <reason> — 回應狀態(CGI 模式專用)現代 Multipart MIME 規範允許同名標頭重複(如多個 Set-Cookie),但上述三者在單一回應中仍建議唯一。
| 分類 | 範圍 | 含義 | 常見代碼 |
|---|---|---|---|
| 資訊回應 | 1xx | 請求已接收,繼續處理 | 100 Continue |
| 成功回應 | 2xx | 請求成功被接收、理解、接受 | 200 OK、201 Created、204 No Content |
| 重定向 | 3xx | 需進一步行動以完成請求 | 301 永久移動、302 臨時移動、304 未修改 |
| 客戶端錯誤 | 4xx | 請求語法錯誤或無法滿足 | 400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found |
| 伺服器錯誤 | 5xx | 伺服器無法完成合法請求 | 500 Internal Server Error、502 Bad Gateway、503 Service Unavailable |
<?php
header('Location: https://www.php.net');
exit; // 強制終止腳本,避免後續輸出干擾
?>
<?php
// 1. 設定過期時間為過去日期
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
// 2. 標示最後修改時間為「現在」
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
// 3. HTTP/1.1 標準快取控制
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Cache-Control: post-check=0, pre-check=0', false); // 相容 IE
// 4. HTTP/1.0 相容
header('Pragma: no-cache');
// 5. 若為檔案下載,補充 Content-Disposition
// header('Content-Disposition: attachment; filename="report.xlsx"');
?>
關鍵差異:no-store 禁止任何儲存(含磁碟),no-cache 允許儲存但每次需向伺服器驗證。生產環境建議兩者合用。
<?php
// 401 未授權 — 觸發瀏覽器彈出登入框
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Basic realm="Admin Area"');
exit;
// 404 未找到 — 隱藏真實路徑、誤導掃描器
header('HTTP/1.1 404 Not Found');
header('Status: 404 Not Found'); // CGI/FastCGI 相容
// 可選:include('404.html'); exit;
?>
<?php
$filePath = '/data/exports/report_2024.xlsx';
$fileName = '年度報表_2024.xlsx';
if (!is_file($filePath)) {
header('HTTP/1.1 404 Not Found');
exit('檔案不存在');
}
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Length: ' . filesize($filePath));
header('Content-Disposition: attachment; filename="' . rawurlencode($fileName) . '"');
header('Accept-Ranges: bytes');
readfile($filePath); // 高效輸出大檔,避免記憶體耗盡
exit;
?>
header() 失效。請儲存為「UTF-8 無 BOM」。warning/notice 會輸出內容。開發階段開啟 error_reporting(E_ALL),上線前設定 display_errors=Off 並記錄至日誌。$_SERVER['HTTP_REFERER'] 或使用 Session 標記。Content-Type、Location 等 Pascal-Case 風格。在現代框架(Laravel、Symfony、Slim)中,建議使用標準化的 Response 物件:
// Laravel 範例
return response()->json($data, 200, ['X-Custom-Header' => 'value'])
->header('Cache-Control', 'no-cache, private');
// 原生 PSR-7 範例
use Nyholm\Psr7\Response;
$response = new Response(200, ['Content-Type' => 'application/json'], json_encode($data));
$response = $response->withHeader('Cache-Control', 'no-cache');
優點:可測試性高、避免「標頭已發送」錯誤、支援中介軟體管線。
header() 是 PHP 與 HTTP 協議溝通的橋樑。掌握其「零輸出前置條件」、「單一標頭唯一性」、「快取控制組合拳」與「狀態碼語義」,能讓你精準控制瀏覽器行為、優化 SEO、強化安全性。下一步建議深入研讀 RFC 7231 與 RFC 7234,建立更堅實的 HTTP 知識體系。