打印暂停的地方

暂停打印.....暂停打印.......暂停打印...........

JSP: 避免用戶端快取網頁內容的幾種方法比較

作者:蔡煥麟
日期:Apr-5-2004


Problem

在有些情況下,我們會希望瀏覽器不要快取網頁內容,例如:系統剛上線時,檔案更新仍然很頻繁,我們會希望使用者立刻看到最新的內容。避免 JSP 網頁被瀏覽器快取的技巧已經很常見,通常是在 JSP 網頁裡面更改 Cache-Control 這個 response header 來達成。但是如果在網頁中有連結外部的 JavaScript 檔案(i.e. 不是直接嵌入 JSP 當中),這些 JavaScript 卻仍然會被瀏覽器 cache 起來 。使得當 JavaScript 檔案更新之後,瀏覽器還是讀取到舊的(快取中的)JavaScript 檔案。

本文實驗了幾種避免用戶端 cache 的解決方法,並且比較各種方法的優缺點之後,得到一個最佳的解決方法。


Experiment

如果你也想試著做看看,你至少必須準備:

  • Web server(例如:Tomcat);
  • 可以開發 Java 和 JSP 程式的工具(例如:UltraEdit 和 JDK 1.4);
  • 你最喜歡的瀏覽器,這裡我使用的是 Internet Explorer 6.0。

以下就是我實驗過的幾個方法和結果:

1. 按 IE 的〔重新整理〕 或者 F5

瀏覽測試的網頁之後,用「重新整理」的方式再瀏覽一次這個網頁。

實驗結果

這個方法毫無疑問可以令 IE 讀取新的網頁檔案,包括網頁連結的外部 JavaScript 檔案。

優點

開發人員不用撰寫程式。

缺點

使用者得自己知道要去按這個鈕才行。

2. 更改 IE 的選項,令其每次查閱畫面時都要下載新版本

這種方式可以令 IE 每次讀取到最新的檔案(我的 IE 版本是 6.0.2800.1106),而且不管 JSP 裡面有沒有加入上面的控制指令,JavaScript 都可以讀到最新的檔案。(前兩個實驗都是在還沒有更改這個選項時進行的,也就是此選項為 "自動")

缺點

無法針對特定的 Web 應用程式或網頁資源指定不要禁止快取,連帶使得瀏覽其他網站的速度也受到影響。

3. 在網址上加上額外參數欺騙 IE

像這樣(javascript):

window.location.href='mypage.jsp?t=<%=System.currentTimeMillis()%>';

實驗結果

只對 JSP 網頁有效,連結的外部檔案還是會讀到舊的。

但是另一種類似但卻比較極端的做法,卻可以做到完全避免用戶端快取。做法是把網頁中的每個超連結都另外加工編碼過,使得目標網頁的超連結每次都不一樣,以迫使瀏覽器無法快取網頁(因為同一個網頁下一次存取時,URL 就不一樣了),例如這個網頁:http://store.apple.com/1-800-MY-APPLE/WebObjects/AppleStore/,裡面的每個超連結都加上了一些無意義的文字,就是一個明顯的例子。以下取其中一個編碼過的 URL 作為範例,應該更能了解其原理:

<A href="http://store.apple.com/1-800-MY-APPLE/WebObjects/AppleStore.woa/ 70302/wo/F13dXXrKLPPg2CHcbI22dEnIDan/0.0.7.1.0.5.13.0.1.1.3.0.7.0.1.1.0" target="">Apple Accessories</a>

如果連網頁中連結的外部 JavaScript/CSS 檔案的 URL 都用這種編碼方式處理,也可以做到完全避免用戶端快取。只是要做到這樣,一方面要考慮開發的簡易性和維護性,另一方面,使用者會無法將特定網頁加到書籤(我的最愛)裡面,也是必須加以考慮的。

4. 在 JSP 裡面更改 Cache-Control header

在 JSP 裡面加上:
<% response.setHeader("Cache-Control", "private,no-cache,no-store"); // for HTTP 1.1 %>
或者
<head> <title>No Cache</title> <meta http-equiv="Cache-Control" content="private,no-cache,no-store"> </head>

測試

瀏覽測試的網頁之後,再瀏覽一次這個網頁,方法是將游標移到網址列,然後按 Enter 鍵。注意不可以用「重新整理」。

實驗結果

只對 JSP 有效,對 JSP 裡面所連結的外部 JavaScript 檔案無效。事實上,包括連結的 .css 檔案也無法自動更新,IE 還是會讀到舊的。這樣應該可以推測所有在 JSP/HTML 連結的外部檔案都會被 IE cache 住。

註:HTTP 1.0 的 Pragma 和 Expires header 也已經試過,因為跟上面的結果沒有差別,又是舊的東西,就不列出來。

5. 撰寫一個過濾器(filter)來更改 HTTP response header

撰寫一個過濾器(filter),並且在部署描述子(deployment descriptor,也就是 web.xml)中設定哪些 URL pattern 要經由這個過濾器處理。過濾器的程式碼如下:

package demoweb.filters; import java.io.IOException; import javax.servlet.*; import javax.servlet.http.*; import java.util.*; public class ResponseHeaderFilter implements Filter { private FilterConfig filterConfig = null; public void destroy() { this.filterConfig = null; } public void doFilter( ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; // 監視哪些 request 被送出,如果輸出到檔案,就跟 Web log 差不多了. System.out.println(request.getRequestURL()); // 根據 filter 初始化參數來設定 HTTP response parameters. for (Enumeration e = filterConfig.getInitParameterNames(); e.hasMoreElements();) { String headerName = (String)e.nextElement(); response.addHeader(headerName, filterConfig.getInitParameter(headerName)); } chain.doFilter(req, resp); } public void init(FilterConfig config) throws ServletException { this.filterConfig = config; } }

部署描述子的檔案內容要加入下列設定:

 <filter> <filter-name>ResponseHeaderFilter</filter-name> <display-name>ResponseHeaderFilter</display-name> <filter-class>demoweb.filters.ResponseHeaderFilter</filter-class> <init-param> <param-name>Cache-Control</param-name> <param-value>private,no-cache,no-store</param-value> </init-param> </filter> <filter-mapping> <filter-name>ResponseHeaderFilter</filter-name> <url-pattern>/jsp/TestCache/*</url-pattern> </filter-mapping>

這個過濾器的運作過程是,當用戶端 request 的資源的 URL 符合部署描述子中的 URL pattern 時,該 request 就會送給過濾器處理,而這個過濾器會取出 web.xml 中的初始參數,並且將參數值加到 response header 裡面。

實驗結果

這個方法不但可以避免 JSP 檔案的快取,連同 JSP 連結的外部檔案也有效。事實上,只要是符合 web.xml 中指定的 URL 樣式的任何資源,都可以用這種方法避免被瀏覽器快取。

關於這個方法的詳細介紹,可以參考這篇文章:Another Java Servlet Filter Most Web Applications Should Have by Jason Falkner。

Discussion & Conclusion

實際測試以上幾種方法後,發覺最後一個使用過濾器的方法有下列幾個優點:

  • 任何網頁內容都可以避免被瀏覽器快取,包括網頁連結的外部檔案;
  • 開發人員可以隨時視需要調整快取的設定(透過修改 web.xml);
  • 只對我們想要避免快取的資源起作用,其他的網頁都不受影響;
  • 使用者完全不用更改瀏覽器的設定,也不會影響使用者瀏覽其他網站的速度。

然而,過濾器的方法對於透過 proxy 上網的使用者來說,可能無法達到百分之百避免用戶端快取,因為用戶端可能仍然會取到 proxy 伺服器上的舊檔案。雖然 HTTP 1.1 規格中有提供相關的方法告知瀏覽器和 proxy 不要快取檔案,但是如果有些 proxy 沒有完全依照這份規格實做,用戶端就仍然有可能讀取到舊的檔案。

相較之下,本文中的第 3 個方法所提到的比較極端的做法(將 URL 編碼),卻不會受到 proxy 的影響,因此比過濾器的方法來得有效,但是這種方法也可能引發一些潛在的問題,例如:使用者無法將特定網頁加到書籤(我的最愛)裡面,因為每次存取的網頁的 URL 都不一樣。另外,過濾器的最大優點在於實作簡單及其靈活彈性的設定,任何時候,當我們不想要 cache 哪些資源,只要調整一下 web.xml 的 URL-pattern 就行了,同時還能夠設定 cache 的有效時間長短。例如:當 Web 應用程式剛開發完成時,可能很多檔案還會經常更新,這時候可能會設定成讓大部分的檔案不要快取;隨著 Web 應用程式逐漸穩定,有些原本不希望 cache 住的東西,後來因為不大修改了,此時反而讓瀏覽器 cache 住這些資源會比較恰當。像這樣能夠隨時調整哪些資源要不要 cache,以及 cache 多久,對於 Web App 開發人員來說,是很有幫助的。而這些優點,則是 URL 編碼方式無法提供的。

因此,如果我們希望能夠控制網站所有的資源(不單只是網頁)要或不要被用戶端快取,在衡量諸多因素之後,撰寫過濾器來更改 HTTP response header 應該是比較適合的方法。

Links


【PHP程式碼如下 】

header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
在檔案最前面加這二行就好了。



Trackback: http://tb.donews.net/TrackBack.aspx?PostId=91367


[点击此处收藏本文]  发表于2004年09月07日 11:18 AM




正在读取评论……

发表评论

大名:
网址:
验证码
评论 
   

导航

blog stats

文章

收藏

相册

*nux

Javascript

PHP

存档


正在读取评论……