在 http response header 裡設 expire 以減少 request 數量

High Performance Web Sites: Rule 3 - Add an Expires Header 提到可以在 http response header 裡設 expire time 減少 client 送出 http request。藉由 last modified time 和 etag 只能省去 client 重下載檔案的時間, client 仍會送出 http request。若一個頁面載入十個檔案 (JavaScript、CSS、圖檔等), client 仍會送出十個 http request。別小看 http request 的時間, 數量一多若有其中一兩個被拖到, 網頁就會拖慢。

Apache 設定 expire 的方式很簡單:
  1. a2enmod expires  # enable mod_expires
  2. 編輯 /etc/apache/conf.d/cache.conf (檔名隨便取):

    ExpiresActive On
    ExpiresByType text/css "access plus 4 weeks"
    ExpiresByType text/js "access plus 4 weeks"
    ExpiresByType image/gif "access plus 4 weeks"
    ExpiresByType image/jpeg "access plus 4 weeks"
    ExpiresByType image/png "access plus 4 weeks"
    這樣會把 CSS、JavaScript 和常用圖檔設成使用者拿到檔案的四週後才會過期。
也可以設成依檔案修改時間來設過期時間, 而不是使用者拿到的時間, 參見 mod_expires 看更詳細的說明。

但設了 expire time 後帶出一個新問題, 萬一 server 端在 expire time 前有更新檔案怎麼辦? 在該頁的留言裡有討論幾種解法的優缺點:
  1. 加版本編號到檔名裡, 如 yahoo_2.0.6.js, Yahoo 是這麼做的。缺點是要改 code (HTML header、CSS 內文等), 也得想想怎麼和版本控制整合, 變成每次 deployment 就得改檔名。
  2. 加上 query string, 像是 base.css?v=CREATED_TIMESTAMP。缺點是有些 CDN 業者和 Proxy 不支援靜態檔案的 query string, 若它們忽略 query string, 靜態檔案就不會被更新了。也有人提到 HTTP 1.1 規定有 query string 的檔案不能被 cache, 雖說大部份瀏覽器仍會 cache。但也有人提到 HTTP 1.1 規定有送出 expire time 就能被 cache。無論如何, CDN 業者和 Proxy 的問題較頭大。
  3. 加上 virtual URL, 像是 /media/css/base.css 變成 /media/VERSION/css/base.css, 這樣就無 CDN、Proxy 的問題。不過不知道要怎麼在 web server 做對應的處置較好, 設 alias 忽略中間那段似乎是最簡單的解法? 即設個 alias 將 /media/VERSION/css/base.css 指到 /media/css/base.css。這樣 CSS 內用相對路徑取圖的話, 應該也不用改 CSS code。修改幅度最小。
附帶一提, 檢測用的相關工具:
  • Firefox 的 YSlow 會查那些靜態檔案沒設 expire time。
  • Firefox 的 Live HTTP Headers 會秀出目前連線的所有 http connection 的 request 和 response headers。可以用來查看 Expire 是否有被設對, 還有 client 是否真的沒發出 request。
  • Chrome 的 Speed Tracer 可以秀出頁面載入所有檔案的開始時間, 包含 http request、response 開始和結束的時間。也有 header 內的訊息。多種願望, 一次滿足!!
瀏覽器對待 expire time 的行為如下 (試出來的):
  • Firefox 按重新整理就不會理會 expire time, 在網址列按 Enter 重讀會參考 expire time。
  • Chrome 按重新整理或在網址列按 Enter 重讀都不會理會 expire time, 只有在網頁內點選的連結才會參考 expire time。

留言

  1. 第二步驟少寫了IfModule mod_expires.c開頭以及結尾/IfModule
    難怪一開始設不起來...

    回覆刪除
  2. 有執行 a2enmod expires 的話應該OK才對, 加 IfModule 的用意是, 沒 enable 時就忽略那段設定

    回覆刪除
  3. 那可能是YSlow的問題
    YSlow一直說我沒有Enable expire time
    可是裝了Live HTTP Header明明就有看到一個月後expire...

    回覆刪除
  4. 然後第二次Firefox就不會發request了,除非按下Ctrl+F5

    回覆刪除

張貼留言

這個網誌中的熱門文章

(C/C++ ) 如何在 Linux 上使用自行編譯的第三方函式庫

熟悉系統工具好處多多

virtualbox 使用 USB 裝置