JavaWeb學習篇之----Session&&Cookie

本文轉自:http://blog.csdn.net/jiangwei0910410003/article/details/23337043

今天繼續來看看JavaWeb的相關知識,這篇文章主要來講一下Session和Cookie的相關知識,首先我們來看一下Cookie的相關知識:


一、Cookie

簡介:

Cookie是客戶端技術,服務器把每個用戶的數據以cookie的形式寫給用戶各自的瀏覽器。當用戶使用瀏覽器再去訪問服務器中的web資源時,就會帶着各自的數據去。這樣,web資源處理的就是用戶各自的數據了。下面這張圖就體現了Cookie技術原理:



javax.servlet.http.Cookie類用於創建一個Cookie,response接口也中定義了一個addCookie方法,它用於在其響應頭中增加一個相應
的Set-Cookie頭字段。 同樣,request接口中也定義了一個getCookies方法,它用於獲取客戶端提交的Cookie。下面來看一下Cookie
的API: 

1.構造函數:Cookie(String name,String value):通過這個構造函數可以指定一個鍵值對,將信息存入到Cookie中,相同的name的值是會被覆蓋的

2.方法:
setMaxAge(int expiry):這個方法是設置Cookie的有效時間,也就是Cookie在瀏覽器中的緩存時間,單位是秒(s)
setPath(String path):這個方法是設置當訪問路徑爲path的資源的時候攜帶Cookie信息,比如:setPath("/ServletDemo")這樣設置之後就是說在訪問應用/ServletDemo中的所有資源的時候都會攜帶Cookie信息數據,如果不進行設置的話,默認是訪問路徑會攜帶Cookie,比如:/ServletDemo/ServletCookieDemo,其中ServletCookieDemo是一個Servlet,ServletDemo是當前的應用的映射路徑
setDomain(String domain):這個方法是設置我們在訪問domain域的時候會攜帶Cookie信息數據,這個可以做到第三方Cookie和跨域Cookie的信息訪問操作,如:在我們的應用中設置新浪域名sina.com,這個就是第三方Cookie技術,在我的應用中設置Cookie信息,並將這個Cookie的域設置成其他的域名,同時如果我將這個域設置成sina.com,來攻擊新浪網站因爲cookie也是數據的,如果每次都攜帶cookie的話,server需要處理cookie,增加server的負擔,所以會禁止第三方cookie,所以這個方法是沒有效果的。當然我們可以在瀏覽器中禁止Cookie和第三方Cookie的信息
getMaxAge()/getPath()/getDomain():這三個方法是和上面的三個方法一一對應的,是獲取相對應的值
getName():這個方法是獲取Cookie中字段的名稱,就是我們在Cookie的構造函數中的第一個參數Name,這樣我們就可以找到我們想要的Cookie信息
getValue():這個方法是獲取Cookie中字段的值,就是我們在Cookie的構造函數中的第二個參數Value,我們通過getName()方法檢測到我們想要的Cookie,然後就可以通過這個方法獲取Cookie中的信息

下面就來看一下實例:通過Cookie技術來實現客戶機上次訪問的時間
  1. //顯示上次訪問的時間   
  2.     public void test(HttpServletRequest request,HttpServletResponse response) throws Exception{  
  3.         //設置編碼   
  4.         response.setCharacterEncoding("utf-8");  
  5.         response.setContentType("text/html;charset=utf-8");  
  6.         PrintWriter out = response.getWriter();  
  7.         out.print("你上一次訪問的時間是:");  
  8.           
  9.         //獲得用戶的時間cookie,並且獲取值,如果第一次的話,是沒有Cookie信息的,所以Cookie數組可能爲null,所以我們要做判斷   
  10.         Cookie cookies[] = request.getCookies();  
  11.         for(int i=0;cookies != null && i<cookies.length;i++){  
  12.             if(cookies[i].getName().equals("lastAccessTime")){  
  13.                 long cookieValue = Long.parseLong(cookies[i].getValue());  
  14.                 Date date = new Date(cookieValue);  
  15.                 out.print(date.toLocaleString());  
  16.             }  
  17.         }  
  18.           
  19.         //創建每次訪問的時候,我們都會回寫一個Cookie給客戶機,並且將Cookie的有效期設置爲30天,路徑設置成整個web應用   
  20.         Cookie cookie = new Cookie("lastAccessTime",System.currentTimeMillis()+"");  
  21.         cookie.setMaxAge(30*24*3600);  
  22.         cookie.setPath("/ServletDemo");  
  23.         response.addCookie(cookie);  
  24.           
  25.     }  
//顯示上次訪問的時間
	public void test(HttpServletRequest request,HttpServletResponse response) throws Exception{
		//設置編碼
		response.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out = response.getWriter();
		out.print("你上一次訪問的時間是:");
		
		//獲得用戶的時間cookie,並且獲取值,如果第一次的話,是沒有Cookie信息的,所以Cookie數組可能爲null,所以我們要做判斷
		Cookie cookies[] = request.getCookies();
		for(int i=0;cookies != null && i<cookies.length;i++){
			if(cookies[i].getName().equals("lastAccessTime")){
				long cookieValue = Long.parseLong(cookies[i].getValue());
				Date date = new Date(cookieValue);
				out.print(date.toLocaleString());
			}
		}
		
		//創建每次訪問的時候,我們都會回寫一個Cookie給客戶機,並且將Cookie的有效期設置爲30天,路徑設置成整個web應用
		Cookie cookie = new Cookie("lastAccessTime",System.currentTimeMillis()+"");
		cookie.setMaxAge(30*24*3600);
		cookie.setPath("/ServletDemo");
		response.addCookie(cookie);
		
	}

在doGet方法中調用這個方法測試一下:
當我們第一次訪問ServletCookie的時候,因爲是沒有Cookie的信息的,所以顯示爲空,但是會回寫一個Cookie給瀏覽器


當我們再次去訪問ServletCookie的時候,就會攜帶上次回寫的Cookie信息,同時此時服務器也會回寫給客戶機最新的訪問時間Cookie信息:


我們也是可以看到的是這次攜帶的Cookie中的lastAccessTime字段的值是和我們第一次訪問的時候服務器回寫給客戶機的Cookie中的字段值相等的

下面我們在來一下怎麼刪除一個Cookie信息,有時候我們發現一個Cookie的信息沒有用了,但是他的有效期還沒過期,所以這時候需要去刪除這個Cookie,我們可以直接在瀏覽器中的直接刪除Cookie資源的,當然我們也可以直接在代碼中刪除的,原理很簡單,就是在回寫一個相同的Cookie覆蓋想要刪除的Cookie,並且將這個Cookie的有效時間設置成0,這樣就可以刪除Cookie了,比如我們刪除我們上面的那個Cookie信息:
  1. //刪除Cookie信息   
  2.     public void test1(HttpServletRequest request,HttpServletResponse response){  
  3.         //下面是刪除cookie   
  4.         Cookie cookie = new Cookie("lastAccessTime",System.currentTimeMillis()+"");  
  5.         cookie.setMaxAge(0);//將有效期設置成0   
  6.         cookie.setPath("/ServletDemo");//設置的路徑必須要和之前的一樣,否則是刪除不了的   
  7.         response.addCookie(cookie);  
  8.     }  
//刪除Cookie信息
	public void test1(HttpServletRequest request,HttpServletResponse response){
		//下面是刪除cookie
		Cookie cookie = new Cookie("lastAccessTime",System.currentTimeMillis()+"");
		cookie.setMaxAge(0);//將有效期設置成0
		cookie.setPath("/ServletDemo");//設置的路徑必須要和之前的一樣,否則是刪除不了的
		response.addCookie(cookie);
	}

我們在次訪問ServletCookie的時候,這時候還是會攜帶一個Cookie信息的,這個Cookie信息是上一次的,此時服務器還是會回寫一個有效期爲0的Cookie給客戶機



當我們再次訪問ServletCookie的時候,這時候就不會在攜帶Cookie信息了


這樣我們就成功刪除了lastAccessTime這個Cookie的信息


我們在來總結一些Cookie的一些細節:

一個Cookie只能標識一種信息,它至少含有一個標識該信息的名稱(NAME)和設置值(VALUE)。 
一個WEB站點可以給一個WEB瀏覽器發送多個Cookie,一個WEB瀏覽器也可以存儲多個WEB站點提供的Cookie。
瀏覽器一般只允許存放300個Cookie,每個站點最多存放20個Cookie,每個Cookie的大小限制爲4KB。
如果創建了一個cookie,並將他發送到瀏覽器,默認情況下它是一個會話級別的cookie(即存儲在瀏覽器的內存中),用戶退出瀏覽器之後即被刪除。若希望瀏覽器將該cookie存儲在磁盤上,則需要使用maxAge,並給出一個以秒爲單位的時間。將最大時效設爲0則是命令瀏覽器刪除該cookie。
注意,刪除cookie時,path必須一致,否則不會刪除


二、Session

Session是服務器端技術,利用這個技術,服務器在運行時可以爲每一個用戶的瀏覽器創建一個其獨享的session對象,由於session爲用戶瀏覽器獨享,所以用戶在訪問服務器的web資源時,可以把各自的數據放在各自的session中,當用戶再去訪問服務器中的其它web資源時,其它web資源再從用戶各自的session中取出數據爲用戶服務。


同時Session也是一個域對象,我們之前介紹了兩個域對象:ServletContext和Request,那麼Session域的作用範圍是:默認情況下是在一個會話期間,當然這個範圍我們是可以設置的,設置之後可以在多個會話之間。那麼Session的生命週期是:

1.Session什麼時候創建:

某server端程序(如Servlet)調用HttpServletRequest.getSession(true)這樣的語句時纔會被創建。

2.Session什麼時候銷燬:

Session在下列情況下被刪除: 
A.程序調用HttpSession.invalidate() 

B.距離上一次收到客戶端發送的session id時間間隔超過了session的最大有效時間 
C.服務器進程被停止 
再次注意關閉瀏覽器只會使存儲在客戶端瀏覽器內存中的session cookie失效,不會使服務器端的session對象失效。


下面在來看一下Session技術原理:



下面在來看一下Session的相關api:

getAttribute(String name)/getAttributeNames()/setAttribute(String name)/removeAttribute(String name):這些方法都是和Session域中的值有關的方法,和之前的ServletContext,Request域的是一樣的

getCreationTime():獲取Session的創建時間

getId():獲取session的id

getServletContext():獲取ServletContext對象

invalidate():刪除session的方法,就是將session設置成無效的

setMaxInactiveInterval(int interval):這個方法設置session的最大有效時間


下面來看一下Session的第一個例子:

  1. HttpSession session = request.getSession();  
  2. System.out.println("SessionObject:"+session);  
  3. System.out.println("SessionId:"+session.getId());  
HttpSession session = request.getSession();
System.out.println("SessionObject:"+session);
System.out.println("SessionId:"+session.getId());

打印Session的對象和ID


我們定義兩個Servlet,將上面的代碼拷貝到Servlet中,訪問第一個Servlet1,然後再訪問第二個Servlet2,我們看一下打印結果:

運行結果:

SessionObject:org.apache.catalina.session.StandardSessionFacade@7244001e
SessionId:96F3F15432E0660E0654B1CE240C4C36
SessionObject:org.apache.catalina.session.StandardSessionFacade@7244001e
SessionId:96F3F15432E0660E0654B1CE240C4C36


我們可以看到我訪問了兩個不同的Servlet,但是通過getSession()方法獲取到的Session是同一個,所以getSession()這個方法內部的執行過程是先看有沒有Session,如果有就不創建了,沒有的話就直接創建。

其實request還有一個方法:getSession(boolean mode)

如果mode是true的話,效果是和getSession()方法的效果一樣的

如果mode是false的話,效果是:如果有Session就返回,沒有的話,就不創建session,而返回一個null


上面我們看到了Session的生命週期,session銷燬的時機並不是關閉瀏覽器,而是在用戶在一段時間內(默認是30分鐘)不使用了,就自動銷燬的,比如:一個人打開一個瀏覽器,開啓一個session,這時候他離開了超過30分鐘,等再回來操作的時候,session就被銷燬了,當然這個有效時間是可以設置的,上面講到了兩種方法:

一種是通過web.xml文件中配置(單位是分鐘):

  1. <session-config>  
  2.     <session-out>10</session-out>  
  3. </session-config>  
	<session-config>
		<session-out>10</session-out>
	</session-config>


另一種是通過代碼設置(單位是秒):

session.setMaxInactiveInterval(30*60);


那麼我們getSession()方法是怎麼判斷Session是存在的呢?其實Session的實現原理是基於Cookie的技術實現的,每次我們在創建一個session的時候,都會有一個對應的sessionid,然後會將這個sessionid信息寫入到cookie中,但是這個cookie的有效時間是這個瀏覽器進程中,即這個cookie是在服務器的內存中的,不會回寫給客戶機緩存到磁盤中。所以我們會在一個瀏覽器中使用同一個session的,當我們關閉瀏覽器的時候,這個cookie就被銷燬了,那麼sessionid就沒有了,當我們在打開瀏覽器的時候,服務端又會重新創建一個session。所以我們會誤認爲session的生命週期是瀏覽器進程。當用戶關閉了瀏覽器雖然session cookie已經消失,但session對象仍然保存在服務器端 


假如現在有一個人買了幾千本書,準備去付款,但是不小心把瀏覽器關了,這時候在打開瀏覽器的時候就會發現之前買的書都不在了,這種情況是很嚴重的,所以我們要改善這種情況,修改的原理很簡單,我們知道那個sessionid是寫在cookie中的,但是這個cookie默認的情況下只存在服務器的內存中的,不會回寫到客戶機瀏覽器的緩存中。如果我們要是修改的話,只需要修改這個cookie的有效期,將這個cookie回寫到客戶機瀏覽器中即可。

  1. HttpSession session = request.getSession();  
  2. String sessionId = session.getId();  
  3. Cookie cookie = new Cookie("JSESSIONID",sessionId);//把系統的session id的覆蓋掉   
  4. cookie.setPath("/ServletDemo");  
  5. cookie.setMaxAge(30*360);//30分鐘,因爲session的生命週期是30分鐘   
  6. response.addCookie(cookie);  
HttpSession session = request.getSession();
String sessionId = session.getId();
Cookie cookie = new Cookie("JSESSIONID",sessionId);//把系統的session id的覆蓋掉
cookie.setPath("/ServletDemo");
cookie.setMaxAge(30*360);//30分鐘,因爲session的生命週期是30分鐘
response.addCookie(cookie);

默認攜帶sessionid信息的cookie字段是:JESSIONID
我們可以覆蓋這個信息的,將有效期設置成30分鐘,因爲默認的session的有效期是30分鐘,所以如果將cookie的有效期大於30分鐘的話,是沒有意義的,因爲當超過30分鐘之後,session就銷燬了,即使攜帶cookie過去,也是找不到對應的session了,但是我們也是可以設置session的有效時間的。所以我們最好將這個cookie的有效期設置要不超過session的有效期,不然是沒有意義的。運行之後,我們可以查看瀏覽器中的Cookie信息,這裏使用IE瀏覽器,點擊F12鍵,選擇緩存-》查看Cookie信息:



上面的就將sessionid的信息以cookie的形式回寫到客戶機緩存中,當我們關閉瀏覽器中,在開啓瀏覽器的話,還是可以訪問到我們之前的數據的,其實這也就實現了多個瀏覽器之間共享一個session的機制。


但是現在還有一個問題,如果用戶手賤他把瀏覽器設置成禁止Cookie了,那麼又悲劇了,我們服務端寫給客戶機的Cookie,但是客戶機是不接受的,所以這樣也是會丟失數據的。那麼這時候我們又該怎麼辦呀?

一種新技術又產生了,那就是URL重寫:

我們首先來看一下,用戶禁止Cookie之後,我們會發現在同一個瀏覽器進程中,訪問不同的Servlet,數據也是沒有的:

我們這裏定義三個Servlet:IndexServlet,BuyServlet,PayServlet

模擬的場景就是:IndexServlet是首頁,裏面寫入連個超鏈接,分別鏈接到購買物品Servlet:BuyServlet,和付款Servlet:PayServlet,代碼如下:

IndexServlet:

  1. protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {  
  2.         req.getSession();  
  3.         String buyUrl = "/ServletDemo/BuyServlet";  
  4.         String payUrl = "/ServletDemo/PayServlet";  
  5.         resp.getWriter().print("<a href='"+buyUrl+"'>Buy</a><br/>");  
  6.         resp.getWriter().print("<a href='"+payUrl+"'>Pay</a><br/>");  
  7.     }  
protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
		req.getSession();
		String buyUrl = "/ServletDemo/BuyServlet";
		String payUrl = "/ServletDemo/PayServlet";
		resp.getWriter().print("<a href='"+buyUrl+"'>Buy</a><br/>");
		resp.getWriter().print("<a href='"+payUrl+"'>Pay</a><br/>");
	}

BuyServlet:

  1. protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {  
  2.         //購買了一件物品,將這個物品存入到session中,然後將這個sessionid回寫到客戶機,有效時間是30分鐘   
  3.         HttpSession session = req.getSession();  
  4.         session.setAttribute("store""air-confication");  
  5.         Cookie cookie = new Cookie("JSESSIONID",session.getId());//把系統的session id的覆蓋掉   
  6.         cookie.setMaxAge(30*360);  
  7.         cookie.setPath("/ServletDemo");  
  8.         resp.addCookie(cookie);  
  9.     }  
protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
		//購買了一件物品,將這個物品存入到session中,然後將這個sessionid回寫到客戶機,有效時間是30分鐘
		HttpSession session = req.getSession();
		session.setAttribute("store", "air-confication");
		Cookie cookie = new Cookie("JSESSIONID",session.getId());//把系統的session id的覆蓋掉
		cookie.setMaxAge(30*360);
		cookie.setPath("/ServletDemo");
		resp.addCookie(cookie);
	}

PayServlet:

  1. public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {  
  2.         //我們從session中拿去商品進行顯示   
  3.         HttpSession session = request.getSession();  
  4.         response.getWriter().print("you buy store is:"+session.getAttribute("store"));  
  5.     }  
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
		//我們從session中拿去商品進行顯示
		HttpSession session = request.getSession();
		response.getWriter().print("you buy store is:"+session.getAttribute("store"));
	}


我們試驗禁止瀏覽器的Cookie:

使用IE瀏覽器:選擇工具-》Internet選項-》隱私-》高級-》禁止Cookie

這裏有一個問題要注意,瀏覽器使用localhost域名,阻止Cookie是沒有效果的,這個問題糾結了好長時間!我們需要直接使用:127.0.0.1來測試:http://127.0.0.1:8080/ServletDemo/IndexServlet



我們點擊Buy,這時候會回寫一個sessionid,但是客戶機禁止Cookie了,所以瀏覽器沒有緩存sessionid,不信的話我們可以查看瀏覽器中緩存的Cookie信息,是沒有我們的Cookie的。

然後我們返回在點擊Pay的時候發現,購買的東東爲null



這個我們在同一個瀏覽器中都發現購買的東西都不見了,這個很蛋疼的,我們之前不回寫sessionid,即使我們禁止Cookie的話也是沒有影響的,因爲那個Cookie是在瀏覽器進程中的,不會回寫,所以禁止Cookie沒有影響。但是我們現在是回寫了sessionid的Cookie,所以這個問題我們得解決,上面說過了使用URL重寫技術,其實這個技術很簡單的,就是將網站中的所以url後面會攜帶一個sessionid,這樣就可以保證當禁止Cookie的時候,我們還是能夠得到sessionid,然後找到相應的session。測試:


這裏只需要修改IndexServlet中的代碼:

  1. protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {  
  2.         resp.setDateHeader("expires"0);  
  3.         req.getSession();  
  4.         String buyUrl = resp.encodeURL("/ServletDemo/BuyServlet");  
  5.         String payUrl = resp.encodeURL("/ServletDemo/PayServlet");  
  6.         resp.getWriter().print("<a href='"+buyUrl+"'>Buy</a><br/>");  
  7.         resp.getWriter().print("<a href='"+payUrl+"'>Pay</a><br/>");  
  8.     }  
protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
		resp.setDateHeader("expires", 0);
		req.getSession();
		String buyUrl = resp.encodeURL("/ServletDemo/BuyServlet");
		String payUrl = resp.encodeURL("/ServletDemo/PayServlet");
		resp.getWriter().print("<a href='"+buyUrl+"'>Buy</a><br/>");
		resp.getWriter().print("<a href='"+payUrl+"'>Pay</a><br/>");
	}


一定要執行req.getSession()這個方法,來生成一個session,不然encodeURL中是不會得到sessionid的

這裏就是重寫了,當然我們直接使用encodeURL這個方法,而不是直接自己去將sessionid拼接到url中

訪問IndexServlet:



我們可以看到響應信息是我們回寫客戶機顯示的連接,url後面是會攜帶jsessionid字段的

這樣,即使用戶禁止了Cookie,也是可以實現數據保留的。但是我們也看到了URL重寫的弊端,對於每個url都必須重寫,這個是很麻煩的,而且還有一個問題,針對URL重寫技術,當我們關閉瀏覽器的時候,再去訪問的時候還是失敗,因爲我們關閉瀏覽器,再去打開瀏覽器訪問IndexServlet的時候,會重新創建一個session,這時候在將sessionid重寫到url中,所以不是之前的session了,至此,也知道了,URL重寫是做不到多個瀏覽器進程之間的session數據共享的,那麼這個問題我們需要去該嗎?這個是沒辦法修改的,只能做到這一步了。


下面在來看一下上面內容的一些細節:

當我們在使用URL重寫技術的時候,我們把瀏覽器中的Cookie不禁止,即保留Cookie和URL重寫這兩種技術

我們會發現當用戶攜帶Cookie過來的時候,url鏈接之後是沒有jsessionid字段了,這個得益於encodeURL方法中做了判斷

所以優先級是Cookie中的sessionid高


下面我們在來通過一個實例來總結一下所講的內容

實例:防止表單重複提交

方法一:前端使用JS技術

  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">  
  2. <html>  
  3.   <head>  
  4.       
  5.     <title>My JSP '1.jsp' starting page</title>  
  6.       
  7.     <meta http-equiv="pragma" content="no-cache">  
  8.     <meta http-equiv="cache-control" content="no-cache">  
  9.     <meta http-equiv="expires" content="0">      
  10.     <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">  
  11.     <meta http-equiv="description" content="This is my page">  
  12.   
  13.     <script type="text/javascript">  
  14.         var iscommitted = false;  
  15.         function dosubmits(){  
  16.             if(!iscommitted){  
  17.                 iscommitted = true;  
  18.                 return true;  
  19.             }else{  
  20.                 return false;  
  21.             }  
  22.         }  
  23.     </script>  
  24.       
  25.     <script type="text/javascript">  
  26.         function dosubmit(){  
  27.             var input = document.getElementById("submit");  
  28.             input.disabled = 'disabled';  
  29.             return true;  
  30.         }  
  31.     </script>  
  32.   
  33.   </head>  
  34.     
  35.   <body>  
  36.     <form action="/ServletDemo/FormResubmit1" method="post" οnsubmit="return dosubmits()">  
  37.         <input type="text" name="username">  
  38.         <input id="submit" type="submit" value="提交">  
  39.     </form>  
  40.   </body>  
  41. </html>  
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    
    <title>My JSP '1.jsp' starting page</title>
    
	<meta http-equiv="pragma" content="no-cache">
	<meta http-equiv="cache-control" content="no-cache">
	<meta http-equiv="expires" content="0">    
	<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
	<meta http-equiv="description" content="This is my page">

	<script type="text/javascript">
		var iscommitted = false;
		function dosubmits(){
			if(!iscommitted){
				iscommitted = true;
				return true;
			}else{
				return false;
			}
		}
	</script>
	
	<script type="text/javascript">
		function dosubmit(){
			var input = document.getElementById("submit");
			input.disabled = 'disabled';
			return true;
		}
	</script>

  </head>
  
  <body>
  	<form action="/ServletDemo/FormResubmit1" method="post" οnsubmit="return dosubmits()">
  		<input type="text" name="username">
  		<input id="submit" type="submit" value="提交">
  	</form>
  </body>
</html>

我們定義了兩個js函數,這兩個js函數都是可以實現防止表單的重複提交

dosubmits()函數是在用戶提交一次之後就通過一個變量來控制不允許提交

dosubmit()函數是在用戶提交一次之後就將提交按鈕設置成不可點擊

但是使用上面的這種方法來防止表單重複提交是有問題的,首先客戶是可以通過查看文件的源代碼,然後修改的代碼的

而且,這種方式解決不了一些問題,比如:當用戶點擊提交之後,他立刻點擊刷新按鈕或者回退按鈕,然後又可以提交了,所以說前端技術是不能徹底防止表單的重複提交,但是我們還是要這麼做的,因爲這樣做至少還是能起到一定的防止效果的,當然我們還需要在服務器端做處理的,實現原理是:

服務器端首先產生一個隨機數,標記一個唯一的表單,然後將這個隨機數寫入到session域中保存,同時我們將這個隨機數使用轉發技術攜帶給一個jsp/html表單中,在使用隱藏標籤將這個隨機數攜帶給提交的servlet中,然後再這個servlet中拿到這個隱藏標籤的參數值,和我們之前寫入到session域中的那個隨機數進行比較,正確的話,說明是第一次提交,我們這時候就將那個隨機數從session域中刪除,當用戶再一次點擊提交表單的時候,再來判斷的時候,因爲session域中沒有了這個隨機數,所以是提交失敗的


下面來看一下實現代碼:

IndexServlet:

  1. package com.weijia.servletsession;  
  2.   
  3. import java.io.IOException;  
  4. import java.security.MessageDigest;  
  5. import java.util.Random;  
  6.   
  7. import javax.servlet.ServletException;  
  8. import javax.servlet.http.HttpServlet;  
  9. import javax.servlet.http.HttpServletRequest;  
  10. import javax.servlet.http.HttpServletResponse;  
  11.   
  12. import sun.misc.BASE64Encoder;  
  13.   
  14. //表單重複提交   
  15. public class IndexServlet extends HttpServlet{  
  16.   
  17.     private static final long serialVersionUID = 1L;  
  18.   
  19.     @Override  
  20.     protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {  
  21.         doPost(req,resp);  
  22.     }  
  23.   
  24.     @Override  
  25.     protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {  
  26.         resp.setDateHeader("expires"0);  
  27.         try{  
  28.             test(req,resp);  
  29.         }catch(Exception e){  
  30.             e.printStackTrace();  
  31.         }  
  32.     }  
  33.       
  34.       
  35.     //產生表單   
  36.     public void test(HttpServletRequest request,HttpServletResponse response) throws Exception{  
  37.         //產生表單號   
  38.         String token = TokenProcessor.generateToken();  
  39.         request.getSession().setAttribute("token", token);  
  40.         request.getRequestDispatcher("/form.jsp").forward(request, response);  
  41.     }  
  42.       
  43.   
  44. }  
  45.   
  46. //單例模式,一個人報數比多個人報數重複率底   
  47. class TokenProcessor{  
  48.       
  49.     private static final TokenProcessor instance = new TokenProcessor();  
  50.       
  51.     private TokenProcessor(){  
  52.           
  53.     }  
  54.       
  55.     public static TokenProcessor getInstance(){  
  56.         return instance;  
  57.     }  
  58.       
  59.     public static String generateToken(){  
  60.         String token = System.currentTimeMillis() + new Random().nextInt() + "";  
  61.         try{  
  62.             MessageDigest md = MessageDigest.getInstance("md5");  
  63.             byte[] md5 = md.digest(token.getBytes());  
  64.             //base64   
  65.             //0011 0010 1100 1101 0010 1001   
  66.             //00001100 00101100 00110100 00101001:最小是0,最大的數是63,共64個數   
  67.             //base碼錶   
  68.             //數據傳輸的作用   
  69.             BASE64Encoder encoder = new BASE64Encoder();  
  70.             return encoder.encode(md5);  
  71.         }catch(Exception e){  
  72.             return null;  
  73.         }  
  74.     }  
  75.       
  76. }  
package com.weijia.servletsession;

import java.io.IOException;
import java.security.MessageDigest;
import java.util.Random;

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

import sun.misc.BASE64Encoder;

//表單重複提交
public class IndexServlet extends HttpServlet{

	private static final long serialVersionUID = 1L;

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

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
		resp.setDateHeader("expires", 0);
		try{
			test(req,resp);
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	
	//產生表單
	public void test(HttpServletRequest request,HttpServletResponse response) throws Exception{
		//產生表單號
		String token = TokenProcessor.generateToken();
		request.getSession().setAttribute("token", token);
		request.getRequestDispatcher("/form.jsp").forward(request, response);
	}
	

}

//單例模式,一個人報數比多個人報數重複率底
class TokenProcessor{
	
	private static final TokenProcessor instance = new TokenProcessor();
	
	private TokenProcessor(){
		
	}
	
	public static TokenProcessor getInstance(){
		return instance;
	}
	
	public static String generateToken(){
		String token = System.currentTimeMillis() + new Random().nextInt() + "";
		try{
			MessageDigest md = MessageDigest.getInstance("md5");
			byte[] md5 = md.digest(token.getBytes());
			//base64
			//0011 0010 1100 1101 0010 1001
			//00001100 00101100 00110100 00101001:最小是0,最大的數是63,共64個數
			//base碼錶
			//數據傳輸的作用
			BASE64Encoder encoder = new BASE64Encoder();
			return encoder.encode(md5);
		}catch(Exception e){
			return null;
		}
	}
	
}


這裏面我們定義了一個令牌發生器(隨機數產生器),使用的是單例模式

但是有一個問題就是:使用Random類產生的隨機數的長度是不一定的,我們現在想讓每次得到的隨機數的長度是一樣的,此時我們會想到MD5,首先每個數據的MD5是唯一的,還有MD5的長度是一定的128位(16Bytes),但是還有一個問題那個MD5是一個字節數組,當我們將其轉化成String類型的時候,不管使用什麼碼錶,都會出現亂碼的問題,在隨機數中出現了亂碼,那就等於是死了,所以我們需要解決這個問題,這時候我們想到了BASE64編碼,因爲我們知道任何字節數據通過BASE64編碼之後生成的字節數據的大小是在0~63之間的(原理很簡單,就是將字節數組從左到右,每六位是一組,然後再高位補足兩個0,那麼這個字節大小的範圍就是在0~63了),同時BASE64有一套自己的碼錶,裏面只有大小寫字母和數字,這樣就不會產生亂碼了。


下面在來看一下我們轉發的form.jsp中的表單定義:

  1. <form action="/ServletDemo/FormResubmit" method="post">  
  2.     <input type="hidden" name="token" value="${token}">  
  3.     用戶名:<input type="text" name="username"><br/>  
  4.     <input type="submit" value="提交">  
  5. </form>  
  <form action="/ServletDemo/FormResubmit" method="post">
  	<input type="hidden" name="token" value="${token}">
  	用戶名:<input type="text" name="username"><br/>
  	<input type="submit" value="提交">
  </form>

這裏我們使用了隱藏標籤hidden,同時也使用了el表達式來取出之前存入到session域中的token字段值,


下面再來看一下處理這個表單的Servlet:FormResubmit

  1. package com.weijia.servletsession;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import javax.servlet.ServletException;  
  6. import javax.servlet.http.HttpServlet;  
  7. import javax.servlet.http.HttpServletRequest;  
  8. import javax.servlet.http.HttpServletResponse;  
  9.   
  10. //表單重複提交   
  11. public class FormResubmit extends HttpServlet{  
  12.   
  13.     @Override  
  14.     protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {  
  15.         doPost(req,resp);  
  16.     }  
  17.   
  18.     @Override  
  19.     protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {  
  20.         boolean flag = isTokenValid(req);  
  21.         if(!flag){  
  22.             System.out.println("請不要重複提交");  
  23.             return;  
  24.         }  
  25.         System.out.println("向數據庫中註冊用戶--------");  
  26.         req.getSession().removeAttribute("token");  
  27.           
  28.     }  
  29.       
  30.     //驗證表單提交是否有效,返回true,表示表單可以提交   
  31.     public boolean isTokenValid(HttpServletRequest request){  
  32.         //首先判斷傳遞過來的表單號是否有效   
  33.         String clientToken = request.getParameter("token");  
  34.         if(clientToken == null){  
  35.             return false;  
  36.         }  
  37.           
  38.         //然後再判斷服務器端session域中時候又令牌信息了   
  39.         String serverToken = (String)request.getSession().getAttribute("token");  
  40.         if(serverToken == null){  
  41.             return false;  
  42.         }  
  43.           
  44.         //在比較表單攜帶過來的隨機數和session域中的令牌信息是否一致   
  45.         if(!clientToken.equals(serverToken)){  
  46.             return false;  
  47.         }  
  48.           
  49.         return true;  
  50.     }  
  51.       
  52. }  
package com.weijia.servletsession;

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 FormResubmit extends HttpServlet{

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

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
		boolean flag = isTokenValid(req);
		if(!flag){
			System.out.println("請不要重複提交");
			return;
		}
		System.out.println("向數據庫中註冊用戶--------");
		req.getSession().removeAttribute("token");
		
	}
	
	//驗證表單提交是否有效,返回true,表示表單可以提交
	public boolean isTokenValid(HttpServletRequest request){
		//首先判斷傳遞過來的表單號是否有效
		String clientToken = request.getParameter("token");
		if(clientToken == null){
			return false;
		}
		
		//然後再判斷服務器端session域中時候又令牌信息了
		String serverToken = (String)request.getSession().getAttribute("token");
		if(serverToken == null){
			return false;
		}
		
		//在比較表單攜帶過來的隨機數和session域中的令牌信息是否一致
		if(!clientToken.equals(serverToken)){
			return false;
		}
		
		return true;
	}
	
}


這樣我們就可以防止表單的重複提交了,其實上面的實現原理就是struts框架中防止表單重複提交的原理


總結:這一篇主要講解了Session和Cookie的相關知識,到此關於JavaWeb的知識,大體上說的差不多了,後面只剩下一個JSP和標籤庫的相關知識了,這兩篇文章會在後面更新。

 

發佈了107 篇原創文章 · 獲贊 43 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章