當圖片、CSS、JS 等靜態資源由 PHP 動態輸出(如權限控管、水印、統計)時,若無快取機制,瀏覽器每次請求都會收到 200 OK 並重複下載,浪費頻寬與伺服器資源。正確的做法是利用 Last-Modified 與 ETag 回應標頭,配合請求標頭 If-Modified-Since、If-None-Match 實現 304 Not Modified。
_addEtag() 方法<?php
private function _addEtag($file) {
$lastModified = filemtime($file); // 檔案最後修改時間(Unix 時間戳)
$etag = md5_file($file); // 檔案內容 MD5 作為 ETag(強驗證器)
// 1. 總是發送快取驗證標頭
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
header('ETag: ' . $etag);
// 2. 檢查瀏覽器攜帶的條件請求標頭
$ifModifiedSince = isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])
? strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) : 0;
$ifNoneMatch = isset($_SERVER['HTTP_IF_NONE_MATCH'])
? trim($_SERVER['HTTP_IF_NONE_MATCH']) : '';
// 3. 滿足任一條件即回傳 304
if ($ifModifiedSince === $lastModified || $ifNoneMatch === $etag) {
header('HTTP/1.1 304 Not Modified');
exit; // 終止腳本,不輸出實體內容
}
}
<?php
// 輸出圖片前呼叫
$imagePath = '/path/to/photo.jpg';
_addEtag($imagePath);
// 發送內容型別與實體
header('Content-Type: image/jpeg');
readfile($imagePath); // 或 imagejpeg($gdResource)
md5_file() 針對內容計算雜湊,確保內容變更必然導致 ETag 改變(強驗證)。若效能敏感,可改用 spl_file_info::getSize() . '-' . $lastModified(弱驗證)。gmdate('D, d M Y H:i:s') 產生 RFC 7231 規範的 HTTP 日期(GMT)。If-None-Match 優先於 If-Modified-Since,但兩者均滿足即可回 304。strtotime():加 @ 抑制警告,避免惡意標頭導致錯誤日誌污染。Cache-Control 與 Expires// 公開快取 1 小時,瀏覽器可直接使用快取而不發請求
header('Cache-Control: public, max-age=3600');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 3600) . ' GMT');
配合 304 機制:首次請求 200 + 快取標頭 → 1 小時內直接讀快取 → 過期後發條件請求 → 304 或 200。
僅需數行代碼即可為 PHP 動態輸出的靜態資源加入專業級快取驗證,大幅降低頻寬與延遲。記得在輸出實體之前呼叫 _addEtag(),並確保檔案路徑真實存在。