Web基礎知識(6)- Java Servlet (六)
Servlet Cookie、Servlet Session
HTTP(超文字傳輸協議)是一個基於請求與響應模式的無狀態協議。
無狀態主要指 2 點:
(1) 協議對於事務處理沒有記憶能力,伺服器不能自動維護使用者的上下文資訊,無法儲存使用者狀態;
(2) 每次請求都是獨立的,不會受到前面請求的影響,也不會影響後面的請求。
當瀏覽器傳送 HTTP 請求到伺服器時,伺服器會響應客戶端的請求,但當同一個瀏覽器再次傳送請求到該伺服器時,伺服器並不知道它就是剛才那個瀏覽器,即 HTTP 協議的請求無法儲存使用者狀態。
通常情況下,使用者通過瀏覽器訪問 Web 應用時,伺服器都需要儲存和跟蹤使用者的狀態。例如,使用者在某購物網站結算商品時,Web 伺服器必須根據請求使用者的身份,找到該使用者所購買的商品。由於 HTTP 協議是無協議的,無法儲存和跟蹤使用者狀態,所以需要其他的方案來解決問此題,它就是會話技術。
從開啟瀏覽器訪問某個網站,到關閉瀏覽器的過程,稱為一次會話。會話技術是指在會話中,幫助伺服器記錄使用者狀態和資料的技術。
常用的會話技術分為兩種:
(1) Cookie:客戶端會話技術
(2) Session:服務端會話技術
1. Servlet Cookie (客戶端會話技術)
Cookie 屬於客戶端會話技術,它是伺服器傳送給瀏覽器的小段文字資訊,儲存在客戶端瀏覽器的記憶體中或硬碟上。當瀏覽器儲存了 Cookie 後,每次訪問伺服器,都會在 HTTP 請求頭中將這個 Cookie 回傳給伺服器。
1) Cookie 的分類
(1) 會話級別 Cookie(預設):Cookie 儲存到瀏覽器的記憶體中,瀏覽器關閉則 Cookie 失效。
(2) 持久的 Cookie:Cookie 以文字檔案的形式儲存到硬碟上。
2) Cookie 的工作流程
(1) 客戶端瀏覽器訪問伺服器時,伺服器通過在 HTTP 響應中增加 Set-Cookie 欄位,將資料資訊傳送給瀏覽器。
(2) 瀏覽器將 Cookie 儲存在記憶體中或硬碟上。
(3) 再次請求該伺服器時,瀏覽器通過在 HTTP 請求訊息中增加 Cookie 請求頭欄位,將 Cookie 回傳給 Web 伺服器。伺服器根據 Cookie 資訊跟蹤客戶端的狀態。
3) Cookie API
javax.servlet.http 包中定義了一個 Cookie 類,利用它的帶參構造方法,可以建立 Cookie 物件。例如:
Cookie c = new Cookie("url", "www.test.com");
其中引數 name 為 Cookie 的名稱,引數 value 為 Cookie 的值,name 與 value 的取值不能包含 [ ] ( ) = , " / ? @ : ; 等字元
HttpServletResponse 介面和 HttpServletRequest 介面也都定義了與 Cookie 相關的方法,如下表所示。
方法 | 描述 |
void addCookie(Cookie cookie) | 用於在響應頭中增加一個相應的 Set-Cookie 頭欄位,所屬介面 javax.servlet.http.HttpServletResponse |
Cookie[] getCookies() | 用於獲取客戶端提交的 Cookie,所屬介面 javax.servlet.http.HttpServletRequest |
javax.servlet.http.Cookie 類中提供了一系列獲取或者設定 Cookie 的方法,如下表。
方法 | 描述 |
int getMaxAge() | 用於獲取指定 Cookie 的最大有效時間,以秒為單位。預設情況下取值為 -1,表示該 Cookie 保留到瀏覽器關閉為止。 |
String getName() | 用於獲取 Cookie 的名稱。 |
String getPath() | 用於獲取 Cookie 的有效路徑。 |
boolean getSecure() | 如果瀏覽器只通過安全協議傳送 Cookie,則返回 true;如果瀏覽器可以使用任何協議傳送 Cookie,則返回 false。 |
String getValue() | 用於獲取 Cookie 的值。 |
int getVersion() | 用於獲取 Cookie 遵守的協議版本。 |
void setMaxAge(int expiry) | 用於設定 Cookie 的最大有效時間,以秒為單位。取值為正值時,表示 Cookie 在經過指定時間後過期。取值為負值時,表示 Cookie 不會被持久儲存,在 Web 瀏覽器退出時刪除。取值為 0 時,表示刪除該 Cookie。 |
void setPath(String uri) | 用於指定 Cookie 的路徑。 |
void setSecure(boolean flag) | 用於設定瀏覽器是否只能使用安全協議(如 HTTPS 或 SSL)傳送 Cookie。 |
void setValue(String newValue) | 用於設定 Cookie 的值。 |
4) Cookie 的使用細節
使用 Cookie 開發時需要注意以下細節:
(1) 一個 Cookie 只能標識一種資訊,它至少包含一個名稱(NAME)和一個值(VALUE)。
(2) 如果建立了一個 Cookie,併發送到瀏覽器,預設情況下它是一個會話級別的 Cookie。使用者退出瀏覽器就被刪除。如果希望將 Cookie 存到磁碟上,則需要呼叫 setMaxAge(int maxAge) 方法設定最大有效時間,以秒為單位。
(3) 使用 setMaxAge(0) 手動刪除 Cookie時,需要使用 setPath 方法指定 Cookie 的路徑,且該路徑必須與建立 Cookie 時的路徑保持一致。
5) Cookie 的缺點
Cookie 雖然可以解決伺服器跟蹤使用者狀態的問題,但是它具有以下缺點:
(1) 在 HTTP 請求中,Cookie 是明文傳遞的,容易洩露使用者資訊,安全性不高。
(2) 瀏覽器可以禁用 Cookie,一旦被禁用,Cookie 將無法正常工作。
(3) Cookie 物件中只能設定文字(字串)資訊。
(4) 客戶端瀏覽器儲存 Cookie 的數量和長度是有限制的。
示例:
1 // Login Page 2 package com.example; 3 4 import java.io.PrintWriter; 5 import java.io.IOException; 6 import java.net.URLDecoder; 7 import java.net.URLEncoder; 8 import java.text.SimpleDateFormat; 9 import java.util.Date; 10 11 import javax.servlet.http.Cookie; 12 import javax.servlet.ServletException; 13 import javax.servlet.http.HttpServlet; 14 import javax.servlet.http.HttpServletRequest; 15 import javax.servlet.http.HttpServletResponse; 16 import javax.servlet.annotation.WebServlet; 17 18 @WebServlet("/login") 19 public class LoginServlet extends HttpServlet { 20 protected void doGet(HttpServletRequest request, HttpServletResponse response) 21 throws ServletException, IOException { 22 23 response.setContentType("text/html;charset=UTF-8"); 24 Cookie[] cookies = request.getCookies(); 25 Cookie cookie = getCookieByName(cookies, "lastTime"); 26 27 PrintWriter writer = response.getWriter(); 28 writer.write("<h1>Login Servlet Page</h1>"); 29 30 if (cookie == null) { 31 writer.write("<p>Your status is Offline</p>"); 32 } else { 33 String value = cookie.getValue(); 34 writer.write("<p>Your status is Online, last visit time is " + URLDecoder.decode(value) 35 + ". Click <a href=\"/logout\" > Here </a> to logout</p>"); 36 } 37 38 Date date = new Date(); 39 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 40 String sDate = sdf.format(date); 41 42 Cookie c = new Cookie("lastTime", URLEncoder.encode(sDate)); 43 c.setMaxAge(60 * 60 * 24); // 秒, 設定有效時間為一天 44 c.setPath("/login"); 45 response.addCookie(c); 46 47 } 48 49 protected void doPost(HttpServletRequest request, HttpServletResponse response) 50 throws ServletException, IOException { 51 } 52 53 public static Cookie getCookieByName(Cookie[] cookies, String name) { 54 if (cookies == null) { 55 return null; 56 } else { 57 for (Cookie cookie : cookies) { 58 if (cookie.getName().equals(name)) { 59 return cookie; 60 } 61 } 62 return null; 63 } 64 } 65 } 66 67 // Logout Page 68 package com.example; 69 70 import java.io.IOException; 71 72 import javax.servlet.http.Cookie; 73 import javax.servlet.ServletException; 74 import javax.servlet.http.HttpServlet; 75 import javax.servlet.http.HttpServletRequest; 76 import javax.servlet.http.HttpServletResponse; 77 import javax.servlet.annotation.WebServlet; 78 79 80 @WebServlet("/logout") 81 public class LogoutServlet extends HttpServlet { 82 83 protected void doGet(HttpServletRequest request, HttpServletResponse response) 84 throws ServletException, IOException { 85 Cookie cookie = new Cookie("lastTime", ""); 86 cookie.setMaxAge(0); // 設定有效時間為0,刪除cookie 87 cookie.setPath("/login"); 88 response.addCookie(cookie); 89 response.sendRedirect("/login"); 90 } 91 92 protected void doPost(HttpServletRequest request, HttpServletResponse response) 93 throws ServletException, IOException { 94 } 95 96 }
2. Servlet Session (服務端會話技術)
Session 是伺服器端會話技術。當瀏覽器訪問 Web 伺服器的資源時,伺服器可以為每個使用者瀏覽器建立一個 Session 物件,每個瀏覽器獨佔一個 Session 物件。
由於每個瀏覽器獨佔一個 Session,所以使用者在訪問伺服器的資源時,可以把資料儲存在各自的 Session 中。當用戶再次訪問該伺服器中的其它資源時,其它資源可以從 Session 中取出資料,為使用者服務。
1) Session 的工作原理
Session 雖然屬於服務端會話技術,但是它的實現離不開客戶端瀏覽器和 Cookie 的支援,其工作原理如下。
(1) 當客戶端第一次請求會話物件時,伺服器會建立一個 Session 物件,併為該 Session 物件分配一個唯一的 SessionID(用來標識這個 Session 物件);
(2) 伺服器將 SessionID 以 Cookie(Cookie 名稱為:“JSESSIONID”,值為 SessionID 的值)的形式傳送給客戶端瀏覽器;
(3) 客戶端瀏覽器再次傳送 HTTP 請求時,會將攜帶 SessionID 的 Cookie 隨請求一起傳送給伺服器;
(4) 伺服器從請求中讀取 SessionID,然後根據 SessionID 找到對應的 Session 物件。
注意:
(1) 流程中的 Cookie 是容器自動生成的,它的 maxAge 屬性取值為 -1,表示僅當前瀏覽器有效。
(2) 瀏覽器關閉時,對應的 Session 並沒有失效,但此時與此 Session 對應的 Cookie 已失效,導致瀏覽器無法再通過 Cookie 獲取伺服器端的 Session 物件。
(3)同一瀏覽器的不同視窗共享同一 Session 物件,但不同瀏覽器視窗之間不能共享 Session 物件。
2) Session 與 Cookie 對比
Session 和 Cookie 都屬於會話技術,都能幫助伺服器儲存和跟蹤使用者狀態,但兩者也存在差異,如下表。
不同點 | Cookie | Session |
儲存位置不同 | Cookie 將資料存放在客戶端瀏覽器記憶體中或硬碟上。 | Session 將資料儲存在伺服器端。 |
大小和數量限制不同 | 瀏覽器對 Cookie 的大小和數量有限制。 | Session 的大小和數量一般不受限制。 |
存放資料型別不同 | Cookie 中儲存的是字串。 | Session 中儲存的是物件。 |
安全性不同 | Cookie 明文傳遞,安全性低,他人可以分析存放在本地的 Cookie 並進行 Cookie 欺騙。 | Session 存在伺服器端,安全性較高。 |
對伺服器造成的壓力不同 | Cookie 儲存在客戶端,不佔用伺服器資源。 | Session 儲存在服務端,每一個使用者獨佔一個 Session。若併發訪問的使用者十分多,就會佔用大量服務端資源。 |
跨域支援上不同 | Cookie 支援跨域名訪問。 | Session 不支援跨域名訪問。 |
3) Session API
Session 物件由伺服器建立,通過 HttpServletRequest.getSession() 方法可以獲得 HttpSession 物件,例如:
HttpSession session=request.getSession();
HttpSession 介面定義了一系列對 Session 物件操作的方法,如下表。
方法 | 描述 |
long getCreationTime() | 返回建立 Session 的時間。 |
String getId() | 返回獲取 Seesion 的唯一的 ID。 |
long getLastAccessedTime() | 返回客戶端上一次傳送與此 Session 關聯的請求的時間。 |
int getMaxInactiveInterval() | 返回在無任何操作的情況下,Session 失效的時間,以秒為單位。 |
ServletContext getServletContext() | 返回 Session 所屬的 ServletContext 物件。 |
void invalidate() | 使 Session 失效。 |
void setMaxInactiveInterval(int interval) | 指定在無任何操作的情況下,Session 失效的時間,以秒為單位。負數表示 Session 永遠不會失效。 |
4) 設定 Session 過期時間
Session 物件在伺服器中駐留一段時間後沒有被使用,就會被銷燬,這個時間就是 Session 的過期時間。
Session 的預設過期時間為 30 分鐘,我們可以通過如下兩種方式設定過期時間。
(1) 使用 <session-config> 元素
在 web.xml 中,使用 <session-config> 及其子元素 <session-timeout> 可以配置 Session 的預設過期時間,程式碼如下。
<web-app>
<!--設定 session 的過期時間-->
<session-config>
<session-timeout>10</session-timeout>
</session-config>
</web-app>
其中:
<session-timeout> 元素用來指定預設 Session 過期時間,以分鐘為單位,該元素值必須為整數。
<session-timeout> 元素的值為零或負數,表示 Session 永遠不會過期。
(2) 呼叫 setMaxInactiveInterval() 方法
通過呼叫 session.setMaxInactiveInterval(int interval) 設定過期時間,以秒為單位,零和負數表示會話永遠不會過期,程式碼如下。
request.getSession().setMaxInactiveInterval(100);
5) Session 的生命週期
(1) Session 物件建立
Session 物件在容器第一次呼叫 request.getSession() 方法時建立。
值得注意的是,當客戶端訪問的 Web 資源是 HTML,CSS,圖片等靜態資源時,伺服器不會建立 Session 物件。
(2) Session 物件銷燬
Session 物件在如下 3 種情況下會被銷燬:
a) Session 過期;
b) 呼叫 session.invalidate() 方法,手動銷燬 Session;
c) 伺服器關閉或者應用被解除安裝。
6) Session 域物件
Session 物件也是一種域物件,它可以對屬性進行操作,進而實現會話中請求之間的資料通訊和資料共享。
在 javax.servlet.http.HttpSession 介面中定義了一系列操作屬性的方法,如下表。
方法 | 描述 |
void setAttribute(String name, Object o) | 把一個 Java 物件與一個屬性名繫結,並將它作為一個屬性存放到 Session 物件中。引數 name 為屬性名,引數 object 為屬性值。 |
Object getAttribute(String name) | 根據指定的屬性名 name,返回 Session 物件中對應的屬性值。 |
void removeAttribute(String name) | 從 Session 物件中移除屬性名為 name 的屬性。 |
Enumeration getAttributeNames() | 用於返回 Session 物件中的所有屬性名的列舉集合。 |
Session 、request 以及 ServletContext 合稱為 Servlet 的三大域物件,它們都能儲存和傳遞資料,但是三者也存在許多差異,如下表。
不同 | request | Session | ServletContext |
建立 | 客戶端向容器傳送請求時建立。 | 容器第一次呼叫 getSession() 方法時建立。 | Servlet 容器啟動時建立。 |
銷燬 | 容器對這次請求做出響應後銷燬。 |
關閉伺服器或應用被解除安裝。 Session 過期,預設為 30 分鐘。 手動呼叫 session.invalidate() 方法進行銷燬。 |
容器關閉或者 Web 應用被移除時銷燬。 |
有效範圍 | 只對當前請求涉及的 Servlet 有效。 | Session 對本次會話期間的所有 Servlet 都有效。 | 對整個 Web 應用內的所有 Servlet 有效。 |
數量 | Servlet 例項可以有多個 request 物件。 | Web 應用中可以有多個 Session,多個 Servet 例項可以共享同一 Session 物件。 | 在整個 Web 應用中只有一個 Context 物件。 |
資料共享 | 通過和請求轉發的配合使用可以實現一次請求中 Web 元件之間共享的資料。 | 通過 Session 域物件可以實現一次會話中的多個請求之間共享資料。 | 通過 Context 物件,可以實現多次會話之間的資料共享。 |
示例:
1 // Login Session Page 2 package com.example; 3 4 import javax.servlet.ServletException; 5 import javax.servlet.annotation.WebServlet; 6 import javax.servlet.http.HttpServlet; 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 import java.io.IOException; 10 import java.io.PrintWriter; 11 import java.text.SimpleDateFormat; 12 import java.util.Date; 13 14 @WebServlet("/loginSession") 15 public class SessionLogin extends HttpServlet { 16 17 protected void doGet(HttpServletRequest request, HttpServletResponse response) 18 throws ServletException, IOException { 19 20 response.setContentType("text/html;charset=UTF-8"); 21 PrintWriter writer = response.getWriter(); 22 writer.write("<h1>Login Session Page</h1>"); 23 24 Date date = new Date(); 25 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 26 Date CreationTime = new Date(request.getSession().getCreationTime()); 27 28 // 會話上次關聯的時間 29 Date LastAccessedTime = new Date(request.getSession().getLastAccessedTime()); 30 String sDate = sdf.format(date); 31 32 // 將當前時間賦值到session域物件中 33 request.getSession().setAttribute("lastTime", sDate); 34 // 設定會話的失效時間,以秒為單位 35 request.getSession().setMaxInactiveInterval(60); 36 37 // 對session中各個資訊輸出到頁面 38 writer.write("<p>Current Time: " + sDate + "<br/>" 39 + "Current SessionID: " + request.getSession().getId() + "<br/>" 40 + "Creation Time: " + sdf.format(CreationTime) + "<br/>" 41 + "Last Accessed Time: " + sdf.format(LastAccessedTime) + "<br/>" 42 + "Max Inactive Interval(Seconds): " + request.getSession().getMaxInactiveInterval() + "</p>" 43 ); 44 45 String url = response.encodeURL("/loginSessionView"); 46 writer.write("<a href=" + url + ">Try again</a>"); 47 } 48 49 protected void doPost(HttpServletRequest request, HttpServletResponse response) 50 throws ServletException, IOException { 51 } 52 } 53 54 // Session View Page 55 package com.example; 56 57 import javax.servlet.ServletException; 58 import javax.servlet.annotation.WebServlet; 59 import javax.servlet.http.HttpServlet; 60 import javax.servlet.http.HttpServletRequest; 61 import javax.servlet.http.HttpServletResponse; 62 import java.io.IOException; 63 import java.io.PrintWriter; 64 import java.text.SimpleDateFormat; 65 import java.util.Date; 66 67 @WebServlet("/loginSessionView") 68 public class SessionView extends HttpServlet { 69 70 protected void doGet(HttpServletRequest request, HttpServletResponse response) 71 throws ServletException, IOException { 72 73 response.setContentType("text/html;charset=UTF-8"); 74 PrintWriter writer = response.getWriter(); 75 76 writer.write("<h1>Login Session View Page</h1>"); 77 78 // 從 session 中獲取上次訪問的時間 79 String value = (String) request.getSession().getAttribute("lastTime"); 80 81 if (value == null) { 82 writer.write("<p>Welcome to Login Session View Page</p>"); 83 } else { 84 writer.write("<p>Welcome to Login Session View Page again, your last visit time is " + value + "</p>"); 85 } 86 87 Date date = new Date(); 88 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 89 String sDate = sdf.format(date); 90 request.getSession().setAttribute("lastTime", sDate); 91 } 92 93 protected void doPost(HttpServletRequest request, HttpServletResponse response) 94 throws ServletException, IOException { 95 } 96 }