- Java EE 程序設計
- 郝玉龍編著
- 3508字
- 2019-07-01 10:16:06
3.7 會話管理
3.1節講過HTTP協議是一種無狀態的協議,客戶端每次打開一個Web頁面,它就會與服務器建立一個新的連接,發送一個新的請求到服務器,服務器處理客戶端的請求,返回響應到客戶端,并關閉與客戶端建立的連接。當客戶端發起新的請求,那么它重新與服務器建立連接,因此服務器并不記錄關于客戶的任何信息。但是對于許多Web應用而言,服務器往往需要記錄特定客戶端與服務器之間的一系列請求響應之間的特定信息。例如,一個在線網上商店需要記錄在線客戶的個人信息、添加到購物車中的商品信息等。如果顧客每打開一個新的頁面都需要重新輸入登錄信息確認身份,那么這個網上商店可能只能關門大吉了。從特定客戶端到服務器的一系列請求稱為會話。在Web服務器看來,一個會話是由在一次瀏覽過程中所發出的全部HTTP請求組成的。換句話說,一次會話是從客戶打開瀏覽器開始到關閉瀏覽器結束。記錄會話信息的技術稱為會話跟蹤,對于開發人員而言會話跟蹤不是容易解決的問題。會話跟蹤的第一個障礙是如何唯一標識每一個客戶會話。這只能通過為每一個客戶分配一個某種標識,并將這些標識保存在客戶端上,以后客戶端發給服務器的每一個HTTP請求都提供這些標識來實現。那么為什么不能用客戶端的IP地址作為標識呢?這是因為在一臺客戶端上可能同時發出多個不同的客戶請求,而且,如果多個不同客戶請求還可能是通過同一個代理服務器發出的,因此IP地址不能作為唯一標識。
常見會話跟蹤技術有Cookie和URL重寫等。
3.7.1 Cookie
Cookie是一小塊可以嵌入到HTTP請求和響應中的數據。典型情況下,Web服務器將Cookie值嵌入到響應的Header,而瀏覽器則在其以后的請求中都將攜帶同樣的Cookie。Cookie的信息中可以有一部分用來存儲會話ID,這個ID被服務器用來將某些HTTP請求綁定在會話中。Cookie由瀏覽器保存在客戶端,通常保存為一個文本文件。Cookie還含有一些其他屬性,諸如可選的注釋、版本號及最長生命期。
為加深對Cookie的理解,下面創建一個Servlet來顯示Cookie的相關信息。代碼如程序3-22所示。
程序3-22:CookieServlet.java
package com.servlet; … @WebServlet(name=" CookieServlet ", urlPatterns={"/cookie"}) public class CookieServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Cookie cookie = null; //獲取請求相關的cookie Cookie[] cookies = request.getCookies( ); boolean newCookie = false; //判斷Cookie ServletStudy是否存在 if (cookies ! = null){ for (int i = 0; i < cookies.length; i++){ if (cookies[i].getName( ).equals("Chapter3")){ cookie= cookies[i]; } }//end for }//end if if (cookie == null){ newCookie=true; int maxAge=10000; //生成cookie對象 cookie= new Cookie("Chapter3", "create by hyl"); cookie.setPath(request.getContextPath( )); cookie.setMaxAge(maxAge); response.addCookie(cookie); }//end if // 顯示信息 response.setContentType("text/html"); java.io.PrintWriter out = response.getWriter( ); out.println("<html>"); out.println("<head>"); out.println("<title>Cookie Info</title>"); out.println("</head>"); out.println("<body>"); out.println( "<h2> Information about the cookie named \"Chapter3\"</h2>"); out.println("Cookie value: "+cookie.getValue( )+"<br>"); if (newCookie){ out.println("Cookie Max-Age: "+cookie.getMaxAge( )+"<br>"); out.println("Cookie Path: "+cookie.getPath( )+"<br>"); } out.println("</body>"); out.println("</html>"); } }
程序說明:HttpServletRequest對象有一個getCookies方法,它可以返回當前請求中的Cookie對象的一個數組。程序首先調用getCookies方法獲得request對象中的所有Cookie,然后尋找是否有名為Chapter3的Cookie。如果有,則調用Cookie對象的getValue、getName等方法顯示其信息;如果沒有,則創建一個新的Cookie對象,并調用response.addCookie方法將其加入到response對象并返回到客戶端。以后客戶端對服務器的任何訪問都會在其頭部攜帶此Cookie。可以通過刷新頁面來查看Cookie的信息,可以看到顯示的Cookie信息是不變的。
重新發布Web應用并啟動瀏覽器,在地址欄中輸入http://localhost:8080/Chapter3/cookie,得到如圖3-27所示的運行結果頁面。

圖3-27 顯示Cookie信息
由于同一客戶端對服務器的請求都會攜帶Cookie,因此可以通過在Cookie中添加與會話相關的信息以達到會話跟蹤的目的。下面通過創建一個Servlet來演示如何通過Cookie實現會話跟蹤。代碼如程序3-23所示。
程序3-23:CookieTrackServlet.java
… @WebServlet(name=" CookieTrackServlet ", urlPatterns={"/ cookietrack"}) public class CookieTrackServlet extends HttpServlet { protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Cookie cookie=null; //獲取請求相關的Cookie Cookie[] cookies=request.getCookies(); //判斷Cookie VisitTimes是否存在,如果存在,其值加1 if(cookies! =null){ boolean flag=false; for(int i=0; (i<cookies.length)&&(! flag); i++){ if(cookies[i].getName().equals("VisitTimes")){ String v=cookies[i].getValue(); int value=Integer.parseInt(v)+1; cookies[i].setValue(Integer.toString(value)); //將值更新后的cookie重新寫回響應 response.addCookie(cookies[i]); flag=true; cookie=cookies[i]; }//end if }//end for }//end if //不存在,創建cookie if(cookie==null){ int maxAge=-1; //創建cookie對象 cookie=new Cookie("VisitTimes", "1"); cookie.setPath(request.getContextPath()); cookie.setMaxAge(maxAge); response.addCookie(cookie); }//end if //顯示信息 response.setContentType("text/html; charset=utf-8"); PrintWriter out= response.getWriter(); out.println("<html>"); out.println("<head>"); out.println("<title>Cookie跟蹤會話</title>"); out.println("</head>"); out.println("<body>"); out.println("<h2>您好!</h2>"); out.println("歡迎您第"+cookie.getValue()+"次訪問本頁面<br>"); out.println("</body>"); out.println("</html>"); } … }
程序說明:程序使用Cookie來實現會話的跟蹤,在本示例中跟蹤的是會話中頁面的訪問次數。程序通過將頁面訪問的次數寫入一個名為VisitTimes的Cookie中。由于對頁面的請求每次都包含了這個Cookie,因此通過每次將Cookie的值取出來顯示頁面的訪問次數,同時又將更新過的值寫回到Cookie來達到會話跟蹤的目的。
重新發布Web應用并啟動瀏覽器,在地址欄中輸入http://localhost:8080/Chapter3/cookietrack,得到如圖3-28所示的運行結果頁面,不停地刷新頁面,頁面中顯示的值也不停地刷新,可以看到服務器可以準確地跟蹤客戶端的訪問次數。

圖3-28 用Cookie實現會話跟蹤
3.7.2 URL重寫
關于是否應當使用Cookie有很多的爭論,因為一些人認為Cookie可能會造成對隱私權的侵犯。有鑒于此,大部分瀏覽器允許用戶關閉Cookie功能,這使得跟蹤會話變得更加困難。如果不能依賴Cookie的支持又該怎么辦呢?那將導致不得不使用另外一種會話跟蹤方法——URL重寫。
URL重寫通過在URL地址后面增加一個包含會話信息的字符串來記錄會話信息。URL地址與會話信息的字符串之間用“?”隔開。如果請求還包含多個參數,則參數與會話信息以及參數間用“&”隔開。
下面通過編寫一個Servlet URLRewrite1來演示如何利用URL重寫來向服務器端傳遞會話信息。這里假設客戶端向服務器端傳遞的會話信息是用戶的身份信息:姓名和年齡。代碼如程序3-24所示。
程序3-24:URLRewrite1.java
package com.servlet; … @WebServlet(name=" URLRewrite1", urlPatterns={"/ url1 "}) public class URLRewrite1 extends HttpServlet { protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html; charset=UTF-8"); java.io.PrintWriter out = response.getWriter( ); String contextPath = request.getContextPath( ); String encodedUrl = response.encodeURL(contextPath + "/url2? name=張三&age=27"); out.println("<html>"); out.println("<head>"); out.println("<title>URL Rewriter</title>"); out.println("</head>"); out.println("<body>"); out.println( "<h1>URL重寫演示:發送參數</h2>"); out.println("轉到URL2<a href=\"" + encodedUrl + "\">here</a>."); out.println("</body>"); out.println("</html>"); out.close(); } … }
程序說明:程序首先調用response的encodeURL生成URL字符串。其中request的getContextPath用來獲取請求上下文路徑。URL字符串包含的會話信息為兩個參數:name和age,其值分別為“張三”和27。
下面通過在Web應用Chapter3中創建一個名為URLRewrite2的Servlet來演示服務器端如何獲取通過URL重寫方式傳遞來的會話信息。代碼如程序3-25所示。
程序3-25:URLRewrite2.java
package com.servlet; … @WebServlet(name=" URLRewrite2", urlPatterns={"/ url2 "}) public class URLRewrite2 extends HttpServlet { protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html; charset=UTF-8"); request.setCharacterEncoding("UTF-8"); java.io.PrintWriter out = response.getWriter( ); String contextPath = request.getContextPath( ); out.println("<html>"); out.println("<head>"); out.println("<title>URL Rewriter</title>"); out.println("</head>"); out.println("<body>"); out.println( "<h1>URL重寫演示:接收參數</h2>"); out.println("下面是接收的參數:<br>"); out.println("name="+request.getParameter("name")); out.println("age="+request.getParameter("age")); out.println("</body>"); out.println("</html>"); out.close(); } … }
程序說明:對于利用URL重寫技術傳遞來會話信息,可以調用request.getParameter來獲取,就像獲取表單提取的參數信息一樣。實際上,通過表單向服務器端提交數據就是通過URL重寫的方式。注意,如果傳遞的是漢字編碼的信息,在提取參數前,別忘了通過“request.setCharacterEncoding("UTF-8");”來設置請求編碼格式,否則得到的將會是亂碼。
重新發布Web應用并啟動瀏覽器,在地址欄中輸入http://localhost:8080/Chapter3/url1,得到如圖3-29所示的運行結果頁面。單擊頁面中的鏈接here,則瀏覽器被導向地址http://localhost:8080/Chapter3/url2,得到如圖3-30所示的運行結果頁面,可以看到,客戶端通過URL重寫的會話信息已經傳遞到Servlet組件URLRewrite2并被正確解析。

圖3-29 URL重寫:發送參數

圖3-30 接收URL重寫的參數信息
3.7.3 HttpSession
為消除代碼中手工管理會話信息的需要(無論使用什么會話跟蹤方式),Servlet規范定義了HttpSession接口以方便Servlet容器進行會話跟蹤。這個高級接口實際上是建立在Cookie和URL重寫這兩種會話跟蹤技術之上的,只不過由Web容器自動實現了關于會話跟蹤的底層機制,不再需要開發人員了解具體細節。HttpSession接口允許Servlet查看和管理關于會話的信息,確保信息持續跨越多個用戶連接等。
使用HttpSession接口進行程序開發的基本步驟如下:
(1)獲取HttpSession對象。
(2)對HttpSession對象進行讀或寫。
(3)手工終止HttpSession,或者什么也不做,讓它自動終止。每個HttpSession對象都有一定的生命周期,超過這個周期,容器自動將HttpSession對象中止。
程序開發中經常使用的HttpSession接口方法有以下幾個:
(1)isNew()。如果客戶端還不知道會話,則返回true。如果客戶端已經禁用了Cookie,則會話在每個請求上都是新的。
(2)getId()。返回包含分配給這個會話的唯一標識的字符串。在使用URL改寫已標識會話時比較有用。
(3)setAttribute()。使用指定的名稱將對象綁定到會話。
(4)getAttribute()。返回綁定到此會話的指定名稱的對象。
(5)setMaxInactiveInterval()。指定在Servlet使該會話無效之前客戶端請求間的時間。負的時間表示會話永遠不會超時。
(6)invalidate()。終止當前會話,并解開與它綁定的對象。
下面通過一個示例來演示如何用HttpSession來存儲當前會話中用戶訪問站點的次數。
在項目中創建Servlet HitCounter,代碼如程序3-26所示。
程序3-26:HitCounter
package com.Servlet; … @WebServlet(name=" HitCounter ", urlPatterns={"/hitcounter "}) public class HitCounter extends HttpServlet { static final String COUNTER_KEY = "Counter"; protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //獲取會話對象 HttpSession session = request.getSession(true); response.setContentType("text/html; charset=gb2312"); java.io.PrintWriter out = response.getWriter(); //從會話中獲取屬性 int count = 1; Integer i = (Integer) session.getAttribute(COUNTER_KEY); if (i ! = null) { count = i.intValue() + 1; } //將屬性信息存入會話 session.setAttribute(COUNTER_KEY, new Integer(count)); Date lastAccessed = new Date(session.getLastAccessedTime( )); Date sessionCreated=new Date(session.getCreationTime()); DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM); //輸出會話信息 out.println("<html>"); out.println("<head>"); out.println("<title>會話計數器</title>"); out.println("</head>"); out.println("<body>"); out.println("你的會話ID: <b>" +session.getId()+ "<br>"); out.println("會話創建時間:"+formatter.format(sessionCreated) + "<br>"); out.println("會話上次訪問時間:"+formatter.format(lastAccessed) + "<br>"); out.println("</b> 會話期間你對頁面發起 <b>" + count + "</b> 次請求"); out.println("<form method=GET action=\"" + request.getRequestURI() + "\">"); out.println("<input type=submit " + "value=\"再次單擊...\">"); out.println("</form>"); out.println("</body>"); out.println("</html>"); out.flush(); out.close(); } … }
程序說明:Servlet中使用HttpServletRequest對象的getSession方法來取得當前的用戶會話。GetSession的參數決定了如果會話不存在,是否創建一個新會話(還有一個版本的getSession沒有任何參數,它將默認創建一個新會話)。一旦獲得了會話對象,就可以像操作哈希表一樣使用一個唯一的鍵,在會話對象中加入或者獲取任何對象。通過調用setAttribute將用戶訪問次數信息存入會話,通過調用getAttribute來獲取會話中存儲的信息。
注意:由于會話數據是由Web容器維護存儲的,在為這些鍵賦值時一定要注意維護它的唯一性。一個比較好的方法是為每個會話屬性的名稱定義一個static final類型的String變量。
重新發布Web應用并啟動瀏覽器,在地址欄中輸入http://localhost:8080/Chapter3/hitcounter,得到如圖3-31所示的運行結果頁面。用戶第一次打開HitCounter Servlet的時候,如果會話還不存在,就會創建一個新的會話(一定要注意,其他Servlet可能已經建立了這個用戶的會話對象)。通過一個唯一鍵從會話對象中取得一個整數,如果這個整數不存在,就使用初始值1,否則每次給這個整數加1。最后,新的值被寫回會話對象。一個簡單的HTML頁被返回給瀏覽器顯示,它顯示了會話ID及用戶通過單擊“再次單擊”按鈕獲取這一頁的訪問次數。

圖3-31 利用會話存儲頁面訪問次數
- Unity 2020 By Example
- ExtGWT Rich Internet Application Cookbook
- 數據結構和算法基礎(Java語言實現)
- HTML5 移動Web開發從入門到精通(微課精編版)
- OpenNI Cookbook
- Java程序設計與計算思維
- Blender 3D Incredible Machines
- Oracle Database 12c Security Cookbook
- JavaScript by Example
- PLC編程與調試技術(松下系列)
- Unity Game Development Scripting
- JavaCAPS基礎、應用與案例
- 從零開始學Linux編程
- 細說Python編程:從入門到科學計算
- Azure Serverless Computing Cookbook