原文地址:http://www.egzcn.com/article/webbc/JSP/2006-03-22/2002.html
五、理解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
六、HttpSession常見問題
(在本小節中session的含義為⑤和⑥的混合)
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在下列情況下被刪除a.程式調用HttpSession.invalidate();或b.距離上一次收到用戶端發送的session id時間間隔超過了session的逾時設定;或c.伺服器處理序被停止(非持久session)
3、如何做到在瀏覽器關閉時刪除session
嚴格的講,做不到這一點。可以做一點努力的辦法是在所有的用戶端頁面裡使用javascript代碼window.oncolose來監視瀏覽器的關閉動作,然後向伺服器發送一個請求來刪除session。但是對於瀏覽器崩潰或者強行殺死進程這些非常規手段仍然無能為力。
4、有個HttpSessionListener是怎麼回事
你可以建立這樣的listener去監控session的建立和銷毀事件,使得在發生這樣的事件時你可以做一些相應的工作。注意是session的建立和銷毀動作觸發listener,而不是相反。類似的與HttpSession有關的listener還有 HttpSessionBindingListener,HttpSessionActivationListener和 HttpSessionAttributeListener。
5、存放在session中的對象必須是可序列化的嗎
不是必需的。要求對象可序列化只是為了session能夠在叢集中被複製或者能夠持久儲存或者在必要時server能夠暫時把session交換出記憶體。在 Weblogic Server的session中放置一個不可序列化的對象在控制台上會收到一個警告。我所用過的某個iPlanet版本如果 session中有不可序列化的對象,在session銷毀時會有一個Exception,很奇怪。
6、如何才能正確的應付用戶端禁止cookie的可能性
對所有的URL使用URL重寫,包括超連結,form的action,和重新導向的URL,具體做法參見[6]
http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770
7、開兩個瀏覽器視窗訪問應用程式會使用同一個session還是不同的session
參見第三小節對cookie的討論,對session來說是只認id不認人,因此不同的瀏覽器,不同的視窗開啟檔案以及不同的cookie儲存方式都會對這個問題的答案有影響。
8、如何防止使用者開啟兩個瀏覽器視窗操作導致的session混亂
這 個問題與防止表單多次提交是類似的,可以通過設定用戶端的令牌來解決。就是在伺服器每次產生一個不同的id返回給用戶端,同時儲存在session裡,客 戶端提交表單時必須把這個id也返回伺服器,程式首先比較返回的id與儲存在session裡的值是否一致,如果不一致則說明本次操作已經被提交過了。可 以參看《J2EE核心模式》關於展示層模式的部分。需要注意的是對於使用javascript window.open開啟的視窗,一般不設定這個id, 或者使用單獨的id,以防主視窗無法操作,建議不要再window.open開啟的視窗裡做修改操作,這樣就可以不用設定。
9、為什麼在Weblogic Server中改變session的值後要重新調用一次session.setValue
做這個動作主要是為了在叢集環境中提示Weblogic Server session中的值發生了改變,需要向其他伺服器處理序複製新的session值。
10、為什麼session不見了
排 除session正常失效的因素之外,伺服器本身的可能性應該是微乎其微的,雖然筆者在iPlanet6SP1加若干補丁的Solaris版本上倒也遇到 過;瀏覽器外掛程式的可能性次之,筆者也遇到過3721外掛程式造成的問題;理論上防火牆或者Proxy 伺服器在cookie處理上也有可能會出現問題。
出現這一問題的大部分原因都是程式的錯誤,最常見的就是在一個應用程式中去訪問另外一個應用程式。我們在下一節討論這個問題。
七、跨應用程式的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,你可以修改所附的樣本程式來做這些實驗。
八、總結
session機制本身並不複雜,然而其實現和配置上的靈活性卻使得具體情況複雜多變。這也要求我們不能把僅僅某一次的經驗或者某一個瀏覽器,伺服器的經驗當作普遍適用的經驗,而是始終需要具體情況具體分析。