本文將詳細討論session的工做機制而且對在Java web application中應用session機制時常見的問題做出解答javascript
1、術語session html
session,中文常常翻譯爲會話,其原本的含義是指善始善終的一系列動做/消息,好比打電話時從拿起電話撥號到掛斷電話這中間的一系列過程能夠稱之爲一個session。有時候咱們能夠看到這樣的話「在一個瀏覽器會話期間,...」,這裏的會話一詞用的就是其本義,是指從一個瀏覽器窗口打開到關閉這個期間①。最混亂的是「用戶(客戶端)在一次會話期間」這樣一句話,它可能指用戶的一系列動做(通常狀況下是同某個具體目的相關的一系列動做,好比從登陸到選購商品到結帳登出這樣一個網上購物的過程,有時候也被稱爲一個transaction),然而有時候也可能僅僅是指一次鏈接,也有多是指含義①,其中的差異只能靠上下文來推斷②。
然而當session一詞與網絡協議相關聯時,它又每每隱含了「面向鏈接」和/或「保持狀態」這樣兩個含義,「面向鏈接」指的是在通訊雙方在通訊以前要先創建一個通訊的渠道,好比打電話,直到對方接了電話通訊才能開始,與此相對的是寫信,在你把信發出去的時候你並不能確認對方的地址是否正確,通訊渠道不必定能創建,但對發信人來講,通訊已經開始了。「保持狀態」則是指通訊的一方可以把一系列的消息關聯起來,使得消息之間能夠互相依賴,好比一個服務員可以認出再次光臨的老顧客而且記得上次這個顧客還欠店裏一塊錢。這一類的例子有「一個TCP session」或者「一個POP3 session」③。
而到了web服務器蓬勃發展的時代,session在web開發語境下的語義又有了新的擴展,它的含義是指一類用來在客戶端與服務器之間保持狀態的解決方案④。有時候session也用來指這種解決方案的存儲結構,如「把xxx保存在session裏」⑤。因爲各類用於web開發的語言在必定程度上都提供了對這種解決方案的支持,因此在某種特定語言的語境下,session也被用來指代該語言的解決方案,好比常常把Java裏提供的javax.servlet.http.HttpSession簡稱爲session⑥。
鑑於這種混亂已不可改變,本文中session一詞的運用也會根據上下文有不一樣的含義,請你們注意分辨。
在本文中,使用中文「瀏覽器會話期間」來表達含義①,使用「session機制」來表達含義④,使用「session」表達含義⑤,使用具體的「HttpSession」來表達含義⑥
2、HTTP協議與狀態保持
HTTP協議自己是無狀態的,這與HTTP協議原本的目的是相符的,客戶端只須要簡單的向服務器請求下載某些文件,不管是客戶端仍是服務器都沒有必要紀錄彼此過去的行爲,每一次請求之間都是獨立的,比如一個顧客和一個自動售貨機或者一個普通的(非會員制)大賣場之間的關係同樣。
然而聰明(或者貪心?)的人們很快發現若是可以提供一些按需生成的動態信息會使web變得更加有用,就像給有線電視加上點播功能同樣。這種需求一方面迫使HTML逐步添加了表單、腳本、DOM等客戶端行爲,另外一方面在服務器端則出現了CGI規範以響應客戶端的動態請求,做爲傳輸載體的HTTP協議也添加了文件上載、cookie這些特性。其中cookie的做用就是爲了解決HTTP協議無狀態的缺陷所做出的努力。至於後來出現的session機制則是又一種在客戶端與服務器之間保持狀態的解決方案。
讓咱們用幾個例子來描述一下cookie和session機制之間的區別與聯繫。筆者曾常常去的一家咖啡店有喝5杯咖啡免費贈一杯咖啡的優惠,然而一次性消費5杯咖啡的機會微乎其微,這時就須要某種方式來紀錄某位顧客的消費數量。想象一下其實也無外乎下面的幾種方案:
一、該店的店員很厲害,能記住每位顧客的消費數量,只要顧客一走進咖啡店,店員就知道該怎麼對待了。這種作法就是協議自己支持狀態。
二、發給顧客一張卡片,上面記錄着消費的數量,通常還有個有效期限。每次消費時,若是顧客出示這張卡片,則這次消費就會與之前或之後的消費相聯繫起來。這種作法就是在客戶端保持狀態。
三、發給顧客一張會員卡,除了卡號以外什麼信息也不紀錄,每次消費時,若是顧客出示該卡片,則店員在店裏的紀錄本上找到這個卡號對應的紀錄添加一些消費信息。這種作法就是在服務器端保持狀態。
因爲HTTP協議是無狀態的,而出於種種考慮也不但願使之成爲有狀態的,所以,後面兩種方案就成爲現實的選擇。具體來講cookie機制採用的是在客戶端保持狀態的方案,而session機制採用的是在服務器端保持狀態的方案。同時咱們也看到,因爲採用服務器端保持狀態的方案在客戶端也須要保存一個標識,因此session機制可能須要藉助於cookie機制來達到保存標識的目的,但實際上它還有其餘選擇。
3、理解cookie機制
cookie機制的基本原理就如上面的例子同樣簡單,可是還有幾個問題須要解決:「會員卡」如何分發;「會員卡」的內容;以及客戶如何使用「會員卡」。
正統的cookie分發是經過擴展HTTP協議來實現的,服務器經過在HTTP的響應頭中加上一行特殊的指示以提示瀏覽器按照指示生成相應的cookie。然而純粹的客戶端腳本如JavaScript或者VBScript也能夠生成cookie。
而cookie的使用是由瀏覽器按照必定的原則在後臺自動發送給服務器的。瀏覽器檢查全部存儲的cookie,若是某個cookie所聲明的做用範圍大於等於將要請求的資源所在的位置,則把該cookie附在請求資源的HTTP請求頭上發送給服務器。意思是麥當勞的會員卡只能在麥當勞的店裏出示,若是某家分店還發行了本身的會員卡,那麼進這家店的時候除了要出示麥當勞的會員卡,還要出示這家店的會員卡。
cookie的內容主要包括:名字,值,過時時間,路徑和域。
其中域能夠指定某一個域好比.google.com,至關於總店招牌,好比寶潔公司,也能夠指定一個域下的具體某臺機器好比www.google.com或者froogle.google.com,能夠用飄柔來作比。
路徑就是跟在域名後面的URL路徑,好比/或者/foo等等,能夠用某飄柔專櫃作比。路徑與域合在一塊兒就構成了cookie的做用範圍。若是不設置過時時間,則表示這個cookie的生命期爲瀏覽器會話期間,只要關閉瀏覽器窗口,cookie就消失了。這種生命期爲瀏覽器會話期的cookie被稱爲會話cookie。會話cookie通常不存儲在硬盤上而是保存在內存裏,固然這種行爲並非規範規定的。若是設置了過時時間,瀏覽器就會把cookie保存到硬盤上,關閉後再次打開瀏覽器,這些cookie仍然有效直到超過設定的過時時間。
存儲在硬盤上的cookie能夠在不一樣的瀏覽器進程間共享,好比兩個IE窗口。而對於保存在內存裏的cookie,不一樣的瀏覽器有不一樣的處理方式。對於IE,在一個打開的窗口上按Ctrl-N(或者從文件菜單)打開的窗口能夠與原窗口共享,而使用其餘方式新開的IE進程則不能共享已經打開的窗口的內存cookie;對於Mozilla Firefox0.8,全部的進程和標籤頁均可以共享一樣的cookie。通常來講是用javascript的window.open打開的窗口會與原窗口共享內存cookie。瀏覽器對於會話cookie的這種只認cookie不認人的處理方式常常給採用session機制的web應用程序開發者形成很大的困擾。 java
下面就是一個goolge設置cookie的響應頭的例子
HTTP/1.1 302 Found
Location: http://www.google.com/intl/zh-CN/
Set-Cookie: PREF=ID=0565f77e132de138:NW=1:TM=1098082649:LM=1098082649:S=KaeaCFPo49RiA_d8;
expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
Content-Type: text/html web
這是使用HTTPLook這個HTTP Sniffer軟件來俘獲的HTTP通信紀錄的一部分:數據庫
瀏覽器在再次訪問goolge的資源時自動向外發送cookie:編程
使用Firefox能夠很容易的觀察現有的cookie的值,使用HTTPLook配合Firefox能夠很容易的理解cookie的工做原理。瀏覽器
IE也能夠設置在接受cookie前詢問:安全
這是一個詢問接受cookie的對話框。
服務器
session機制是一種服務器端的機制,服務器使用一種相似於散列表的結構(也可能就是使用散列表)來保存信息。
當程序須要爲某個客戶端的請求建立一個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,而。好比weblogic對於web應用程序生成的cookie,JSESSIONID=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是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>
這種技術如今已較少應用,筆者接觸過的很古老的iPlanet6(SunONE應用服務器的前身)就使用了這種技術。
實際上這種技術能夠簡單的用對action應用URL重寫來代替。
在談論session機制的時候,經常聽到這樣一種誤解「只要關閉瀏覽器,session就消失了」。其實能夠想象一下會員卡的例子,除非顧客主動對店家提出銷卡,不然店家絕對不會輕易刪除顧客的資料。對session來講也是同樣的,除非程序通知服務器刪除一個session,不然服務器會一直保留,程序通常都是在用戶作log off的時候發個指令去刪除session。然而瀏覽器歷來不會主動在關閉以前通知服務器它將要關閉,所以服務器根本不會有機會知道瀏覽器已經關閉,之因此會有這種錯覺,是大部分session機制都使用會話cookie來保存session id,而關閉瀏覽器後這個session id就消失了,再次鏈接服務器時也就沒法找到原來的session。若是服務器設置的cookie被保存到硬盤上,或者使用某種手段改寫瀏覽器發出的HTTP請求頭,把原來的session id發送給服務器,則再次打開瀏覽器仍然可以找到原來的session。
偏偏是因爲關閉瀏覽器不會致使session被刪除,迫使服務器爲seesion設置了一個失效時間,當距離客戶端上一次使用session的時間超過這個失效時間時,服務器就能夠認爲客戶端已經中止了活動,纔會把session刪除以節省存儲空間。
5、理解javax.servlet.http.HttpSession
HttpSession是Java平臺對session機制的實現規範,由於它僅僅是個接口,具體到每一個web應用服務器的提供商,除了對規範支持以外,仍然會有一些規範裏沒有規定的細微差別。這裏咱們以BEA的Weblogic Server8.1做爲例子來演示。
首先,Weblogic Server提供了一系列的參數來控制它的HttpSession的實現,包括使用cookie的開關選項,使用URL重寫的開關選項,session持久化的設置,session失效時間的設置,以及針對cookie的各類設置,好比設置cookie的名字、路徑、域,cookie的生存時間等。
通常狀況下,session都是存儲在內存裏,當服務器進程被中止或者重啓的時候,內存裏的session也會被清空,若是設置了session的持久化特性,服務器就會把session保存到硬盤上,當服務器進程從新啓動或這些信息將可以被再次使用,Weblogic Server支持的持久性方式包括文件、數據庫、客戶端cookie保存和複製。
複製嚴格說來不算持久化保存,由於session實際上仍是保存在內存裏,不過一樣的信息被複制到各個cluster內的服務器進程中,這樣即便某個服務器進程中止工做也仍然能夠從其餘進程中取得session。
cookie生存時間的設置則會影響瀏覽器生成的cookie是不是一個會話cookie。默認是使用會話cookie。有興趣的能夠用它來試驗咱們在第四節裏提到的那個誤解。
cookie的路徑對於web應用程序來講是一個很是重要的選項,Weblogic Server對這個選項的默認處理方式使得它與其餘服務器有明顯的區別。後面咱們會專題討論。
關於session的設置參考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869 cookie
6、HttpSession常見問題(在本小節中session的含義爲⑤和⑥的混合)
一、session在什麼時候被建立
一個常見的誤解是覺得session在有客戶端訪問時就被建立,然而事實是直到某server端程序調用HttpServletRequest.getSession(true)這樣的語句時才被建立,注意若是JSP沒有顯示的使用 <%@page session="false"%> 關閉session,則JSP文件在編譯成Servlet時將會自動加上這樣一條語句HttpSession session = HttpServletRequest.getSession(true);這也是JSP中隱含的session對象的來歷。
因爲session會消耗內存資源,所以,若是不打算使用session,應該在全部的JSP中關閉它。
二、session什麼時候被刪除
綜合前面的討論,session在下列狀況下被刪除a.程序調用HttpSession.invalidate();或b.距離上一次收到客戶端發送的session id時間間隔超過了session的超時設置;或c.服務器進程被中止(非持久session)
三、如何作到在瀏覽器關閉時刪除session
嚴格的講,作不到這一點。能夠作一點努力的辦法是在全部的客戶端頁面裏使用javascript代碼window.oncolose來監視瀏覽器的關閉動做,而後向服務器發送一個請求來刪除session。可是對於瀏覽器崩潰或者強行殺死進程這些很是規手段仍然無能爲力。
四、有個HttpSessionListener是怎麼回事
你能夠建立這樣的listener去監控session的建立和銷燬事件,使得在發生這樣的事件時你能夠作一些相應的工做。注意是session的建立和銷燬動做觸發listener,而不是相反。相似的與HttpSession有關的listener還有HttpSessionBindingListener,HttpSessionActivationListener和HttpSessionAttributeListener。
五、存放在session中的對象必須是可序列化的嗎
不是必需的。要求對象可序列化只是爲了session可以在集羣中被複制或者可以持久保存或者在必要時server可以暫時把session交換出內存。在Weblogic Server的session中放置一個不可序列化的對象在控制檯上會收到一個警告。我所用過的某個iPlanet版本若是session中有不可序列化的對象,在session銷燬時會有一個Exception,很奇怪。
六、如何才能正確的應付客戶端禁止cookie的可能性
對全部的URL使用URL重寫,包括超連接,form的action,和重定向的URL,具體作法參見[6]
http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770
七、開兩個瀏覽器窗口訪問應用程序會使用同一個session仍是不一樣的session
參見第三小節對cookie的討論,對session來講是隻認id不認人,所以不一樣的瀏覽器,不一樣的窗口打開方式以及不一樣的cookie存儲方式都會對這個問題的答案有影響。
八、如何防止用戶打開兩個瀏覽器窗口操做致使的session混亂
這個問題與防止表單屢次提交是相似的,能夠經過設置客戶端的令牌來解決。就是在服務器每次生成一個不一樣的id返回給客戶端,同時保存在session裏,客戶端提交表單時必須把這個id也返回服務器,程序首先比較返回的id與保存在session裏的值是否一致,若是不一致則說明本次操做已經被提交過了。能夠參看《J2EE核心模式》關於表示層模式的部分。須要注意的是對於使用javascript window.open打開的窗口,通常不設置這個id,或者使用單獨的id,以防主窗口沒法操做,建議不要再window.open打開的窗口裏作修改操做,這樣就能夠不用設置。
九、爲何在Weblogic Server中改變session的值後要從新調用一次session.setValue
作這個動做主要是爲了在集羣環境中提示Weblogic Server session中的值發生了改變,須要向其餘服務器進程複製新的session值。
十、爲何session不見了
排除session正常失效的因素以外,服務器自己的可能性應該是微乎其微的,雖然筆者在iPlanet6SP1加若干補丁的Solaris版本上倒也遇到過;瀏覽器插件的可能性次之,筆者也遇到過3721插件形成的問題;理論上防火牆或者代理服務器在cookie處理上也有可能會出現問題。
出現這一問題的大部分緣由都是程序的錯誤,最多見的就是在一個應用程序中去訪問另一個應用程序。咱們在下一節討論這個問題。
7、跨應用程序的session共享
經常有這樣的狀況,一個大項目被分割成若干小項目開發,爲了可以互不干擾,要求每一個小項目做爲一個單獨的web應用程序開發,但是到了最後忽然發現某幾個小項目之間須要共享一些信息,或者想使用session來實現SSO(single sign on),在session中保存login的用戶信息,最天然的要求是應用程序間可以訪問彼此的session。
然而按照Servlet規範,session的做用範圍應該僅僅限於當前應用程序下,不一樣的應用程序之間是不可以互相訪問對方的session的。各個應用服務器從實際效果上都遵照了這一規範,可是實現的細節卻可能各有不一樣,所以解決跨應用程序session共享的方法也各不相同。
首先來看一下Tomcat是如何實現web應用程序之間session的隔離的,從Tomcat設置的cookie路徑來看,它對不一樣的應用程序設置的cookie路徑是不一樣的,這樣不一樣的應用程序所用的session id是不一樣的,所以即便在同一個瀏覽器窗口裏訪問不一樣的應用程序,發送給服務器的session id也能夠是不一樣的。
根據這個特性,咱們能夠推測Tomcat中session的內存結構大體以下。
筆者之前用過的iPlanet也採用的是一樣的方式,估計SunONE與iPlanet之間不會有太大的差異。對於這種方式的服務器,解決的思路很簡單,實際實行起來也不難。要麼讓全部的應用程序共享一個session id,要麼讓應用程序可以得到其餘應用程序的session id。
iPlanet中有一種很簡單的方法來實現共享一個session id,那就是把各個應用程序的cookie路徑都設爲/(實際上應該是/NASApp,對於應用程序來說它的做用至關於根)。
<session-info>
<path>/NASApp</path>
</session-info>
須要注意的是,操做共享的session應該遵循一些編程約定,好比在session attribute名字的前面加上應用程序的前綴,使得setAttribute("name", "neo")變成setAttribute("app1.name", "neo"),以防止命名空間衝突,致使互相覆蓋。
在Tomcat中則沒有這麼方便的選擇。在Tomcat版本3上,咱們還能夠有一些手段來共享session。對於版本4以上的Tomcat,目前筆者還沒有發現簡單的辦法。只能藉助於第三方的力量,好比使用文件、數據庫、JMS或者客戶端cookie,URL參數或者隱藏字段等手段。
咱們再看一下Weblogic Server是如何處理session的。
從截屏畫面上能夠看到Weblogic Server對全部的應用程序設置的cookie的路徑都是/,這是否是意味着在Weblogic Server中默認的就能夠共享session了呢?然而一個小實驗便可證實即便不一樣的應用程序使用的是同一個session,各個應用程序仍然只能訪問本身所設置的那些屬性。這說明Weblogic Server中的session的內存結構可能以下:
對於這樣一種結構,在session機制自己上來解決session共享的問題應該是不可能的了。除了藉助於第三方的力量,好比使用文件、數據庫、JMS或者客戶端cookie,URL參數或者隱藏字段等手段,還有一種較爲方便的作法,就是把一個應用程序的session放到ServletContext中,這樣另一個應用程序就能夠從ServletContext中取得前一個應用程序的引用。示例代碼以下,
應用程序A : context.setAttribute("appA", session); 應用程序B : contextA = context.getContext("/appA"); HttpSession sessionA = (HttpSession)contextA.getAttribute("appA"); 值得注意的是這種用法不可移植,由於根據ServletContext的JavaDoc,應用服務器能夠處於安全的緣由對於context.getContext("/appA");返回空值,以上作法在Weblogic Server 8.1中經過。 那麼Weblogic Server爲何要把全部的應用程序的cookie路徑都設爲/呢?原來是爲了SSO,凡是共享這個session的應用程序均可以共享認證的信息。一個簡單的實驗就能夠證實這一點,修改首先登陸的那個應用程序的描述符weblogic.xml,把cookie路徑修改成/appA訪問另一個應用程序會從新要求登陸,即便是反過來,先訪問cookie路徑爲/的應用程序,再訪問修改過路徑的這個,雖然再也不提示登陸,可是登陸的用戶信息也會丟失。注意作這個實驗時認證方式應該使用FORM,由於瀏覽器和web服務器對basic認證方式有其餘的處理方式,第二次請求的認證不是經過session來實現的。具體請參看[7] secion 14.8 Authorization,你能夠修改所附的示例程序來作這些試驗。 8、總結 session機制自己並不複雜,然而其實現和配置上的靈活性卻使得具體狀況複雜多變。這也要求咱們不能把僅僅某一次的經驗或者某一個瀏覽器,服務器的經驗看成廣泛適用的經驗,而是始終須要具體狀況具體分析。