瀏覽器快取¶
一般背景:為何瀏覽器快取是件好事¶
在任何網站上,讓瀏覽器快取靜態物件(例如不包含使用者內容,且在數小時、數天或數週內都相同)是一種良好的做法。
(這通常指的是像 .css、.js 和圖片檔這樣的檔案)
但有些內容會快速變更,因此不能被瀏覽器快取。
如果瀏覽器被告知「不要快取這個物件」
那麼它就知道不要快取。這是在可能變更的頁面上,網站(和 Redmine)會做的事情:例如,在議題頁面上,它會將訊息放入 HTTP 標頭中。
但是,如果沒有這個訊息,也就是說,如果瀏覽器不確定它最近下載的頁面元件
它會向伺服器發送 304 請求:詢問「你能告訴我,這個檔案是否還沒有過期嗎?」。對於 304 請求,伺服器不需要再次發送整個物件:只需要一個簡短的「是的,它仍然是新的」回應。
在 Redmine 的預設設定中,它會在每個頁面上產生大量的 304 連線:您可以在像 Firebug 這樣的工具中看到這些連線。每個 .css 和 .js 檔案等等都會有一個連線。非常多。
這些對使用者體驗不利:因為瀏覽器必須等待這些回應,然後才能繼續建構頁面。
所以,我們需要告訴瀏覽器,這些物件在很長一段時間內都不會過期
有一些簡單的方法可以設定 Apache 和 nginx 來做到這一點:告訴它們在 HTTP 標頭中設定一個遙遠的「過期」日期:這樣瀏覽器就知道:「好的,這個物件沒有過期,因為我們還沒有超過過期日期」。
因此,當新使用者訪問 Redmine 時,他們的瀏覽器在第一個頁面上會 GET .css 和 .js 檔案等,但在之後的頁面上就不需要再次取得它們了。
使用者將會體驗到更快的網頁建構速度!
Redmine 的問題¶
不幸的是,Redmine(相當不明智地)將 .js 檔名用於確實包含使用者內容的內容:也就是說,不應該在瀏覽器中快取的內容。
所以,這意味著:如果您在 Apache/nginx 中添加了一個簡單的設定,用於快取任何名稱為 *.js 的內容:那麼您的 Redmine 就會出問題!
請參閱議題 #17770 - 這個問題在這裡被報告,看看 Redmine 團隊是否可以改變它,停止在不適當的地方使用 .js。
如果您使用簡單的配置,它會在以下情況中斷:(a) 在問題中編輯日誌時 (b) 將檔案上傳到問題時
Redmine 的解決方法¶
簡單的情況,會像上面那樣破壞 Redmine,因為它不關心 js 檔案所在的目錄:例如
location ~* \.(ico|css|js|gif|jp?g|png)(\?[0-9]+)?$ { expires 365d; }
所以需要使用更複雜的配置:在設置緩存標頭之前,還要檢查 .js 檔案所在的目錄。
我讓它運作起來了
# the regex logic: after either /javascripts/ or /stylesheets/ find the suffixes we want, followed by any quantity of numbers 0-9 # This works because the files we want to cache always appear after one of those 2 directories: but not the files we want to ignore # /journals/edit/24174.js and /uploads.js?attachment_id=1&filename=my-file-to-upload.png location ~* /(?:(?:plugin_assets|themes).+)?(javascripts|stylesheets|images|favicon).+(css|js|jpg|gif|ico|png)(\?[0-9]+)$ { # add_header X-SV-test 304-killer; use this do-nothing HTTP Header, if you need to play with the regexp #- for testing without fear of breaking anything! expires 365d; }
從 Redmine 3.4.0 (#24617) 開始,靜態檔案請求會附加一個 ?timestamp,以避免升級 Redmine 版本和緩存提供錯誤檔案時出現問題。 但是,自定義主題不會在請求中附加時間戳,因此 application.css 和 responsive.css 可能會嚴重損壞。 有關解決方法,請參閱 #29625。
如果您想確保安全,請勿緩存沒有 ?timestamp 的檔案
- # the regex logic: after either /javascripts/ or /stylesheets/ find the suffixes we want, followed by any quantity of numbers 0-9
+ # the regex logic: after either /javascripts/ or /stylesheets/ find the suffixes we want, followed by one or more numbers 0-9
# This works because the files we want to cache always appear after one of those 2 directories: but not the files we want to ignore
# /journals/edit/24174.js and /uploads.js?attachment_id=1&filename=my-file-to-upload.png
- location ~* /(?<file>/(?:(?:plugin_assets|themes).+)?(?:javascripts|stylesheets|images|favicon).+(?:css|js|jpe?g|gif|ico|png|html|woff|ttf|svg)(\?[0-9]+)?$) {
+ location ~* /(?<file>/(?:(?:plugin_assets|themes).+)?(?:javascripts|stylesheets|images|favicon).+(?:css|js|jpe?g|gif|ico|png|html|woff|ttf|svg)(\?[0-9]+)$) {
請注意,在 nginx 中:'expires' 是在物件中設置 'Expiry' HTTP 標頭的配置。
在上面,365d 表示 365 天。
有關完整的 nginx 'expires' 配置詳細信息,請參閱:https://nginx.dev.org.tw/en/docs/http/ngx_http_headers_module.html#expires
對於 Apache:請參閱 mod_expires:https://apache-httpd.dev.org.tw/docs/2.2/mod/mod_expires.html