核心規則
- 必須在任何輸出前調用(HTML、空白、echo、print、BOM)。
- PHP 4+ 可用
ob_start() 開啟輸出緩衝解決「headers already sent」。
header() 發送原始 HTTP 報頭,不自動換行。
常用報頭類型
1. 重定向
<?php
// 302 臨時重定向 (默認)
header('Location: https://example.com/login.php');
exit; // 關鍵:阻止後續代碼執行
// 301 永久重定向 (SEO 友好)
header('Location: https://example.com/new-url', true, 301);
exit;
// 相對路徑 (不推薦,RFC 7231 要求絕對 URI)
// header('Location: /login.php'); // 瀏覽器通常兼容
?>
2. 緩存控制
<?php
// 禁用緩存 (動態頁面、API)
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Cache-Control: post-check=0, pre-check=0', false); // HTTP/1.0
header('Pragma: no-cache'); // HTTP/1.0 兼容
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // 過去時間
// 靜態資源長期緩存 (一年)
header('Cache-Control: public, max-age=31536000, immutable');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT');
// 驗證緩存 (Last-Modified / ETag)
$lastMod = filemtime('data.json');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastMod) . ' GMT');
$etag = md5_file('data.json');
header('ETag: "' . $etag . '"');
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $lastMod) {
header('HTTP/1.1 304 Not Modified');
exit;
}
if (isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
trim($_SERVER['HTTP_IF_NONE_MATCH'], '"') === $etag) {
header('HTTP/1.1 304 Not Modified');
exit;
}
?>
3. 文件下載
<?php
$file = '/var/www/files/report.pdf';
$filename = 'monthly_report.pdf';
if (!is_file($file)) {
header('HTTP/1.1 404 Not Found');
exit;
}
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="' . rawurlencode($filename) . '"');
header('Content-Length: ' . filesize($file));
header('Content-Transfer-Encoding: binary');
header('Accept-Ranges: bytes'); // 支持斷點續傳
// 大文件分塊輸出,避免內存溢出
$fp = fopen($file, 'rb');
while (!feof($fp)) {
echo fread($fp, 8192);
flush(); // 發送輸出緩衝
}
fclose($fp);
exit;
?>
4. Content-Type 常用值
| 類型 | 值 |
| JSON | application/json; charset=utf-8 |
| XML | application/xml; charset=utf-8 |
| HTML | text/html; charset=utf-8 |
| 純文本 | text/plain; charset=utf-8 |
| CSV | text/csv; charset=utf-8 |
| PDF | application/pdf |
| ZIP | application/zip |
| 圖片 | image/jpeg, image/png, image/gif, image/webp |
| 字體 | font/woff2, font/woff |
5. 自定義報頭與 CORS
<?php
// CORS 跨域允許
header('Access-Control-Allow-Origin: https://app.example.com'); // 或 *
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400'); // 預檢緩存 1天
// 處理 OPTIONS 預檢請求
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit(0);
}
// 自定義報頭 (X- 前綴慣例)
header('X-Request-ID: ' . uniqid());
header('X-RateLimit-Limit: 1000');
header('X-RateLimit-Remaining: 999');
?>
header() 參數詳解
header(string $header, bool $replace = true, int $http_response_code = 0): void
$replace:默認 true,替換同名報頭;false 允許同名多值(如多個 `Set-Cookie`)。
$http_response_code:強制設置狀態碼(PHP 5.4+ 可用 http_response_code())。
常見錯誤與調試
<?php
// 錯誤:輸出前有空白/BOM
// 輸出緩衝解決
ob_start();
// ... 業務代碼 ...
header('Location: /login');
ob_end_flush();
// 調試:查看已發送報頭
header('X-Debug: test');
$headers = headers_list();
print_r($headers);
// 檢查是否已發送
if (headers_sent($file, $line)) {
error_log("Headers already sent in $file:$line");
}
?>
總結
- 零輸出前調用是鐵律,善用
ob_start()。
- 重定向必加
exit。
- 緩存控制區分動態禁用與靜態長期,善用
Last-Modified/ETag 驗證。
- 文件下載大文件分塊輸出 + flush,設置
Content-Length 支持進度條。
- API 統一輸出
Content-Type: application/json; charset=utf-8。