HTTP 頭緩存Last-Modified,ETag,Expires

http://www.jdon.com/40381



Last-Modified和Expires針對瀏覽器,而ETag則與客戶端無關,因此可適合REST架構中。二者都應用在瀏覽器端的區別是:Expires日期到達前,瀏覽器不會再發出新的請求,除非用戶按瀏覽器的刷新,因此,Last-Modified和Expires基本是下降瀏覽器向服務器發出請求的次數,而ETag更側重客戶端和服務器之間聯繫。

先談Last-Modified和Expires,最新的Tomcat 7 將ExpireFilter加 入其容器中,這樣,Java WEB也能夠象Apache的Mod_expire模塊同樣對Http頭部進行統一設置了,不過它只對響應文檔類型進行統一設置判斷,如 text/html或text/image 或/css等等,若是想對個別URL輸出的jsp進行定製就不行,urlrewrite聽說是能夠,可是要把URL在其配置文件再配置一下,麻煩,一旦 jsp改動影響面大,還有一個問題就是web.xml配置了Tomcat 7容器的ExpireFilter,與容器耦合,移植性差(移植到Resin就不行了)。

因此,我在JiveJdon 4.2最新版本中,經過加入下面一段代碼在服務器端對來自客戶端的Last-Modified以及當前時間進行判斷,如未過 期,response.setStatus設爲304,能夠終止後面的各類Jsp界面計算,直接返回瀏覽器一個304的響應包,JSP頁面也不會輸出到客 戶端,將帶寬節省給更加須要互動實時性的請求。

再談談ETag,ETag定義:RFC2616(也就是HTTP/1.1)中沒有說明ETag該是什麼格式的,只要確保用雙引號括起來就好了,因此你能夠用文件的hash,甚至是直接用Last-Modified,如下是服務器端返回的格式:
ETag: "50b1c1d4f775c61:df3" 客戶端向服務端發出的請求:If-None-Match: W/"50b1c1d4f775c61:df3" 這樣,在J2EE/JavaEE服務器端,咱們判斷若是ETag沒改變也是返回狀態304,起到相似Last-Modified和Expires效果。

與Last-Modified和Expires區別是:若是過了Expires日期,服務器確定會再次發出JSP完整響應;或者用戶強按瀏覽器的刷新按 鈕,服務器也必須響應,apache等靜態頁面輸出也是這樣,可是這時動態頁面就發揮了做用,若是JSP涉及的業務領域模型仍是沒有更新,和原來同樣,那 麼就沒必要再將動態頁面輸出了(瀏覽器客戶端已有一份),從Etag中獲取上次設置的領域模型對象修改日期,和如今內存中領域模型(In-memory Model)修改日期進行比較,若是修改日期一致,表示領域模型沒有被更新過,那麼返回響應包304,瀏覽器將繼續用本地緩存的該頁面,再次節省了帶寬傳輸。

經過上述Expire和Etag兩次緩存, 能夠大大下降服務器的響應負載,若是你的應用不是狀態集中併發修改和實時輸出,而是分散修改而後分發,如我的空間 我的博客(每一個人只是修改它們本身的狀態,不影響全局)或QQ相似我的工具,那麼採起這樣的方法效果很是明顯,實際就是一種動態頁面靜態化技術,但比一般 事先進行頁面靜態化要靈活強大。

InfoQ的那篇:http://www.infoq.com/articles/etags還用MD5計算放入其中,Md5計算稍微複雜點,負載大了點,有的人結合Hibernate或數據庫觸發器來判斷數據庫數據是否更新,以決定Etag的更新,這將表現層和持久層耦合在一塊兒,因爲JiveJdon採起的是MDD/DDD模型驅動架構,表現層的Etag更新是根據中間業務層的模型對象修改日期來決定,不涉及數據庫層,並且起到服務器緩存的更新和http的Etag更新一致的效果,在鬆耦合設計和性能上取得綜合平衡。

代碼以下:css


public static boolean checkHeaderCache(long adddays, long modelLastModifiedDate, HttpServletRequest request, HttpServletResponse response) {
// com.jdon.jivejdon.presentation.filter.ExpiresFilter
request.setAttribute(
"myExpire", adddays);

// convert seconds to ms.
long adddaysM = adddays * 1000;
long header = request.getDateHeader(
"If-Modified-Since");
long now = System.currentTimeMillis();
if (header > 0 && adddaysM > 0) {
if (modelLastModifiedDate > header) {
// adddays = 0; // reset
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
if (header + adddaysM > now) {
// during the period happend modified
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return false;
}
}

// if over expire data, see the Etags;
// ETags if ETags no any modified
String previousToken = request.getHeader(
"If-None-Match");
if (previousToken != null && previousToken.equals(Long.toString(modelLastModifiedDate))) {
// not modified
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return false;
}
// if th model has modified , setup the new modified date
response.setHeader(
"ETag", Long.toString(modelLastModifiedDate));
setRespHeaderCache(adddays, request, response);
return true;

}

public static boolean setRespHeaderCache(long adddays, HttpServletRequest request, HttpServletResponse response) {
request.setAttribute(
"myExpire", adddays);

long adddaysM = adddays * 1000;
String maxAgeDirective =
"max-age=" + adddays;
response.setHeader(
"Cache-Control", maxAgeDirective);
response.setStatus(HttpServletResponse.SC_OK);
response.addDateHeader(
"Last-Modified", System.currentTimeMillis());
response.addDateHeader(
"Expires", System.currentTimeMillis() + adddaysM);
return true; }
相關文章
相關標籤/搜索