Servlet第五篇【介紹會話技術、Cookie的API、詳解、應用】(修訂版)

Servlet第五篇【介紹會話技術、Cookie的API、詳解、應用】(修訂版)

原創 Java3y Java3y 2019-02-03

前言

只有光頭才能變強。

文本已收錄至我的GitHub倉庫,歡迎Star:https://github.com/ZhongFuCheng3y/3y

什麼是會話技術

基本概念: 指用戶開一個瀏覽器,訪問一個網站,只要不關閉該瀏覽器,不管該用戶點擊多少個超鏈接,訪問多少資源,直到用戶關閉瀏覽器,整個這個過程我們稱爲一次會話.


爲什麼我們要使用會話技術?

會話跟蹤技術可以解決我們很多很多問題。

  • 在論壇登陸的時候,很多時候會有一個小框框問你是否要自動登陸,當你下次登陸的時候就不用輸入密碼了

 

  • 根據我以前瀏覽過的商品,猜我喜歡什麼商品


Cookie

會話跟蹤技術有Cookie和SessionCookie技術是先出現的。我們先講Cookie技術吧。

什麼是Cookie

Cookie是由W3C組織提出,最早由netscape社區發展的一種機制

  • 網頁之間的交互是通過HTTP協議傳輸數據的,而Http協議是無狀態的協議。無狀態的協議是什麼意思呢?一旦數據提交完後,瀏覽器和服務器的連接就會關閉,再次交互的時候需要重新建立新的連接

  • 服務器無法確認用戶的信息,於是乎,W3C就提出了:給每一個用戶都發一個通行證,無論誰訪問的時候都需要攜帶通行證,這樣服務器就可以從通行證上確認用戶的信息。通行證就是Cookie


Cookie的流程:瀏覽器訪問服務器,如果服務器需要記錄該用戶的狀態,就使用response向瀏覽器發送一個Cookie,瀏覽器會把Cookie保存起來。當瀏覽器再次訪問服務器的時候,瀏覽器會把請求的網址連同Cookie一同交給服務器

Cookie API

  • Cookie類用於創建一個Cookie對象

  • response接口中定義了一個addCookie方法,它用於在其響應頭中增加一個相應的Set-Cookie頭字段

  • request接口中定義了一個getCookies方法,它用於獲取客戶端提交的Cookie

常用的Cookie方法:

  • public Cookie(String name,String value)

  • setValue與getValue方法

  • setMaxAge與getMaxAge方法

  • setPath與getPath方法

  • setDomain與getDomain方法

  • getName方法

簡單使用Cookie

  • 創建Cookie對象,發送Cookie給瀏覽器、

       //設置response的編碼

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



       //創建Cookie對象,指定名稱和值

       Cookie cookie = new Cookie("username", "zhongfucheng");



       //向瀏覽器給一個Cookie

       response.addCookie(cookie);



       response.getWriter().write("我已經向瀏覽器發送了一個Cookie");
  • 瀏覽器本身沒有任何Cookie

  • 訪問Servlet1,再回到文件夾中,還是沒有發現Cookie,這是爲什麼呢?我明明向瀏覽器發送了一個Cookie的

  • 原來發送Cookie給瀏覽器是需要設置Cookie的時間的。在給瀏覽器之前,設置一下Cookie的時間

       //設置Cookie的時間

       cookie.setMaxAge(1000);
  • 再次訪問,已經發現文件夾中多了個Cookie的文本了


Cookie細節

Cookie不可跨域名性

  • 很多人在初學的時候可能有一個疑問:在訪問Servlet的時候瀏覽器是不是把所有的Cookie都帶過去給服務器會不會修改了別的網站的Cookie

  • 答案是否定的。Cookie具有不可跨域名性。瀏覽器判斷一個網站是否能操作另一個網站的Cookie的依據是域名。所以一般來說,當我訪問baidu的時候,瀏覽器只會把baidu頒發的Cookie帶過去,而不會帶上google的Cookie。

Cookie保存中文

  • 上面我們的例子保存的是英文字符,下面我們來看下保存中文字符會怎麼樣。

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

       PrintWriter printWriter = response.getWriter();



       String name = "中國";

       Cookie cookie = new Cookie("country", name);

       cookie.setMaxAge(2000);

       response.addCookie(cookie);



       printWriter.write("我頒發了一個Cookie,值保存的是中文數據");
  • 訪問Servlet1,好吧。出異常了!

  • 中文屬於Unicode字符,英文數據ASCII字符,中文佔4個字符或者3個字符,英文佔2個字符。

  • 解決:Cookie使用Unicode字符時需要對Unicode字符進行編碼。

       //對Unicode字符進行編碼

       Cookie cookie = new Cookie("country", URLEncoder.encode(name, "UTF-8"));

再次訪問Servlet1,已經把Cookie成功頒發給瀏覽器了

  • 我們發現Cookie保存在硬盤的中文數據是經過編碼的,那麼我們在取出Cookie的時候要對中文數據進行解碼

      Cookie[] cookies = request.getCookies();

       for (int i = 0; cookies != null && i < cookies.length; i++) {

           String name = cookies[i].getName();



           //經過URLEncoding就要URLDecoding

           String value = URLDecoder.decode(cookies[i].getValue(), "UTF-8");



           printWriter.write(name + "------" + value);

       }
  • 取出存進Cookie的值


Cookie的有效期

Cookie的有效期是通過setMaxAge()來設置的

  • 如果MaxAge爲正數瀏覽器會把Cookie寫到硬盤中,只要還在MaxAge秒之前,登陸網站時該Cookie就有效【不論關閉了瀏覽器還是電腦】

  • 如果MaxAge爲負數Cookie是臨時性的,僅在本瀏覽器內有效,關閉瀏覽器Cookie就失效了,Cookie不會寫到硬盤中。Cookie默認值就是-1。這也就爲什麼在我第一個例子中,如果我沒設置Cookie的有效期,在硬盤中就找不到對應的文件。

  • 如果MaxAge爲0,則表示刪除該Cookie。Cookie機制沒有提供刪除Cookie對應的方法,把MaxAge設置爲0等同於刪除Cookie


Cookie的修改和刪除

  • 上面我們已經知道了Cookie機制沒有提供刪除Cookie的方法。其實細心點我們可以發現,Cookie機制也沒有提供修改Cookie的方法。那麼我們怎麼修改Cookie的值呢

  • Cookie存儲的方式類似於Map集合,如下圖所示

  •  

    Cookie的名稱相同,通過response添加到瀏覽器中,會覆蓋原來的Cookie

  •  

    以country爲名保存的是%E4%B8%AD%E5%9B%BD,下面我再以country爲名,把值改變一下。 


 
       String name = "看完博客就點贊";



       //對Unicode字符進行編碼

       Cookie cookie = new Cookie("country", URLEncoder.encode(name, "UTF-8"));
  • 再次查看Cookie的時候,值已經改變了,但是文件還是那一份

  • 現在我要刪除該Cookie,把MaxAge設置爲0,並添加到瀏覽器中即可

       String name = "看完博客就點贊";



       //對Unicode字符進行編碼

       Cookie cookie = new Cookie("country", URLEncoder.encode(name, "UTF-8"));



       //一定不要忘記添加到瀏覽器中

       cookie.setMaxAge(0);

       response.addCookie(cookie);



       printWriter.write("我刪除了該Cookie");

訪問Servlet,在硬盤已經找不到Cookie的文件了!

  •  

    注意:刪除,修改Cookie時,新建的Cookie除了value、maxAge之外的所有屬性都要與原Cookie相同。否則瀏覽器將視爲不同的Cookie,不予覆蓋,導致刪除修改失敗

  •  

    我們來試驗一下把。

           String name = "看完博客就點贊";
    
    
    
           //對Unicode字符進行編碼
    
           Cookie cookie = new Cookie("country", URLEncoder.encode(name, "UTF-8"));
    
    
    
           //一定不要忘記添加到瀏覽器中
    
           cookie.setMaxAge(10000);
    
           response.addCookie(cookie);

     

 

  • 上面新建了一個Cookie,我修改下Cookie的其他屬性,再刪除,看能否把Cookie刪除掉

       //一定不要忘記添加到瀏覽器中



       cookie.setPath("/ouzicheng");

       cookie.setMaxAge(0);

       response.addCookie(cookie);



       printWriter.write("刪除一個Cookie");
  • 結果Cookie還在硬盤中

 


Cookie的域名

Cookie的domain屬性決定運行訪問Cookie的域名。domain的值規定爲“.域名”

  •  

    Cookie的隱私安全機制決定Cookie是不可跨域名的。也就是說www.baidu.com和www.google.com之間的Cookie是互不交接的。即使是同一級域名,不同二級域名也不能交接,也就是說:www.goole.com和www.image.goole.com的Cookie也不能訪問

  •  

    我在本地上配置了3個虛擬主機,localhost,www.zhongfucheng.com,www.image.zhongfucheng.com【如果不知道怎麼配置,在我Tomcat的博客有】

     

  • 我用www.zhongfucheng.com域名發送了一個Cookie給瀏覽器

       Cookie cookie = new Cookie("name", "zhongfucheng");

       cookie.setMaxAge(1000);

       response.addCookie(cookie);



       printWriter.write("使用www.zhongfucheng.com域名添加了一個Cookie");

 

  • 首先,證明了Cookie不可跨名性,localhost域名拿不到www.zhongfucheng.com頒發給瀏覽器的Cookie

  • 再使用www.image.zhongfucheng.com域名訪問,證明即使一級域名相同,二級域名不同,也不能獲取到Cookie

  • 當然,使用www.zhongfucheng.com當然能獲取到Cookie,Cookie通過請求頭帶給服務器

  • 現在我希望一級域名相同的網頁Cookie之間可以相互訪問。也就是說www.image.zhongfucheng.com可以獲取到www.zhongfucheng.com的Cookie就需要使用到domain方法。

       Cookie cookie = new Cookie("name", "ouzicheng");

       cookie.setMaxAge(1000);

       cookie.setDomain(".zhongfucheng.com");

       response.addCookie(cookie);



       printWriter.write("使用www.zhongfucheng.com域名添加了一個Cookie,只要一級是zhongfucheng.com即可訪問");

  1. 使用www.zhongfucheng.com發佈一個Cookie 

     

  2.  

    使用www.image.zhongfucheng.com域名訪問一下。發現可以獲取到Cookie了

     


Cookie的路徑

Cookie的path屬性決定允許訪問Cookie的路徑

  •  

    一般地,Cookie發佈出來,整個網頁的資源都可以使用。現在我只想Servlet1可以獲取到Cookie,其他的資源不能獲取

     

  •  

    使用Servlet2頒發一個Cookie給瀏覽器,設置路徑爲"/Servlet1"。

     

       Cookie cookie = new Cookie("username", "java");

       cookie.setPath("/Servlet1");

       cookie.setMaxAge(1000);

       response.addCookie(cookie);



       printWriter.write("該Cookie只有Servlet1獲取得到");
  • 使用Servlet3訪問服務器,看看瀏覽器是否把Cookie帶上。顯然,瀏覽器訪問Servlet3並沒有把Cookie帶上。

  • 使用Servlet1訪問服務器,看看瀏覽器是否把Cookie帶上。答案是肯定的!


Cookie的安全屬性

  • HTTP協議不僅僅是無狀態的,而且是不安全的!如果不希望Cookie在非安全協議中傳輸,可以設置Cookie的secure屬性爲true,瀏覽器只會在HTTPS和SSL等安全協議中傳輸該Cookie

  • 當然了,設置secure屬性不會將Cookie的內容加密。如果想要保證安全,最好使用md5算法加密【後面有】。


Cookie的應用

顯示用戶上次訪問的時間

  •  

    其實就是每次登陸的時候,取到Cookie保存的值,再更新下Cookie的值

  •  

    訪問Serlvet有兩種情況

  •  

    第一次訪問

  •  

    已經訪問過了

     

  •  

    全部代碼如下:

           SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
           response.setContentType("text/html;charset=UTF-8");
    
           PrintWriter printWriter = response.getWriter();
    
    
    
    
    
    
    
           //獲取網頁上所有的Cookie
    
           Cookie[] cookies = request.getCookies();
    
    
    
           //判斷Cookie的值是否爲空
    
           String cookieValue = null;
    
           for (int i = 0; cookies != null && i < cookies.length; i++) {
    
    
    
               //獲取到以time爲名的Cookie
    
               if (cookies[i].getName().equals("time")) {
    
                   printWriter.write("您上次登陸的時間是:");
    
                   cookieValue = cookies[i].getValue();
    
                   printWriter.write(cookieValue);
    
    
    
                   cookies[i].setValue(simpleDateFormat.format(new Date()));
    
                   response.addCookie(cookies[i]);
    
    
    
                   //既然已經找到了就可以break循環了
    
                   break;
    
               }
    
           }
    
    
    
           //如果Cookie的值是空的,那麼就是第一次訪問
    
           if (cookieValue == null) {
    
               //創建一個Cookie對象,日期爲當前時間
    
               Cookie cookie = new Cookie("time", simpleDateFormat.format(new Date()));
    
    
    
               //設置Cookie的生命期
    
               cookie.setMaxAge(20000);
    
    
    
               //response對象回送Cookie給瀏覽器
    
               response.addCookie(cookie);
    
    
    
               printWriter.write("您是第一次登陸啊!");
    
           }

     

  • 按照正常的邏輯來寫,程序流程應該是這樣子的。先創建Cookie對象,回送Cookie給瀏覽器。再遍歷Cookie,更新Cookie的值

  •  

    但是,按照上面的邏輯是做不到的!因爲每次訪問Servlet的時候都會覆蓋原來的Cookie,取到Cookie的值永遠都是當前時間,而不是上次保存的時間。

     

  •  

    我們換一個邏輯寫:先檢查(遍歷)所有Cookie有沒有我要的,如果得不到我想要的Cookie,Cookie的值是null,那麼就是第一次登陸,於是就有了上面的代碼了。

     

  •  

    我們來看下效果吧!當我第一次登陸的時候

     

  • Cookie保存在硬盤中。

  • 再次訪問Servlet。明顯地,取到的就是Cookie的值


顯示上次瀏覽過商品

  • 我就以書籍爲例子了!首先設計Book對象

   private String id ;

   private String name ;

   private String author;



   public Book() {

   }



   public Book(String id, String name, String author) {

       this.id = id;

       this.name = name;

       this.author = author;

   }



   ...各種set、get方法
  • 設計一個簡單的數據庫存儲數據。就用LinkedHashMap集合【根據商品的id找書籍所以用Map,刪改較多所以用Linked】

   private static LinkedHashMap<String, Book> linkedHashMap = new LinkedHashMap();





   //簡化開發複雜度,book的id和商品的id相同

   static {

       linkedHashMap.put("1", new Book("1", "javaweb", "zhong"));

       linkedHashMap.put("2", new Book("2", "java", "fu"));

       linkedHashMap.put("3", new Book("3", "oracle", "cheng"));

       linkedHashMap.put("4", new Book("4", "mysql", "ou"));

       linkedHashMap.put("5", new Book("5", "ajax", "zi"));

   }



   //獲取到所有書籍

   public static LinkedHashMap getAll() {

       return linkedHashMap;

   }
  • 顯示網頁上所有的書籍【首頁】

       printWriter.write("網頁上所有的書籍:"+"<br/>");



       //拿到數據庫所有的書

       LinkedHashMap<String, Book> linkedHashMap = DB.getAll();

       Set<Map.Entry<String, Book>> entry = linkedHashMap.entrySet();



       //顯示所有的書到網頁上

       for (Map.Entry<String, Book> stringBookEntry : entry) {

           Book book = stringBookEntry.getValue();

           printWriter.write(book.getId() +"           "+ book.getName()+"<br/>");

       }

  • 接着,我們要做的就是給顯示的書籍掛上一個超鏈接,當用戶點擊想看的書籍時,就跳轉到該書籍的詳細信息頁面

  • 超鏈接應該把書的id傳遞過去,不然處理頁面是不知道用戶想看的是哪一本書的!

       //顯示所有的書到網頁上

       for (Map.Entry<String, Book> stringBookEntry : entry) {

           Book book = stringBookEntry.getValue();

           printWriter.write("<a href='/ouzicheng/Servlet2?id=" + book.getId() + "''target=_blank' +" + book.getName() + "</a>");

           printWriter.write("<br/>");

       }

  • 接收id,找到用戶想要看哪一本書,輸出該書的詳細信息

       String id = request.getParameter("id");



       //由於book的id和商品的id是一致的。獲取到用戶點擊的書

       Book book = (Book) DB.getAll().get(id);



       //輸出書的詳細信息

       printWriter.write("書的編號是:" + book.getId()+"<br/>");

       printWriter.write("書的名稱是:" + book.getName()+"<br/>");

       printWriter.write("書的作者是:" + book.getAuthor()+"<br/>")
  • 點擊想要的書籍。

  • 得到書籍的詳細信息

  •  

    既然用戶點擊了書籍,那麼服務器就應該頒發Cookie給瀏覽器,記住用戶點擊了該書籍

     

  •  

    現在問題來了,Cookie的值應該是什麼呢?試想一下,待會還要把瀏覽過的書籍顯示出來,所以用書籍的id是最好不過的。想到了用書籍的id作爲Cookie的值,我們還要定義一些規則!

     

  •  

    我們可能有非常多的書籍,不可能把用戶瀏覽過的書籍都顯示出來。所以我們定義只能顯示3本瀏覽過的書籍

     

  •  

    書籍的id都是數字,如果不做任何修改,存到Cookie裏邊可能就是231,345,123此類的數字,這樣取出某一個id的時候就十分費勁並且後面還要判斷該書是否存在Cookie裏邊了,所以我們要把存儲到Cookie的書籍id分割起來。所以我們定義”_“作爲分隔符

     

  •  

    按上面的應用,我們的邏輯應該是:先遍歷下Cookie,看下有沒有我們想要的Cookie。如果找到想要的Cookie,那就取出Cookie的值

     

       String bookHistory = null;

       Cookie[] cookies = request.getCookies();

       for (int i = 0; cookies != null && i < cookies.length; i++) {

           if (cookies[i].getName().equals("bookHistory")) {

               bookHistory = cookies[i].getValue();

           }

       }

  1.  

    1. Cookie的值爲null【直接把傳入進來的id當做是Cookie的值】

    2. Cookie的值長度有3個了【把排在最後的id去掉,把傳進來的id排在最前邊】

    3. Cookie的值已經包含有傳遞進來的id了【把已經包含的id先去掉,再把id排在最前面】

    4. Cookie的值就只有1個或2個,直接把id排在最前邊

  2. 取出了Cookie的值也分幾種情況

           if (bookHistory == null) {
    
               return id;
    
           }
    
    
    
           //如果Cookie的值不是null的,那麼就分解Cookie的得到之前的id。
    
           String[] strings = bookHistory.split("\\_");
    
    
    
           //爲了增刪容易並且還要判斷id是否存在於該字符串內-----我們使用LinkedList集合裝載分解出來的id
    
           List list = Arrays.asList(strings);
    
           LinkedList<String> linkedList = new LinkedList<>();
    
           linkedList.addAll(list);
    
    
    
           if (linkedList.contains(id)) {
    
               linkedList.remove(id);
    
               linkedList.addFirst(id);
    
           }else {
    
               if (linkedList.size() >= 3) {
    
                   linkedList.removeLast();
    
                   linkedList.addFirst(id);
    
               } else {
    
                   linkedList.addFirst(id);
    
               }
    
           }

     

  • 這麼折騰完了,我們的Cookie值就在LinkedList集合裏邊了。接下來,我們要做的就是把集合中的值取出來,拼接成一個字符串

  StringBuffer stringBuffer = new StringBuffer();



       //遍歷LinkedList集合,添加個下劃線“_”

       for (String s : linkedList) {

           stringBuffer.append(s + "_");

       }



       //最後一個元素後面就不需要下劃線了

       return stringBuffer.deleteCharAt(stringBuffer.length() - 1).toString();
  • 好的,我們現在已經完成了Cookie值了。接下來設置Cookie的生命週期,回送給瀏覽器即可

String bookHistory = makeHistory( request, id);
Cookie cookie = new Cookie("bookHistory",bookHistory);
cookie.setMaxAge(30000);
response.addCookie(cookie);
  1. 既然我們已經把Cookie回送給瀏覽器了。那麼接下來我們就在首頁上獲取Cookie的值,顯示用戶瀏覽過什麼商品就行了
	printWriter. write("您曾經瀏覽過的商品:");
	printWriter.write("<br/>");

	//顯示用戶瀏覽過的商品

	Cookie[] cookies = request.getCookies();
	for(int i = 0; cookies != null && i < cookies.length; i++){
		if(cookies[i].getName().equals("bookHistory")){
			//獲取到的bookHistory是2_3_1之類的
			String bookHistory = cookies[i].getValue();
			//拆解成每一個id值
			String[] ids = bookHistory.split("\\_");
			//得到每一個id值
			for(String id : ids){
			//通過id找到每一本書
				Book book = linkedHashMap.get(id);
				printWriter.write(book.getName());
				printWriter.write("<br/>");
			}
			break;
		}
	}
  1.  

  • 好的,我們來試驗一下吧!!,第一次訪問首頁,並沒有瀏覽過的商品

  • 當我點擊javaweb書籍再訪問首頁的時候

  • 再點擊ajax然後訪問首頁

  • 再點擊javaweb然後訪問首頁

  • 點擊oracle然後訪問首頁

  • 好的,經過測試,該程序應該沒有什麼問題了!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章