1. 程式人生 > >Servlet之Request物件與Response物件

Servlet之Request物件與Response物件

概述

使用者傳送了一個HTTP請求到Web容器(一個Web應用對應一個Web容器),Web容器建立了一個HttpServletRequest的request物件,將使用者傳送的請求資料封裝到了這個request物件當中;然後它又建立了一個HttpServletRequest的response物件,這個物件中還沒有資料;之後又呼叫了HttpServlet這個servlet的service方法,將之前建立的request和response物件傳了進去。service方法需要獲取請求方式,由於使用者是GET請求,所以呼叫doGet方法。doGet方法做處理的時候會嘗試從request中拿資料,然後將處理完的邏輯結果放到response物件中。最後處理完了,控制權回到Web容器(方法結束,控制權要轉回方法的呼叫者,比如A方法呼叫B方法,B方法執行完畢後控制權回到A方法),Web容器銷燬request和response物件,在銷燬response物件之前,Web容器會將response物件中的資料拿出來返回給使用者。

由此我們可以看出request和response的生命歷程:當一個請求到達Web容器,會建立request和response物件,處理這次請求所用的時間就是它存活的時間,當應答離開Web容器之前,request和response物件會被銷燬。

Request

Request類抽象的是使用者的一次請求,一個Request物件,與一個實際的使用者請求相對應。

繼承結構

ServletrRequest--通用的介面,定義了一個request應該具有的基本的方法,HttpServletRequest在ServletRequest基礎上,增加很多和Http協議相關的方法

Request物件中,封裝了一次使用者請求的全部資訊。

下面將介紹Request的API,主要是獲取使用者請求的資訊,以及一些額外的功能。

1.獲取使用者相關資訊

這裡我們順便說一下:我們在myeclipse中配置tomcat的時候選擇的執行方式是Debug模式,所以我們在修改完Servlet之後不需要重啟伺服器,但是在修改完之後不要立即訪問Servlet,我們需要給伺服器一點時間讓它來重新編譯java檔案來生成class檔案。

getRequestURL方法 -- 返回客戶端發出請求完整URL

//由於該方法返回的是一個StringBuffer物件
//所以需要用toString來轉為字串
String url=req.getRequestURL().toString();

getRequestURI方法 -- 返回請求行中的資源名部分(請求行中間的部分)

String uri=req.getRequestURI();

getQueryString方法 -- 返回請求行中的引數部分(只限於GET請求)

String queryStr=req.getQueryString();

getRemoteAddr方法 -- 返回發出請求的客戶機的IP地址(不同瀏覽器返回的ip地址的格式不同,但是都代表本機)

String ip=req.getRemoteAddr();

getMethod方法 -- 返回客戶機的請求方式(GET請求或者POST請求)

String method=req.getMethod();

getContextPath方法*** -- 獲得當前web應用的虛擬目錄名稱(用這個方法來動態獲取虛擬路徑,當專案需要修改虛擬路徑時,這樣就不需要在程式碼中修改路徑了)

String contextPath=req.getContextPath();

2.獲取請求頭資訊(學習檢視原始碼中對方法的解釋)

getHeader(name)方法 --- String(name表示請求頭的名字,我們通過這個方法來獲取請求頭中的資訊)

//檢視原始碼發現解釋說case insensitive(大小寫不敏感)
//所以我們這裡寫host也可以獲取Host請求頭中的資訊
String host=req.getHeader("host");

getHeaders(String name)方法 --- Enumeration<String>(同一個請求頭如果有多個值,會以多個鍵相同而值不同的方式顯示,由於不同的瀏覽器顯示的格式不同,所以在一個請求頭有多個值的情況下用getHeader()拿不到所有資料,所以有了getHeaders()這個方法)

getHeaderNames方法 --- Enumeration<String>(獲取所有的請求頭的名字,這裡我們順便獲取一下對應請求頭的資訊)

Enumeration<String> names= req.getHeaderNames();
		while(names.hasMoreElements()){
			String name=names.nextElement();
			System.out.println("name="+name+",value="+req.getHeader(name));
		}

getIntHeader(name)方法  --- int(如果你確定你要的請求頭的值是int型別的值,就可以直接用這個方法得到int型別的請求頭資料)

getDateHeader(name)方法 --- long(日期對應毫秒)(如果你確定你的請求頭的值是日期型別,就可以用這個方法)

3.獲取請求引數***

我給大家提供一個param.html檔案用來測試

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
	</head>
	<body>
		<h1>GET提交</h1>
		<form action="/day09/servlet/你寫的servlet的名字" method="GET">
			使用者名稱: <input type="text" name="username" />
			暱稱: <input type="text" name="nickname" />
			<input type="submit" value="提交" />
		</form>
		<h1>POST提交</h1>
		<form action="/day09/servlet/你寫的servlet的名字" method="POST">
			使用者名稱: <input type="text" name="username" />
			暱稱: <input type="text" name="nickname" />
			愛好: <input type="checkbox" name="like" value="lanqiu" />籃球
				<input type="checkbox" name="like" value="zuqiu" />足球
				<input type="checkbox" name="like" value="taiqiu" />檯球
			<input type="submit" value="提交" />
		</form>
	</body>
</html>

getParameter(String name) -- String 獲取name對應的value,針對一對一的資料,例如input框等

//啟動伺服器後,我們訪問param.html
//然後將資料輸入後在後臺列印輸出
//獲取name對應的value,針對一對一的資料,例如input框等
String username=req.getParameter("username");
String nickname=req.getParameter("nickname");
System.out.println("username="+username);
System.out.println("nickname="+nickname);

getParameterValues(String name) -- String[] 通過name獲得多值 如: 愛好(我們發現當勾選多個複選框時,用getParameter()方法只獲取了一個值,所以有了getParameterValues()這個方法來獲取一個name對應的所有value組成的陣列,對應checkbox等)

// 獲取一個name對應的所有value組成的陣列,對應checkbox等
String[] values=req.getParameterValues("like");
//注意:checkbox有可能為null
//為了避免空指標異常,所以我們要先判斷一下,再遍歷陣列
if(values!=null){
	for(String value:values){
		System.out.println("like="+value);
	}
}

getParameterMap() -- Map<String,String[ ]> key :name value: 多值(獲取所有的請求引數以及它們的值,由於存在一個name對應多個value的情況,所以這個方法預設的value是一個String的陣列,如果是一對一,那麼陣列中就只有一個元素)

當用戶第一次呼叫該方法時,獲取request中封裝的所有請求引數,組成一個新的map集合,返回該集合的引用;後續使用者多次呼叫該方法,不再建立新的map集合,而是返回第一次建立的map集合的引用

//注意導的包是Entry-java.util.Map
Map<String, String[]> map=req.getParameterMap();
	for(Entry<String, String[]> entry: map.entrySet()){
		System.out.println("name="+entry.getKey());
		String[] values=entry.getValue();
		if(values!=null){
			for(String value:values){
				System.out.println("\tvalue="+value);
			}
		}
	}

getParameterNames() -- Enumeration<String> 獲得所有請求引數的name(返回的是表單中所有name的值)

//獲取所有請求引數的name
Enumeration<String> names=req.getParameterNames();
	while(names.hasMoreElements()){
		System.out.println("name="+names.nextElement());
	}

我們得到了請求引數,有可能會出現亂碼問題,比如你輸入中文,使用POST提交之後在後臺輸出顯示為亂碼。 

// POST請求的亂碼問題
// 通知伺服器使用UTF-8對請求實體中的資料進行解碼
//這樣字符集就統一了
req.setCharacterEncoding("utf-8");//這行程式碼必須寫在獲取引數的上面
String username=req.getParameter("username");
System.out.println("username="+username);

那麼GET提交呢?由於現在沒有成熟的API可以解決GET提交的亂碼問題,所以我們需要手動編解碼。我們在瀏覽器中將中文"x小明"用utf-8變成了byte陣列,這個byte陣列在提交到伺服器的時候,伺服器"未經允許"就直接將這個byte陣列用iso8859-1轉化為了字串,這個字串肯定是亂碼啊!所以我們再用iso8859-1將這個字串轉化回byte陣列,然後再用utf-8轉化為字串,這個時候,我們得到的字串就是正確的資料了。(適用於tomcat7及其以下版本,tomcat8就自動修復了,只需要寫和POST提交一樣的程式碼就可以解決亂碼問題) 

// GET請求的中文亂碼問題-手動編解碼
// GET請求的引數是拼接在請求行的uri後面的
// req.getCharacterEncoding()對請求行無效
// 即:req.setCharacterEncoding("utf-8");無效
String username=req.getParameter("username");
System.out.println("username="+username);
// 使用iso8859-1字符集將亂碼字串變成byte陣列
byte[] array=username.getBytes("iso8859-1");
// 使用utf-8字符集將byte[]變成正確的字串
String username2=new String(array, "utf-8");
System.out.println("username2="+username2);

4.實現請求的轉發***

實現資源跳轉的三種方式:請求的轉發request實現,請求的重定向定時重新整理response實現。

請求的轉發(在web應用內的轉發):使用者向Servlet1請求資源,Servlet1沒有。但是Servlet1知道Servlet2有這個資源,於是將使用者的請求轉發給了Servlet2,然後由Servlet2直接將資源返回給使用者。

request.getRequestDispatcher("目標資源的URL").forward(request,response);

特點:1.使用者傳送了一次請求,收到一次應答

           2.位址列是不變的,保持在第一個資源,隱藏了後續其他資源的路徑

           3.只能在當前web應用內部的資源間實現轉發

案例:使用者向Servlet1借$800,Servlet1沒有,但它知道Servlet2有,所以將使用者的請求轉發給了Servlet2,Servlet2將$800給了使用者

forward是在伺服器上將一個請求轉發到另一個資源(servlet、JSP檔案、HTML檔案)的方法,這個方法允許一個Servlet對請求進行初步的處理,然後由另一個資源來生成應答。

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Servlet1 extends HttpServlet {

	public void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		System.out.println("Servlet1...收到請求...");
		// 在請求轉發之前,新增進response實體中的內容
		// 在請求轉發時,都會被清空
		// resp.getWriter().write("Servlet1...No money...");
		// resp.flushBuffer();//清空緩衝區,將內容直接寫給使用者

		// 實現請求的轉發
		// 獲取一個請求的排程器,並將排程的目標(URL)傳進去
		// RequestDispatcher
		// rd=req.getRequestDispatcher("/servlet/Servlet2");
		// 通過排程器實現請求的轉發,將request和reponse物件傳過去
		// rd.forward(req, resp);
		req.getRequestDispatcher("/servlet/Servlet2").forward(req, resp);
                //請求轉發就像方法的呼叫,轉發程式碼之後的程式碼會在轉發結束後繼續執行
		System.out.println("servlet1...afterforward...");
		// rd.forward(req, resp);

	}

	public void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		doGet(req, resp);
	}

}
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Servlet2 extends HttpServlet {

	public void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		System.out.println("Servlet2...收到請求...");
		resp.getWriter().write("Servlet2...Money...$800");
                //請求轉發就像方法的呼叫,轉發程式碼之後的程式碼會在轉發結束後繼續執行
		System.out.println("Servlet2...finished");
	}

	public void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		doGet(req, resp);
	}

}

***在請求轉發之前,如果response緩衝區寫入了資料但是還沒有打給瀏覽器,在請求轉發時這些資料將會被清空

***在請求轉發之前,如果response緩衝區寫入了資料並且打給了瀏覽器(flushBuffer),請求轉發失敗丟擲異常

***請求轉發就像方法的呼叫,轉發程式碼之後的程式碼會在轉發結束後繼續執行

***不可以多次轉發(在一個Servlet中不能多次forward,因為這破壞了一次請求一次應答的原則),但可以多重轉發(Servlet1->Servlet2->Servlet3)

5.作為作用域來使用***

javaEE的四大作用域:Request、ServletContext(Application)、Session、PageContext

域物件:一個物件具有可以被看見的範圍,利用這個物件身上的map集合,在這個可見範圍內實現資源的共享,像這樣的物件就稱之為域物件。

Request作用域

生命週期:一次請求開始到一次請求結束

作用範圍:在本次請求鏈上的所有元件都可以看見

主要功能:Servlet在請求轉發時帶資料到目的地

API(適用於所有作用域)

setAttribute(String name,Object valObj); 往作用域中存鍵值對

getAttribute(String name);獲取作用域中對應name的值

removeAttribute(String name);從作用域中移除對應name

getAttributeNames();獲取所有引數的name

Attribute和Parameter是兩個完全獨立的map集合,Parameter代表的是請求引數的map集合,裡面只能封裝使用者在表單上提交的請求引數,是一個只讀的map集合;而Attribute是作為作用域使用的map集合,既可讀也可寫,它們兩個是獨立的,資料之間不互動

使用者檢視購物車,將請求傳送給了Servlet,Servlet利用JDBC將資料從資料庫中拿出來,然後存入Request作用域中(setAttribute),呼叫forward方法將Request傳給jsp,jsp從Request作用域中將資料取出(getAttribute),生成包含了購物車資訊的html頁面,最後返回給使用者。

6.實現請求的包含(不屬於資源的跳轉)

請求的包含:允許一個Servlet將其他資源生成的應答內容包含進來,共同生成應答內容

request.getRequestDispatcher("目標資源的URL").include(request,response);

如果瀏覽器請求ServletA, 在A的內部可以通過request.getRequestDispatcher("B的虛擬路徑").include(request, response);將ServletB包含進來, 這時將由A和B共同處理該請求, B處理的結果將會併入A處理的結果, 一起響應給瀏覽器

特點:1.使用者傳送了一次請求,收到一次應答(使用者不知道位址列發生變化)

           2.位址列是不變的,保持在第一個資源,隱藏了後續其他資源的路徑

           3.只能在當前web應用內部的資源間實現包含

案例:使用者向Servlet1借$1000,Servlet1只有$200,Servlet2有$800,Servlet1從Servlet2那裡拿到$800,然後將自己的$200也一併交給使用者。(程式碼只是將請求轉發中的forward改為include,然後再改一下數值,這裡就不寫了...)

include是在伺服器上將響應中的資源(servlet、JSP頁面、HTML檔案)整合到一起一併傳送給使用者的方法。

Response

Response類抽象的是伺服器給使用者的一次應答,一個Response物件,與一個實際的伺服器應答相對應。

繼承結構

ServletResponse--通用的響應介面,定義了響應物件應該具有的功能,HttpServletResponse在ServletResponse的基礎上,添加了很多和Http協議相關的方法。

Response物件中,封裝了伺服器一次應答中的所有資料。

response中的重要方法

一個狀態行:HTTP/1.1 200 OK

HTTP/1.1:當前所遵循的協議的版本

200:響應碼--一個三位數字,範圍為100~600,表示伺服器處理請求的結果

200~299:表示伺服器正確處理了請求

300~399:表示伺服器正確處理了請求,但是如果想要繼續執行,還需要更多的額外資訊

400~499:表示客戶端的請求有問題

500~599:表示伺服器端發生了問題

200:表示伺服器處理成功

302:表示請求重定向

304、307:通知瀏覽器使用快取資源

404:表示客戶請求的資源不存在

500:表示伺服器端處理請求出錯

OK:描述字元

1.設定狀態碼的方法

void setStatus(int sc) sc為狀態碼

viod setStatus(int sc) sc為狀態碼,sm為描述資訊

2.設定響應頭的方法

void setHeader(String name, String value) 
void setDateHeader(String name, long date)
void setIntHeader(String name, int value) 

void addHeader(String name, String value) 
void addDateHeader(String name, long date)
void addIntHeader(String name, int value)
set方法是如有沒有則新增,如果有則更新
add方法是不論有沒有都新增新的

3.設定響應內容的方法***

ServletOutputStream getOutputStream() 
PrintWriter getWriter()

response的功能

1.向客戶端傳送資料(新增應答實體內容時的亂碼問題)***

向客戶端傳送資料有兩種方法

1)獲取位元組流:getOutputStream()\

位元組流傳送資料的中文亂碼問題:伺服器端指定了用uft-8來發送資料,瀏覽器在接收資料是,如果不指定解碼字符集,將使用預設的平臺碼GBK,這會導致編解碼不一致導致亂碼。

解決方案:

第一步:通知瀏覽器使用utf-8開啟伺服器傳送來的資料(有兩個作用,一是通知瀏覽器我的應答內容是html,字符集是utf-8;二是伺服器在看到這個內容後,也會使用utf-8對應答內容進行編碼)

response.setHeader("Content-Type", "text/html;charset=utf-8");

第二步:指定伺服器編碼為utf-8

response.getOutputStream().write("中國".getBytes("utf-8"));

2)獲取字元流:getWriter()

字元流傳送資料的中文亂碼問題:利用字元流傳送資料, 底層還是要轉成位元組. 伺服器端如果不手動指定, 伺服器預設會使用iso8859-1碼錶, 由於裡面沒有中文漢字, 所以伺服器端傳送給客戶端就是一堆亂碼, 客戶端不管使用什麼碼錶都無法轉成正常的字元。伺服器會根據getCharacterEncoding()方法返回的編碼來發送資料, 如果沒有指定, 該方法預設返回iso8859-1

解決方案:

第一步:通知伺服器使用utf-8來發送響應實體中的資料

response.setCharacterEncoding("utf-8");

第二步:需要指定瀏覽器在接收資料時也使用同一個編碼來開啟資料

response.setHeader("Content-Type", "text/html;charset=utf-8");

等價於

response.setContentType("text/html;charset=utf-8");

總結:

 1)不管是字元流還是位元組流, 解決亂碼問題, 可以用一行程式碼搞定:

response.setContentType("text/html;charset=xxx");

2)getOutputStream()和getWriter() 這兩個方法是互斥的, 在一次請求當中呼叫了其中的一個, 就不能再呼叫另一個!

3)在呼叫完getOutputStream()或getWriter()方法之後, 不需要手動去關閉流, 伺服器會自動幫我們去關閉!

4)這個兩個方法獲取到的流並不是指向客戶端的流, 而是指向response緩衝區的流, 通過流將資料寫入response緩衝區, service方法執行結束, 請求回到伺服器, 由伺服器將資料組織成響應訊息打給瀏覽器!

出現異常應該看三類資訊

異常的型別:異常發生的原因(訊息)

異常出現的位置:at...

                          at...                          

2.實現請求的重定向***

請求的重定向:使用者向Servlet1請求資源,Servlet1沒有,它知道Servlet2有,但是Servlet1不能將請求轉發給Servlet2,所以Servlet1給了瀏覽器一個回答:302+location。瀏覽器收到這個回答之後,會馬上根據location給Servlet2再發送一個請求,這時瀏覽器就會拿到Servlet2給的應答資源。

特點:1.使用者傳送了兩次請求,收到兩次次應答(使用者知道位址列發生變化)

           2.位址列發生變化

           3.可以轉發當前伺服器內部的資源,也可以轉發到其他伺服器的資源

實現步驟:

1)給使用者302狀態碼

resp.setStatus(302);

2)給使用者設定location應答頭

resp.setHeader("location", req.getContextPath()+"/servlet/重定向到的那個Servlet的名字");

上面兩行程式碼可以由下面的這個方法來替代 

resp.sendRedirect(req.getContextPath()+"/servlet/重定向到的那個Servlet的名字");

另外:如果路徑不加 / 那麼是一個相對路徑,相對於當前Servlet的父路徑,如果路徑加/,那麼是絕對路徑,從 localhost開始,即:localhost/路徑 。

3.實現定時重新整理***

實現定時重新整理是通過Refresh響應頭,以實現在多少秒之後跳轉到另一個資源

在這之前你可以給使用者一個提示資訊

// 1. 處理應答亂碼問題
resp.setContentType("text/html;charset=utf-8");
// 2. 給使用者提示資訊
resp.getWriter().write("<h1 style='text-align:center;color:red'>恭喜您,請求成功!!!</h1>");
//3秒後跳轉到百度首頁
resp.setHeader("refresh", "3;url=http://www.baidu.com");

這裡總結一下,請求轉發/請求重定向/定時重新整理都可以實現資源的跳轉, 區別是什麼呢?

請求轉發:
一次請求,一次響應,request物件是同一個。
位址列不會發生變化。
只能用於伺服器內部的資源跳轉, 並且只能是同一應用中的不同資源上進行跳轉, 不可用在不同應用和不同伺服器中的資源跳轉。

請求重定向:
兩次請求,兩次響應,request物件不是同一個。
位址列會發生變化。
可以用於伺服器內部的資源跳轉, 也可以用於不同應用和不同伺服器之間的資源跳轉。

定時重新整理:
兩次請求,兩次響應,request物件不是同一個。
位址列會發生變化。
可以用於伺服器內部的資源跳轉, 也可以用於不同應用和不同伺服器之間的資源跳轉。
 和重定向不同的是, 定時重新整理可以在重新整理到新的地址之間設定一個時間, 在間隔的這段時間內可以輸出文字到瀏覽器並維繫一段時間。

那什麼時候用哪種方式進行資源的跳轉呢?

***如果是同一伺服器中的同一應用內部的資源跳轉:

  • 如果需要利用request域在跳轉的資源之間傳輸資料, 只能用請求轉發。
  • 如果不想讓位址列發生變化, 只能用請求轉發。
  • 如果需要位址列發生變化, 只能用重定向或定時重新整理。
  • 如果沒有什麼特殊需求, 三種方式都可以, 但是推薦使用轉發, 可以減少請求次數降低伺服器的壓力。
  • 如果只是想更新重新整理操作, 最好使用重定向或定時重新整理, 使用請求轉發, 在重新整理時會把剛才的操作再做一遍, 可能會導致一些問題, 比如表單重複提交或重複支付訂單等。

***如果是不同伺服器或不同應用內部的資源跳轉, 只能用重定向或者定時重新整理:

  • 重定向和定時重新整理的主要區別在於: 重定向會立即跳轉, 而定時重新整理可以設定一個時間間隔, 在指定時間後再進行跳轉。
  • 如果在跳轉之前需要輸出提示資訊(如: 註冊成功, xx秒後跳轉到xxx)只能用定時重新整理, 否則兩種方式都可以。

4.控制瀏覽器的快取行為***

由於不同的瀏覽器的快取行為可能是不同的, 我們可以在伺服器中通過設定響應頭來控制瀏覽器的快取行為!!

控制瀏覽器不要快取:比如不讓伺服器快取驗證碼圖片

setDateHeader("Expires", -1);
setHeader("Cache-control", "no-cache");
setHeader("Pragma","no-cache");

控制瀏覽器快取:雖然有些瀏覽器設定的是永遠都請求最新的資源,但是伺服器希望你把這個資源快取一下,你不要請求了,每次瀏覽器請求的都是快取下的資源,直到瀏覽器快取的資源過期了,再請求新的資源

setDateHeader("Expires", System.currentTimeMillis()+1000*60*60*24);
setHeader("Cache-control", "max-age=60");

在HTTP1.0中
"Pragma"-控制瀏覽器不要快取資源
"Expires"-控制瀏覽器快取一個資源多長時間
Expires設定的快取時間是一個固定的值,這個值可以被翻譯成一個準確的時間,但是生成這個時間依賴於服
務器的時間。伺服器當前時間+快取時間=  實際給客戶端的快取時間。可能存在伺服器時間和客戶端時間不一致的情況,導致快取效果變差甚至失效。

比如說伺服器當前時間為8:00,向客戶端傳送一個資源,這個資源的有效時間是5分鐘,則這個資源在8:05之前有效。但是當前客戶端的時間已經顯示為8:10,所以這個資源變得沒有意義。

在HTTP1.1中
"cache-control":"mag-age:60*5"這是一個相對值,瀏覽器從收到應答開始計時,相對於當前客戶端的時間,快取5分鐘。
:"no-cache"不是不快取,而是不使用過期的快取資源。實際就是在每次使用該資源時,通過HTTP請求頭標籤If-Modified-Since,將當前資源的時間戳(資源最後一次修改時間)傳送給伺服器,向伺服器確認該資源是否依舊有效,如果伺服器修改了資源,那麼資源對應的時間戳也會改變,伺服器發現客戶端請求的資源的時間戳落後於自己當前資源的時間戳,就會將新的資源返回給使用者。

:“no-stor” 不快取

If-Modified-Since是標準的HTTP請求頭標籤,在傳送HTTP請求時,把瀏覽器端快取頁面的最後修改時間一起發到伺服器去,伺服器會把這個時間與伺服器上實際檔案的最後修改時間進行比較。

如果時間一致,那麼返回HTTP狀態碼304(不返回檔案內容),客戶端接到之後,就直接把本地快取檔案顯示到瀏覽器中。

如果時間不一致,就返回HTTP狀態碼200和新的檔案內容,客戶端接到之後,會丟棄舊檔案,把新檔案快取起來,並顯示到瀏覽器中。

// 可以通過伺服器來控制瀏覽器不快取資源
// 為了適用於所有HTTP版本,這裡我們都寫上,並且值相同
// 控制瀏覽器不要快取一個資源,如果兩個HTTP版本都支援,優先順序:pragma>cache-control
resp.setHeader("pragma", "no-cache");
resp.setHeader("cache-control", "no-cache");
// 可以通過伺服器來控制瀏覽器快取資源
// 為了適用於所有HTTP版本,這裡我們都寫上,並且值相同
// 控制瀏覽器的快取行為:快取一個資源一段時間,如果兩個HTTP版本都支援,優先順序:cache-control>Expires
resp.setDateHeader("Expires", System.currentTimeMillis()+1000*10);
resp.setHeader("cache-control", "max-age=10");

 over!!!