Session機制
除了使用Cookie,Web應用程序中還經常使用Session來記錄客戶端狀態。Session是服務器端使用的一種記錄客戶端狀態的機制,使用上比Cookie簡單一些,相應的也增加了服務器的存儲壓力。
Session技術則是服務端的解決方案,它是通過服務器來保持狀態的。由于Session這個詞匯包含的語義很多,因此需要在這里明確一下 Session的含義。首先,我們通常都會把Session翻譯成會話,因此我們可以把客戶端瀏覽器與服務器之間一系列交互的動作稱為一個 Session。從這個語義出發,我們會提到Session持續的時間,會提到在Session過程中進行了什么操作等等;其次,Session指的是服務器端為客戶端所開辟的存儲空間,在其中保存的信息就是用于保持狀態。從這個語義出發,我們則會提到往Session中存放什么內容,如何根據鍵值從 Session中獲取匹配的內容等。要使用Session,第一步當然是創建Session了。那么Session在何時創建呢?當然還是在服務器端程序運行的過程中創建的,不同語言實現的應用程序有不同創建Session的方法,而在Java中是通過調用HttpServletRequest的getSession方法(使用true作為參數)創建的。在創建了Session的同時,服務器會為該Session生成唯一的Session id,而這個Session id在隨后的請求中會被用來重新獲得已經創建的Session;在Session被創建之后,就可以調用Session相關的方法往Session中增加內容了,而這些內容只會保存在服務器中,發到客戶端的只有Session id;當客戶端再次發送請求的時候,會將這個Session id帶上,服務器接受到請求之后就會依據Session id找到相應的Session,從而再次使用之。正式這樣一個過程,用戶的狀態也就得以保持了。
什么是Session
Session是另一種記錄客戶狀態的機制,不同的是Cookie保存在客戶端瀏覽器中,而Session保存在服務器上。客戶端瀏覽器訪問服務器的時候,服務器把客戶端信息以某種形式記錄在服務器上。這就是Session。客戶端瀏覽器再次訪問時只需要從該Session中查找該客戶的狀態就可以了。
如果說Cookie機制是通過檢查客戶身上的“通行證”來確定客戶身份的話,那么Session機制就是通過檢查服務器上的“客戶明細表”來確認客戶身份。Session相當于程序在服務器上建立的一份客戶檔案,客戶來訪的時候只需要查詢客戶檔案表就可以了。
實現用戶登錄
Session對應的類為javax.servlet.http.HttpSession類。每個來訪者對應一個Session對象,所有該客戶的狀態信息都保存在這個Session對象里。Session對象是在客戶端第一次請求服務器的時候創建的。Session也是一種key-value的屬性對,通過getAttribute(Stringkey)和setAttribute(String key,Objectvalue)方法讀寫客戶狀態信息。Servlet里通過request.getSession()方法獲取該客戶的Session,例如:
HttpSession session = request.getSession(); // 獲取Session對象
session.setAttribute("loginTime", new Date()); // 設置Session中的屬性
out.println("登錄時間為:" +(Date)session.getAttribute("loginTime")); // 獲取Session屬性
request還可以使用getSession(boolean create)來獲取Session。區別是如果該客戶的Session不存在,request.getSession()方法會返回null,而getSession(true)會先創建Session再將Session返回。
Servlet中必須使用request來編程式獲取HttpSession對象,而JSP中內置了Session隱藏對象,可以直接使用。如果使用聲明了<%@page session="false" %>,則Session隱藏對象不可用。下面的例子使用Session記錄客戶賬號信息。 session.jsp:
<%@ page language="java" pageEncoding="UTF-8"%>
<jsp:directive.page import="com.helloweenvsfei.sessionWeb.bean.Person"/>
<jsp:directive.page import="java.text.SimpleDateFormat"/>
<jsp:directive.page import="java.text.DateFormat"/>
<jsp:directive.page import="java.util.Date"/>
<%!
DateFormat dateFormat = newSimpleDateFormat("yyyy-MM-dd"); // 日期格式化器
%>
<%
response.setCharacterEncoding("UTF-8"); // 設置request編碼
Person[] persons =
{
// 基礎數據,保存三個人的信息
new Person("Liu Jinghua","password1", 34, dateFormat.parse
("1982-01-01")),
new Person("Hello Kitty","hellokitty", 23, dateFormat.parse
("1984-02-21")),
new Person("Garfield", "garfield_pass",23, dateFormat.parse
("1994-09-12"))
};
String message = ""; // 要顯示的消息
if(request.getMethod().equals("POST"))
{
// 如果是POST登錄
for(Person person :persons)
{
// 遍歷基礎數據,驗證賬號、密碼
// 如果用戶名正確且密碼正確
if(person.getName().equalsIgnoreCase(request.getParameter("username"))&&person.getPassword().equals(request.getParameter("password")))
{
// 登錄成功,設置將用戶的信息以及登錄時間保存到Session
session.setAttribute("person", person); // 保存登錄的Person
session.setAttribute("loginTime", new Date()); // 保存登錄的時間
response.sendRedirect(request.getContextPath() + "/welcome.jsp");
return;
}
}
message = "用戶名密碼不匹配,登錄失敗。"; // 登錄失敗
}
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01Transitional//EN">
<html>
// ... HTML代碼為一個FORM表單,代碼略,請看隨書光盤
</html>
登錄界面驗證用戶登錄信息,如果登錄正確,就把用戶信息以及登錄時間保存進Session,然后轉到歡迎頁面welcome.jsp。welcome.jsp中從Session中獲取信息,并將用戶資料顯示出來。 welcome.jsp:
<%@ page language="java" pageEncoding="UTF-8"%>
<jsp:directive.pageimport="com.helloweenvsfei.sessionWeb.bean.Person"/>
<jsp:directive.page import="java.text.SimpleDateFormat"/>
<jsp:directive.page import="java.text.DateFormat"/>
<jsp:directive.page import="java.util.Date"/>
<%!
DateFormat dateFormat = newSimpleDateFormat("yyyy-MM-dd"); // 日期格式化器
%>
<%
Person person =(Person)session.getAttribute("person"); // 獲取登錄的person
Date loginTime =(Date)session.getAttribute("loginTime"); // 獲取登錄時間
%>
// ... 部分HTML代碼略
<table>
<tr><td>您的姓名:</td>
<td><%= person.getName()%></td>
</tr>
<tr><td>登錄時間:</td>
<td><%= loginTime%></td>
</tr>
<tr><td>您的年齡:</td>
<td><%= person.getAge()%></td>
</tr>
<tr><td>您的生日:</td>
<td><%=dateFormat.format(person.getBirthday()) %></td>
</tr>
</table>
注意:程序中Session中直接保存了Person類對象與Date類對象,使用起來要比Cookie方便。當多個客戶端執行程序時,服務器會保存多個客戶端的Session。獲取Session的時候也不需要聲明獲取誰的Session。Session機制決定了當前客戶只會獲取到自己的Session,而不會獲取到別人的Session。各客戶的Session也彼此獨立,互不可見。
提示:Session的使用比Cookie方便,但是過多的Session存儲在服務器內存中,會對服務器造成壓力。
Session的生命周期
Session保存在服務器端。為了獲得更高的存取速度,服務器一般把Session放在內存里。每個用戶都會有一個獨立的Session。如果Session內容過于復雜,當大量客戶訪問服務器時可能會導致內存溢出。因此,Session里的信息應該盡量精簡。
Session在用戶第一次訪問服務器的時候自動創建。需要注意只有訪問JSP、Servlet等程序時才會創建Session,只訪問HTML、IMAGE等靜態資源并不會創建Session。如果尚未生成Session,也可以使用request.getSession(true)強制生成Session。
Session生成后,只要用戶繼續訪問,服務器就會更新Session的最后訪問時間,并維護該Session。用戶每訪問服務器一次,無論是否讀寫Session,服務器都認為該用戶的Session“活躍(active)”了一次。
Session的有效期
由于會有越來越多的用戶訪問服務器,因此Session也會越來越多。為防止內存溢出,服務器會把長時間內沒有活躍的Session從內存刪除。這個時間就是Session的超時時間。如果超過了超時時間沒訪問過服務器,Session就自動失效了。
Session的超時時間為maxInactiveInterval屬性,可以通過對應的getMaxInactiveInterval()獲取,通過setMaxInactiveInterval(longinterval)修改。
Session的超時時間也可以在web.xml中修改。另外,通過調用Session的invalidate()方法可以使Session失效。
Session的常用方法
Session中包括各種方法,使用起來要比Cookie方便得多。Session的常用方法如下所示。
void setAttribute(String attribute, Object value):設置Session屬性。value參數可以為任何Java Object。通常為Java Bean。value信息不宜過大
String getAttribute(String attribute):返回Session屬性 Enumeration
getAttributeNames():返回Session中存在的屬性名
void removeAttribute(String attribute):移除Session屬性
String getId():返回Session的ID。該ID由服務器自動創建,不會重復
long getCreationTime():返回Session的創建日期。返回類型為long,常被轉化為Date類型,例如:Date createTime = new Date(session.get CreationTime())
long getLastAccessedTime():返回Session的最后活躍時間。返回類型為long
int getMaxInactiveInterval():返回Session的超時時間。單位為秒。超過該時間沒有訪問,服務器認為該Session失效 void
setMaxInactiveInterval(int second):設置Session的超時時間。單位為秒
void putValue(String attribute, Object value):不推薦的方法。已經被setAttribute(String attribute, Object Value)替代
Object getValue(String attribute):不被推薦的方法。已經被getAttribute(String attr)替代
boolean isNew():返回該Session是否是新創建的
void invalidate():使該Session失效
Tomcat中Session的默認超時時間為20分鐘。通過setMaxInactiveInterval(int seconds)修改超時時間。可以修改web.xml改變Session的默認超時時間。例如修改為60分鐘:
<session-config>
<session-timeout>60</session-timeout> <!-- 單位:分鐘 -->
</session-config>
注意:<session-timeout>參數的單位為分鐘,而setMaxInactiveInterval(int s)單位為秒。
在server.xml中定義context時采用如下定義(單位為秒):
<Context path="/livsorder" docBase="/home/httpd/html/livsorder" defaultSessionTimeOut="3600" isWARExpanded="true"
isWARValidated="false" isInvokerEnabled="true"
isWorkDirPersistent="false"/>
Session對瀏覽器的要求
雖然Session保存在服務器,對客戶端是透明的,它的正常運行仍然需要客戶端瀏覽器的支持。這是因為Session需要使用Cookie作為識別標志。HTTP協議是無狀態的,Session不能依據HTTP連接來判斷是否為同一客戶,因此服務器向客戶端瀏覽器發送一個名為JSESSIONID的Cookie,它的值為該Session的id(也就是HttpSession.getId()的返回值)。Session依據該Cookie來識別是否為同一用戶。
該Cookie為服務器自動生成的,它的maxAge屬性一般為–1,表示僅當前瀏覽器內有效,并且各瀏覽器窗口間不共享,關閉瀏覽器就會失效。
因此同一機器的兩個瀏覽器窗口訪問服務器時,會生成兩個不同的Session。但是由瀏覽器窗口內的鏈接、腳本等打開的新窗口(也就是說不是雙擊桌面瀏覽器圖標等打開的窗口)除外。這類子窗口會共享父窗口的Cookie,因此會共享一個Session。
注意:新開的瀏覽器窗口會生成新的Session,但子窗口除外。子窗口會共用父窗口的Session。例如,在鏈接上右擊,在彈出的快捷菜單中選擇“在新窗口中打開”時,子窗口便可以訪問父窗口的Session。
如果客戶端瀏覽器將Cookie功能禁用,或者不支持Cookie怎么辦?例如,絕大多數的手機瀏覽器都不支持Cookie。Java Web提供了另一種解決方案:URL地址重寫。
URL地址重寫
URL地址重寫是對客戶端不支持Cookie的解決方案。URL地址重寫的原理是將該用戶Session的id信息重寫到URL地址中。服務器能夠解析重寫后的URL獲取Session的id。這樣即使客戶端不支持Cookie,也可以使用Session來記錄用戶狀態。HttpServletResponse類提供了encodeURL(Stringurl)實現URL地址重寫,例如:
<td>
<a href="<%=response.encodeURL("index.jsp?c=1&wd=Java") %>">
Homepage</a>
</td>
該方法會自動判斷客戶端是否支持Cookie。如果客戶端支持Cookie,會將URL原封不動地輸出來。如果客戶端不支持Cookie,則會將用戶Session的id重寫到URL中。重寫后的輸出可能是這樣的:
<td>
<a href="index.jsp;jsessionid=0CCD096E7F8D97B0BE608AFDC3E1931E?c=1&wd=Java">Homepage</a>
</td>
即在文件名的后面,在URL參數的前面添加了字符串“;jsessionid=XXX”。其中XXX為Session的id。分析一下可以知道,增添的jsessionid字符串既不會影響請求的文件名,也不會影響提交的地址欄參數。用戶單擊這個鏈接的時候會把Session的id通過URL提交到服務器上,服務器通過解析URL地址獲得Session的id。
如果是頁面重定向(Redirection),URL地址重寫可以這樣寫:
<%
if(“administrator”.equals(userName)) {
response.sendRedirect(response.encodeRedirectURL(“administrator.jsp”));
return;
}
%>
效果跟response.encodeURL(String url)是一樣的:如果客戶端支持Cookie,生成原URL地址,如果不支持Cookie,傳回重寫后的帶有jsessionid字符串的地址。
對于WAP程序,由于大部分的手機瀏覽器都不支持Cookie,WAP程序都會采用URL地址重寫來跟蹤用戶會話。
注意:TOMCAT判斷客戶端瀏覽器是否支持Cookie的依據是請求中是否含有Cookie。盡管客戶端可能會支持Cookie,但是由于第一次請求時不會攜帶任何Cookie(因為并無任何Cookie可以攜帶),URL地址重寫后的地址中仍然會帶有jsessionid。當第二次訪問時服務器已經在瀏覽器中寫入Cookie了,因此URL地址重寫后的地址中就不會帶有jsessionid了。
由于Cookie可以被人為的禁止,必須有其他機制以便在Cookie被禁止時仍然能夠把session id傳遞回服務器。經常被使用的一種技術叫做URL重寫,就是把session id直接附加在URL路徑的后面,附加方式也有兩種: 一種是作為URL路徑的附加信息,表現形式為http://...../xxx;jsessionid= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764 一種是作為查詢字符串附加在URL后面,表現形式為http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
這兩種方式對于用戶來說是沒有區別的,只是服務器在解析的時候處理的方式不同,采用第一種方式也有利于把session id的信息和正常程序參數區分開來。為了在整個交互過程中始終保持狀態,就必須在每個客戶端可能請求的路徑后面都包含這個session id。
另一種技術叫做表單隱藏字段。就是服務器會自動修改表單,添加一個隱藏字段,以便在表單提交時能夠把session id傳遞回服務器。比如下面的表單:
<form name="testform" action="/xxx">
<input type="text">
</form>
在被傳遞給客戶端之前將被改寫成:
<form name="testform" action="/xxx">
<input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
<input type="text">
</form>
這種技術現在已較少應用。
在談論session機制的時候,常常聽到這樣一種誤解“只要關閉瀏覽器,session就消失了”。其實可以想象一下會員卡的例子,除非顧客主動對店家提出銷卡,否則店家絕對不會輕易刪除顧客的資料。對session來說也是一樣的,除非程序通知服務器刪除一個session,否則服務器會一直保留,程序一般都是在用戶做log off的時候發個指令去刪除session。然而瀏覽器從來不會主動在關閉之前通知服務器它將要關閉,因此服務器根本不會有機會知道瀏覽器已經關閉,之所以會有這種錯覺,是大部分session機制都使用會話cookie來保存session id,而關閉瀏覽器后這個 session id就消失了,再次連接服務器時也就無法找到原來的session。如果服務器設置的cookie被保存到硬盤上,或者使用某種手段改寫瀏覽器發出的HTTP請求頭,把原來的session id發送給服務器,則再次打開瀏覽器仍然能夠找到原來的session。
恰恰是由于關閉瀏覽器不會導致session被刪除,迫使服務器為seesion設置了一個失效時間,當距離客戶端上一次使用session的時間超過這個失效時間時,服務器就可以認為客戶端已經停止了活動,才會把session刪除以節省存儲空間。
Session中禁止使用Cookie
既然WAP上大部分的客戶瀏覽器都不支持Cookie,索性禁止Session使用Cookie,統一使用URL地址重寫會更好一些。Java Web規范支持通過配置的方式禁用Cookie。下面舉例說一下怎樣通過配置禁止使用Cookie。
打開項目sessionWeb的WebRoot目錄下的META-INF文件夾(跟WEB-INF文件夾同級,如果沒有則創建),打開context.xml(如果沒有則創建),編輯內容如下: /META-INF/context.xml:
<?xml version='1.0' encoding='UTF-8'?>
<Context path="/sessionWeb"cookies="false">
</Context>
或者修改Tomcat全局的conf/context.xml,修改內容如下: context.xml:
<!-- The contents of this file will be loaded for eachweb application -->
<Context cookies="false">
<!-- ... 中間代碼略 -->
</Context>
部署后TOMCAT便不會自動生成名JSESSIONID的Cookie,Session也不會以Cookie為識別標志,而僅僅以重寫后的URL地址為識別標志了
注意:該配置只是禁止Session使用Cookie作為識別標志,并不能阻止其他的Cookie讀寫。也就是說服務器不會自動維護名為JSESSIONID的Cookie了,但是程序中仍然可以讀寫其他的Cookie。
Cookie與Session的區別
cookie數據存放在客戶的瀏覽器上,session數據放在服務器上;
cookie不是很安全,別人可以分析存放在本地的COOKIE并進行COOKIE欺騙,考慮到安全應當使用session;
session會在一定時間內保存在服務器上。當訪問增多,會比較占用你服務器的性能。考慮到減輕服務器性能方面,應當使用COOKIE;
單個cookie在客戶端的限制是3K,就是說一個站點在客戶端存放的COOKIE不能超過3K;
Cookie和Session的方案雖然分別屬于客戶端和服務端,但是服務端的session的實現對客戶端的cookie有依賴關系的,上面我講到服務端執行session機制時候會生成session的id值,這個id值會發送給客戶端,客戶端每次請求都會把這個id值放到http請求的頭部發送給服務端,而這個id值在客戶端會保存下來,保存的容器就是cookie,因此當我們完全禁掉瀏覽器的cookie的時候,服務端的session也會不能正常使用(注意:有些資料說ASP解決這個問題,當瀏覽器的cookie被禁掉,服務端的session任然可以正常使用,ASP我沒試驗過,但是對于網絡上很多用php和jsp編寫的網站,我發現禁掉cookie,網站的session都無法正常的訪問)。