HttpSession工作原理簡介

工程師WWW發表於2014-08-29

HTTP協議(http://www.w3.org/Protocols/)是“一次性單向”協議。 
服務端不能主動連線客戶端,只能被動等待並答覆客戶端請求。客戶端連線服務端,發出一個HTTP Request,服務端處理請求,並且返回一個HTTP Response給客戶端,本次HTTP Request-Response Cycle結束。 
我們看到,
HTTP協議本身並不能支援服務端儲存客戶端的狀態資訊。於是,Web Server中引入了session的概念,用來儲存客戶端的狀態資訊。 
這裡用一個形象的比喻來解釋session的工作方式。假設Web Server是一個商場的存包處,HTTP Request是一個顧客,第一次來到存包處,管理員把顧客的物品存放在某一個櫃子裡面(這個櫃子就相當於Session),然後把一個號碼牌交給這個顧 客,作為取包憑證(這個號碼牌就是Session ID)。顧客(HTTP Request)下一次來的時候,就要把號碼牌(Session ID)交給存包處(Web Server)的管理員。管理員根據號碼牌(Session ID)找到相應的櫃子(Session),根據顧客(HTTP Request)的請求,Web Server可以取出、更換、新增櫃子(Session)中的物品,Web Server也可以讓顧客(HTTP Request)的號碼牌和號碼牌對應的櫃子(Session)失效。顧客(HTTP Request)的忘性很大,管理員在顧客回去的時候(HTTP Response)都要重新提醒顧客記住自己的號碼牌(Session ID)。這樣,顧客(HTTP Request)下次來的時候,就又帶著號碼牌回來了。 
我們可以看到,Session ID實際上是在客戶端和服務端之間通過HTTP Request和HTTP Response傳來傳去的。 

我們看到,號碼牌(Session ID)必須包含在HTTP Request裡面。關於HTTP Request的具體格式,請參見HTTP協議(http://www.w3.org/Protocols/)。這裡只做一個簡單的介紹。 
在Java Web Server(即Servlet/JSP Server)中,Session ID用jsessionid表示(請參見Servlet規範)。 
HTTP Request一般由3部分組成: 
(1)Request Line 
這一行由HTTP Method(如GET或POST)、URL、和HTTP版本號組成。 
例如,GET http://www.w3.org/pub/WWW/TheProject.html HTTP/1.1 
GET http://www.google.com/search?q=Tomcat HTTP/1.1 
POST http://www.google.com/search HTTP/1.1 
GET http://www.somsite.com/menu.do;jsessionid=1001 HTTP/1.1 

(2)Request Headers 
這部分定義了一些重要的頭部資訊,如,瀏覽器的種類,語言,型別。Request Headers中還可以包括Cookie的定義。例如: 
User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0) 
Accept-Language: en-us 
Cookie: jsessionid=1001 

(3)Message Body 
如果HTTP Method是GET,那麼Message Body為空。 
如果HTTP Method是POST,說明這個HTTP Request是submit一個HTML Form的結果, 
那麼Message Body為HTML Form裡面定義的Input屬性。例如, 
user=guest 
password=guest 
jsessionid=1001 
主意,如果把HTML Form元素的Method屬性改為GET。那麼,Message Body為空,所有的Input屬性都會加在URL的後面。你在瀏覽器的URL位址列中會看到這些屬性,類似於 
http://www.somesite/login.do?user=guest&password=guest&jsessionid=1001 

從理論上來說,這3個部分(Request URL,Cookie Header, Message Body)都可以用來存放Session ID。由於Message Body方法必須需要一個包含Session ID的HTML Form,所以這種方法不通用。 
一般用來實現Session的方法有兩種: 
(1)URL重寫。 
Web Server在返回Response的時候,檢查頁面中所有的URL,包括所有的連線,和HTML Form的Action屬性,在這些URL後面加上“;jsessionid=XXX”。 
下一次,使用者訪問這個頁面中的URL。jsessionid就會傳回到Web Server。 
(2)Cookie。 
如果客戶端支援Cookie,Web Server在返回Response的時候,在Response的Header部分,加入一個“set-cookie: jsessionid=XXXX”header屬性,把jsessionid放在Cookie裡傳到客戶端。 
客戶端會把Cookie存放在本地檔案裡,下一次訪問Web Server的時候,再把Cookie的資訊放到HTTP Request的“Cookie”header屬性裡面,這樣jsessionid就隨著HTTP Request返回給Web Server。


=======================================================================

一個session就是一系列某使用者和伺服器間的通訊。伺服器有能力分辨出不同的使用者。一個session的建立是從一個使用者向伺服器發第一個請求開始,而以使用者顯式結束或session超時為結束。
其工作原理是這樣的:
1.當一個使用者向伺服器傳送第一個請求時,伺服器為其建立一個session,併為此session建立一個標識號;
2.這個使用者隨後的所有請求都應包括這個標識號。伺服器會校對這個標識號以判斷請求屬於哪個session。
這種機制不使用IP作為標識,是因為很多機器是通過代理伺服器方式上網,沒法區分每一臺機器。
對於session標識號(sessionID),有兩種方式實現:cookies和URL重寫。

HttpSession的使用
我們來看看在API中對session是如何定義和操作的。
當需要為使用者端建立一個session時,servlet容器就建立了一個HttpSession物件。其中儲存了和本session相關的資訊。所以,在一個servlet中有多少個不同使用者連線,就會有多少個HttpSession物件。
使用的機理是:
1.從請求中提取HttpSession物件;
2.增加或刪除HttpSession中的屬性;
3.根據需要關閉HttpSession或使其失效。

在請求中有兩個過載的方法用來獲取HttpSession物件。
HttpSession getSession(boolean create)/getSession();作用是提取HttpSession物件,如果沒有自動建立。

獲取到HttpSession物件後,我們就需要使用HttpSession的某些方法去設定和更改某些引數了。如:
void setAttribute(String name, Object value);
Object getAttribute(String name);
void removeAttribute(String name);

在javax.servlet.http包裡一共定義了四個session監聽器介面和與之關聯的兩個session事件。分別是:
HttpSessionAttributeListener and HttpSessionBindingEvent;
HttpSessionBindingListener and HttpSessionBindingEvent;


HttpSessionListener and HttpSessionEvent;
HttpSessionActivationListener and HttpSessionEvent.

他們的繼承關係是:
所有四個介面的父類是java.util.EventListener;
HttpSessionEvent擴充套件java.util.EventObject;
而HttpSessionBindingEvent又擴充套件了HttpSessionEvent。

以下分別詳述:
HttpSessionAttributeListener
當session中的屬性被新增,更改,刪除時得到通知。這個介面上節講過,主要看其它三個。

HttpSessionBindingListener
當一個實現了HttpSessionBindingListener的類被加入到HttpSession中(或從中移出)時,會產生HttpBindingEvent事件,而這些事件會被它本身接收到。
本介面定義了兩個方法:
void valueBound(HttpSessionBindingEvent e);
void valueUnbound(HttpSessionBindingEvent e);
當多個實現了HttpSessionBindingListener的類被加入到HttpSession中時,各類的方法只對本類感興趣,不會去理會其它類的加入。
即使是同一類的不同例項間,也是互不關心的(各掃門前雪)。

我們可以看到前兩個介面都對HttpSessionBindingEvent事件做出反應,但機理不同。
HttpSessionAttributeListener是在web.xml中登記的,servlet容器僅建立一個例項,來為任何在session中增加屬性的servlet服務。觸發事件的物件是所有可以轉換為Object的例項。
HttpSessionBindingListener不用在web.xml中登記,在每個servlet中用new建立例項,且僅對本例項向session中的加入(或移出)感興趣。觸發事件的物件僅僅是自己。

HttpSessionListener
對於session的建立和取消感興趣。需要在web.xml中登記。
共有兩個方法:
void sessionCreated(HttpSessionEvent se);
void sessionDestroyed(HttpSessionEvent se);
使用它我們可以容易的建立一個類來對session計數。
也許我們會簡單的考慮使用sessionDestroyed方法來在session結束後做一些清理工作。但是,請注意,當這個方法被呼叫的時候,session已經結束了,你不能從中提取到任何資訊了。因此,我們要另闢蹊徑。
一種通常採用的方法是使用HttpSessionBindingListener介面。在session建立時增加一個屬性,而在session結束前最 後一件事將這個屬性刪除,這樣就會觸發valueUnbound方法,所有對session的清理工作可以在這個方法中實現。

HttpSessionActivationListener
當session在分散式環境中跨JVM時,實現該介面的物件得到通知。共兩個方法:
void sessionDidActivate(HttpSessionEvent se);
void sessionWillPassivate(HttpSessionEvent se);


1、HTTP協議本身是“連線-請求-應答-關閉連線”模式的,是一種無狀態協議(HTTP只是一個傳輸協議);
2、Cookie規範是為了給HTTP增加狀態跟蹤用的(如果要精確把握,建議仔細閱讀一下相關的RFC),但不是唯一的手段;
3、所謂Session,指的是客戶端和服務端之間的一段互動過程的狀態資訊(資料);這個狀態如何界定,生命期有多長,這是應用本身的事情;
4、由於B/S計算模型中計算是在伺服器端完成的,客戶端只有簡單的顯示邏輯,所以,Session資料對客戶端應該是透明的不可理解的並且應該受控於服 務端;Session資料要麼儲存到服務端(HttpSession),要麼在客戶端和服務端之間傳遞(Cookie或url rewritting或Hidden input);
5、由於HTTP本身的無狀態性,服務端無法知道客戶端相繼發來的請求是來自一個客戶的,所以,當使用服務端HttpSession儲存會話資料的時候客戶端的每個請求都應該包含一個session的標識(sid, jsessionid 等等)來告訴服務端;
6、會話資料儲存在服務端(如HttpSession)的好處是減少了HTTP請求的長度,提高了網路傳輸效率;客戶端session資訊儲存則相反;
7、客戶端Session儲存只有一個辦法:cookie(url rewritting和hidden input因為無法做到持久化,不算,只能作為交換session id的方式,即a method of session tracking),而服務端做法大致也是一個道理:容器有個session管理器(如tomcat的 org.apache.catalina.session包裡面的類),提供session的生命週期和持久化管理並提供訪問session資料的 api;
8、使用服務端還是客戶端session儲存要看應用的實際情況的。
一般來說不要求使用者註冊登入的公共服務系統(如google)採用cookie做客戶 端session儲存(如google的使用者偏好設定),而有使用者管理的系統則使用服務端儲存。原因很顯然:無需使用者登入的系統唯一能夠標識使用者的就是用 戶的電腦,換一臺機器就不知道誰是誰了,服務端session儲存根本不管用;而有使用者管理的系統則可以通過使用者id來管理使用者個人資料,從而提供任意複雜的個性化服務;
9、客戶端和服務端的session儲存在效能、安全性、跨站能力、程式設計方便性等方面都有一定的區別,而且優劣並非絕對(譬如TheServerSide 號稱不使用HttpSession,所以效能好,這很顯然:一個具有上億的訪問使用者的系統,要在服務端資料庫中檢索出使用者的偏好資訊顯然是低效的,Session管理器不管用什麼資料結構和演算法都要耗費大量記憶體和CPU時間;而用cookie,則根本不用檢索和維護session資料,伺服器可 以做成無狀態的,當然高效);
10、 所謂的“會話cookie”簡單的說就是沒有明確指明有效期的cookie,僅在瀏覽器當前程式生命期內有效,可以被後繼的Set-Cookie操作清除掉。
當程式需要為某個客戶端的請求建立一個session的時候,伺服器首先檢查這個客戶端的請求裡是否已包含了一個session標識 - 稱為 session id,如果已包含一個session id則說明以前已經為此客戶端建立過session,伺服器就按照session id把這個 session檢索出來使用;如果客戶端請求不包含session id,則為此客戶端建立一個session並且生成一個與此session相關聯的session id,session id的值應該是一個既不會重複,又不容易被找到規律以仿造的字串,這個 session id將被在本次響應中返回給客戶端儲存。
儲存這個session id的方式可以採用cookie,這樣在互動過程中瀏覽器可以自動的按照規則把這個標識發揮給伺服器。一般這個cookie的名字都是類似於SEEESIONID.
1、session在何時被建立
一個常見的誤解是以為session在有客戶端訪問時就被建立,然而事實是直到某server端程式呼叫 HttpServletRequest.getSession(true)這樣的語句時才被建立,注意如果JSP沒有顯示的使用 <% @page session="false"%> 關閉session,則JSP檔案在編譯成Servlet時將會自動加上這樣一條語句 HttpSession session = HttpServletRequest.getSession(true);這也是JSP中隱含的 session物件的來歷。由於session會消耗記憶體資源,因此,如果不打算使用session,應該在所有的JSP中關閉它。
2、存放在session中的物件必須是可序列化的嗎
不是必需的。要求物件可序列化只是為了session能夠在叢集中被複制或者能夠持久儲存或者在必要時server能夠暫時把session交換出記憶體。
3、如何才能正確的應付客戶端禁止cookie的可能性
對所有的URL使用URL重寫,包括超連結,form的action,和重定向的URL,具體做法參見:
http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770
Instead, use the HttpServletResponse.encodeURL() method, for example:
out.println("<a href=\""     + response.encodeURL("myshop/catalog.jsp")      + "\">catalog</a>");
Calling the encodeURL() method determines if the URL needs to be rewritten, and if so, it rewrites it by including the session ID in the URL. The session ID is appended to the URL and begins with a semicolon.

In addition to URLs that are returned as a response to WebLogic Server, also encode URLs that send redirects. For example:
if (session.isNew())  response.sendRedirect (response.encodeRedirectUrl(welcomeURL));
4、開兩個瀏覽器視窗訪問應用程式會使用同一個session還是不同的session
參見第三小節對cookie的討論,對session來說是隻認id不認人,因此不同的瀏覽器,不同的視窗開啟方式以及不同的cookie儲存方式都會對這個問題的答案有影響。

相關文章