http協議中與cache相關的header主要有這麼幾個:
1.Pragma:只能取固定值no-cache,用於http/1.0裡面,禁止瀏覽器緩衝
2. Expires:用於指定當前緩衝的文檔在什麼時候被認為到期,GMT格式,超過了這個日期瀏覽器就不使用本機快取,而是像伺服器發出新的請求。
3.Cache-Control
no-cache:禁止瀏覽器和Proxy 伺服器緩衝,可以單獨指定不緩衝某些資訊頭,例如:no-cache=Set-Cookie不緩衝cookie資訊
no-store:專指不能緩衝到硬碟
max-age:xx秒以後文檔過時,可以代替Expires,如果同時出現,max-age優先
s-max-age:Proxy 伺服器中緩衝的文檔的有效期間
must-revalidate:對於客戶機的請求,Proxy 伺服器必須向伺服器驗證緩衝的文檔是否過時,總是擷取最新的文檔給客戶機
4.if-modified-since
當客戶機訪問一個已經緩衝的頁面時,只有該頁面自某個指定的時間以來發生過更改,才需要重新擷取該文檔。
if-modified-since用於指定這個時間,GMT格式。
5.last-modified:服務端設定的文檔的最後的更新日期。
6.if-match:定義判斷網頁到期的條件
伺服器給客戶機傳送網頁的時候,可以傳遞代表實體內容特徵的頭欄位(ETag),這種頭欄位被叫做實體標籤。當客戶機再次向服務端發請求的時候,
會使用if-match攜帶實體標籤資訊。
7.ETag:用於伺服器向用戶端傳送的代表實體內容特徵的標記資訊。
下面我們就通過具體的例子來看一下,這幾個header是如何控制瀏覽器的緩衝行文的:
1. 緩衝協商機制Last-Modified/If-Modified-Since
瀏覽器會緩衝瀏覽過的頁面,如果伺服器告訴了瀏覽器Last-Modified,瀏覽器在下次訪問緩衝的頁面的時候,就可以使用If-Modified-Since詢問伺服器可否使用本機快取,如果可以,伺服器會發送304狀態代碼。
但是,如果伺服器沒有輸出Last-Modified頭欄位,瀏覽器就不會發送If-Modified-Since頭。
我們先寫一個CacheServlet:
@WebServlet(urlPatterns={"/cacheservlet.html"})
public class CacheServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=gb2312");
PrintWriter out = response.getWriter();
out.print("hello,cache!");
out.close();
}
}
清一下瀏覽器的緩衝,訪問http://localhost:8080/cache/cacheservlet.html,可以看到輸出:hello,cache!
通過httpwatch,可以看到瀏覽器發出的請求:
GET /cache/cacheservlet.html HTTP/1.1
Accept: image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Accept-Language: zh-CN
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.2)
Accept-Encoding: gzip, deflate
Host: localhost:8080
Connection: Keep-Alive
服務端返回的響應:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=gb2312
Content-Length: 12
Date: Tue, 17 Apr 2012 07:30:19 GMT
hello,cache!
可以看到伺服器並沒有輸出last-modified頭欄位,再次重新整理瀏覽器,可以看到同樣的結果,瀏覽器也並沒有發送If-Modified-Since。開啟瀏覽器的緩衝:
我們可以看到,已經緩衝了cacheservlet.html這個頁面,但是,“上次修改時間”是“無”,“到期日”也是“無”。因此,無論怎麼重新整理,都是返回200.
下面我們修改一下我們的servlet,添加last-modified頭:
@WebServlet(urlPatterns={"/cacheservlet.html"})
public class CacheServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=gb2312");
response.setDateHeader("Last-Modified", new Date().getTime());
PrintWriter out = response.getWriter();
out.print("hello,cache!");
out.close();
}
}
然偶我們清一下緩衝,重啟tomcat,再次訪問這個頁面,
瀏覽器的請求:
GET /cache/cacheservlet.html HTTP/1.1
Accept: */*
Accept-Language: zh-CN
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.2)
Accept-Encoding: gzip, deflate
Host: localhost:8080
Connection: Keep-Alive
服務端的響應:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Last-Modified: Tue, 17 Apr 2012 07:33:09 GMT
Content-Type: text/html;charset=gb2312
Content-Length: 12
Date: Tue, 17 Apr 2012 07:33:09 GMT
hello,cache!
可以看出來,這次輸出了Last-Modified頭,然後看一下緩衝:
這次就有上次修改時間了,因為服務端輸出的last-modified是按照GMT格式的,瀏覽器顯示的東八區的,二者正好相差了8個小時。
這次我們再重新整理瀏覽器:
用戶端的請求:
GET /cache/cacheservlet.html HTTP/1.1
Accept: */*
Accept-Language: zh-CN
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.2)
Accept-Encoding: gzip, deflate
If-Modified-Since: Tue, 17 Apr 2012 07:33:09 GMT
Host: localhost:8080
Connection: Keep-Alive
服務端的響應:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Last-Modified: Tue, 17 Apr 2012 07:34:29 GMT
Content-Type: text/html;charset=gb2312
Content-Length: 12
Date: Tue, 17 Apr 2012 07:34:29 GMT
hello,cache!
顯然,這次用戶端的請求就攜帶了If-Modified-Since頭,但是伺服器的預設實現中:
@Override
protected long getLastModified(HttpServletRequest req) {
return super.getLastModified(req);
}
這個方法預設返回-1,因此,伺服器認為用戶端的緩衝到期,因此還是返回了200.
具體參考HttpServlet的源碼:
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
}
…….
}
現在,我們讓瀏覽器快取頁面面60秒,修改CacheServlet:
@WebServlet(urlPatterns={"/cacheservlet.html"})
public class CacheServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long clientLastModified = request.getDateHeader("If-Modified-Since");
if(clientLastModified + 60 * 1000 > new Date().getTime()){//如果在60秒以內,直接返回304,,讓瀏覽器使用本機快取
response.setStatus(304);
return;
}
response.setContentType("text/html;charset=gb2312");
response.setDateHeader("Last-Modified", new Date().getTime());
PrintWriter out = response.getWriter();
out.print("hello,cache!");
out.close();
}
}
清一下緩衝,重啟tomcat,再次訪問這個頁面,
用戶端的請求:
GET /cache/cacheservlet.html HTTP/1.1
Accept: */*
Accept-Language: zh-CN
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.2)
Accept-Encoding: gzip, deflate
If-Modified-Since: Tue, 17 Apr 2012 07:34:29 GMT
Host: localhost:8080
Connection: Keep-Alive
服務端的響應:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Last-Modified: Tue, 17 Apr 2012 07:36:08 GMT
Content-Type: text/html;charset=gb2312
Content-Length: 12
Date: Tue, 17 Apr 2012 07:36:08 GMT
hello,cache!
重新整理頁面,用戶端的請求:
GET /cache/cacheservlet.html HTTP/1.1
Accept: */*
Accept-Language: zh-CN
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.2)
Accept-Encoding: gzip, deflate
If-Modified-Since: Tue, 17 Apr 2012 07:36:08 GMT
Host: localhost:8080
Connection: Keep-Alive
服務端的響應:
HTTP/1.1 304 Not Modified
Server: Apache-Coyote/1.1
Date: Tue, 17 Apr 2012 07:36:08 GMT
可以看到,服務端返回304,瀏覽器就可以直接使用本機快取,一分鐘以後,再次重新整理:
用戶端的請求:
GET /cache/cacheservlet.html HTTP/1.1
Accept: */*
Accept-Language: zh-CN
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.2)
Accept-Encoding: gzip, deflate
If-Modified-Since: Tue, 17 Apr 2012 07:36:08 GMT
Host: localhost:8080
Connection: Keep-Alive
服務端的響應:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Last-Modified: Tue, 17 Apr 2012 07:37:28 GMT
Content-Type: text/html;charset=gb2312
Content-Length: 12
Date: Tue, 17 Apr 2012 07:37:28 GMT
hello,cache!
雖然用戶端發送了If-Modified-Since頭,但是,因為已經超過了60秒,因此返回200.
2. 徹底的消滅請求-Expires
雖然瀏覽器的緩衝沒有到期的情況下,伺服器可以返回304,能節省頻寬提高伺服器的處理效率,但是,畢竟瀏覽器還是向伺服器發出了請求,能不能在不到期的時候,瀏覽器可以不詢問伺服器,直接使用本地的緩衝?答案是肯定的。Expires頭用來告訴瀏覽器,緩衝的頁面何時到期,在到期之前,瀏覽器可以直接使用本地的緩衝,而不需要詢問伺服器。
修改我們的CacheServlet:
@WebServlet(urlPatterns={"/cacheservlet.html"})
public class CacheServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long clientLastModified = request.getDateHeader("If-Modified-Since");
if(clientLastModified + 60 * 1000 > new Date().getTime()){
response.setStatus(304);
return;
}
response.setContentType("text/html;charset=gb2312");
response.setDateHeader("Last-Modified", new Date().getTime());
response.setDateHeader("Expires", new Date().getTime() + 60 * 1000);//60秒之後,瀏覽器的緩衝到期
PrintWriter out = response.getWriter();
out.print("hello,cache!");
out.close();
}
}
清一下緩衝,重啟tomcat,再次訪問這個頁面,
用戶端的請求:
GET /cache/cacheservlet.html HTTP/1.1
Accept: */*
Accept-Language: zh-CN
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.2)
Accept-Encoding: gzip, deflate
Host: localhost:8080
Connection: Keep-Alive
服務端的響應:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Last-Modified: Tue, 17 Apr 2012 07:44:39 GMT
Expires: Tue, 17 Apr 2012 07:45:39 GMT
Content-Type: text/html;charset=gb2312
Content-Length: 12
Date: Tue, 17 Apr 2012 07:44:39 GMT
hello,cache!
瀏覽器的緩衝:
可以看出來,這次的到期日已經不再是無。
F5重新整理瀏覽器,還是會向伺服器發請求,而不是直接使用本地的緩衝,
用戶端請求:
GET /cache/cacheservlet.html HTTP/1.1
Accept: */*
Accept-Language: zh-CN
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.2)
Accept-Encoding: gzip, deflate
If-Modified-Since: Tue, 17 Apr 2012 07:44:39 GMT
Host: localhost:8080
Connection: Keep-Alive
服務端響應:
HTTP/1.1 304 Not Modified
Server: Apache-Coyote/1.1
Date: Tue, 17 Apr 2012 07:44:40 GMT
但是,如果是直接在瀏覽器按斷行符號,會顯示:
也就是說是直接取的本機快取,而並沒有與伺服器互動。
到此為止,似乎很完美,但是,有這麼兩個問題:(1)瀏覽器安全色問題,有的瀏覽器不支援Expires頭(2)伺服器的時間很可能和用戶端的時間不一致,如果用戶端的時間落後於伺服器的時間一個小時的話,如果伺服器的expires設定為1個小時之內,就都沒有用,這確實是個問題!很幸運的是,http還提供了另一個頭叫做:Cache-Control,他可以設定緩衝的時間,用秒作單位,這個時間是相對於用戶端的瀏覽器的。
我們再次修改我們的CacheServlet:
@WebServlet(urlPatterns={"/cacheservlet.html"})
public class CacheServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long clientLastModified = request.getDateHeader("If-Modified-Since");
if(clientLastModified + 60 * 1000 > new Date().getTime()){
response.setStatus(304);
return;
}
response.setContentType("text/html;charset=gb2312");
response.setDateHeader("Last-Modified", new Date().getTime());
response.setDateHeader("Expires", new Date().getTime() + 60 * 1000);
response.setHeader("Cache-Control", "max-age="+60);//告訴瀏覽器緩衝60秒
PrintWriter out = response.getWriter();
out.print("hello,cache!");
out.close();
}
}
清一下緩衝,重啟tomcat,再次訪問這個頁面,
用戶端的請求:
GET /cache/cacheservlet.html HTTP/1.1
Accept: */*
Accept-Language: zh-CN
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.2)
Accept-Encoding: gzip, deflate
If-Modified-Since: Tue, 17 Apr 2012 07:44:39 GMT
Host: localhost:8080
Connection: Keep-Alive服務端的響應:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Last-Modified: Tue, 17 Apr 2012 07:50:42 GMT
Expires: Tue, 17 Apr 2012 07:51:42 GMT
Cache-Control: max-age=60
Content-Type: text/html;charset=gb2312
Content-Length: 12
Date: Tue, 17 Apr 2012 07:50:42 GMT
hello,cache!
已經輸出了Cache-Control頭.
幾種不同的重新整理之間的區別:
(1) ctrl+F5:強制重新整理,不管瀏覽器有沒有快取頁面面,都會向伺服器發送請求,而且不會發送If-modified-since頭。
(2) F5:會使用If-modified-since進行緩衝協商,但是expires無效
(3) 在地址欄按斷行符號和點擊頁面超連結,會進行協商,而且expires頭有效。
服務端頁面緩衝:
前面說的都是瀏覽器端的緩衝,通過使用諸如velocity或者freemarker之類模板技術,很容易就能夠實現服務端的頁面緩衝,大體的思路應該是這樣:
當controller收到一個請求的時候:
1.尋找memcache裡面是否緩衝過與這個url匹配的ResponseContent對象
2.如果沒有找到,就調用action的方法,然後把產生的ResponseContent存放到memcache裡面
3.如果能找到,說明服務端緩衝可用
3.1如果用戶端緩衝可用(根據last-modified判斷),直接返回304,使用用戶端緩衝
3.2如果用戶端緩衝失效,使用服務端緩衝的ResponseContent,輸出到用戶端。
但是,請注意,使用了緩衝以後,是無法保證頁面的即時性的。
【注】
(1)在做實驗的時候,請設定瀏覽器的檢查網頁更新為自動。
(2)IE瀏覽器比較笨,它不會緩衝沒有尾碼名或者尾碼名是.do的檔案(我的是IE8),因此,在做實驗的時候,需要把Servlet的映射路徑變為html的樣子,否則,很有可能實驗失敗。
(3)其他與緩衝相關的http頭還有:
Pragma,ETag/If-None-Match
(4) 禁止瀏覽器緩衝
Pragma:no-cache // HTTP 1.0的不緩衝回應標頭
Expires:0
Cache-Control :no-cache
Cache-Control :no-store //該設定是防止Firefox緩衝
參考:構建高效能WEB網站 郭欣,深入體驗javaweb開發內幕-核心基礎 張孝祥