正確設定快取標頭可減少頻寬、降低伺服器負載、提升使用者體驗。PHP header() 函數可輸出原始 HTTP 標頭,結合 Last-Modified、ETag、Cache-Control 等機制實現精細控制。
| 標頭 | HTTP 版本 | 說明 |
|---|---|---|
Last-Modified | 1.0 | 資源最後修改時間(GMT 格式) |
Expires | 1.0 | 快取過期絕對時間(GMT) |
Pragma: no-cache | 1.0 | 舊版相容,強制不快取 |
Cache-Control | 1.1 | 現代標準,指令豐富(max-age, no-store, must-revalidate 等) |
public:可被瀏覽器、Proxy 等共享快取private:僅瀏覽器私有快取(含用戶隱私資料)no-cache:快取前必須向伺服器驗證(搭配 ETag/Last-Modified)no-store:完全不快取(敏感資料必須)max-age=<秒數>:快取有效期(秒),優先於 Expiress-maxage=<秒數>:針對共享快取(Proxy/CDN)覆蓋 max-agemust-revalidate:過期後必須驗證,不可使用過期副本<?php
// 1. 取得資料最後異動時間(範例:從資料庫查詢)
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$stmt = $pdo->query("SELECT MAX(updated_at) AS last_mod FROM articles");
$row = $stmt->fetch();
$lastModTimestamp = strtotime($row['last_mod']);
// 2. 設定快取間隔(6 小時)
$interval = 6 * 3600; // 秒
// 3. 輸出快取標頭
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModTimestamp) . ' GMT');
header('Expires: ' . gmdate('D, d M Y H:i:s', $lastModTimestamp + $interval) . ' GMT');
header('Cache-Control: max-age=' . $interval . ', must-revalidate, public');
// 4. 支援條件請求(304 Not Modified)
$etag = '"' . md5($lastModTimestamp) . '"';
header('ETag: ' . $etag);
if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] === $etag) {
header('HTTP/1.1 304 Not Modified');
exit;
}
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
$clientTime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
if ($clientTime >= $lastModTimestamp) {
header('HTTP/1.1 304 Not Modified');
exit;
}
}
// 5. 正常輸出內容
echo "<h1>文章列表</h1>...";
?>
<?php
header('Cache-Control: no-cache, no-store, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // 過去時間
?>
ETag 與 Last-Modified 實作條件請求,可回傳 304 Not Modified 節省頻寬。s-maxage 區別瀏覽器與邊緣快取期限。