摘要:雖然session機(jī)制在web應(yīng)用程序中被采用已經(jīng)很長(zhǎng)時(shí)間了,但是仍然有很多人不清楚session機(jī)制的本質(zhì),以至不能正確的應(yīng)用這一技術(shù)。本文將詳細(xì)討論session的工作機(jī)制并且對(duì)在Java web application中應(yīng)用session機(jī)制時(shí)常見的問題作出解答。
一、術(shù)語(yǔ)session
在我的經(jīng)驗(yàn)里,session這個(gè)詞被濫用的程度大概僅次于transaction,更加有趣的是transaction與session在某些語(yǔ)境下的含義是相同的。
session,中文經(jīng)常翻譯為會(huì)話,其本來(lái)的含義是指有始有終的一系列動(dòng)作/消息,比如打電話時(shí)從拿起電話撥號(hào)到掛斷電話這中間的一系列過程可以稱之為一個(gè)session。有時(shí)候我們可以看到這樣的話“在一個(gè)瀏覽器會(huì)話期間,...”,這里的會(huì)話一詞用的就是其本義,是指從一個(gè)瀏覽器窗口打開到關(guān)閉這個(gè)期間①。最混亂的是“用戶(客戶端)在一次會(huì)話期間”這樣一句話,它可能指用戶的一系列動(dòng)作(一般情況下是同某個(gè)具體目的相關(guān)的一系列動(dòng)作,比如從登錄到選購(gòu)商品到結(jié)賬登出這樣一個(gè)網(wǎng)上購(gòu)物的過程,有時(shí)候也被稱為一個(gè)transaction),然而有時(shí)候也可能僅僅是指一次連接,也有可能是指含義①,其中的差別只能靠上下文來(lái)推斷②。
然而當(dāng)session一詞與網(wǎng)絡(luò)協(xié)議相關(guān)聯(lián)時(shí),它又往往隱含了“面向連接”和/或“保持狀態(tài)”這樣兩個(gè)含義,“面向連接”指的是在通信雙方在通信之前要先建立一個(gè)通信的渠道,比如打電話,直到對(duì)方接了電話通信才能開始,與此相對(duì)的是寫信,在你把信發(fā)出去的時(shí)候你并不能確認(rèn)對(duì)方的地址是否正確,通信渠道不一定能建立,但對(duì)發(fā)信人來(lái)說,通信已經(jīng)開始了。“保持狀態(tài)”則是指通信的一方能夠把一系列的消息關(guān)聯(lián)起來(lái),使得消息之間可以互相依賴,比如一個(gè)服務(wù)員能夠認(rèn)出再次光臨的老顧客并且記得上次這個(gè)顧客還欠店里一塊錢。這一類的例子有“一個(gè)TCP session”或者“一個(gè)POP3 session”③。
而到了web服務(wù)器蓬勃發(fā)展的時(shí)代,session在web開發(fā)語(yǔ)境下的語(yǔ)義又有了新的擴(kuò)展,它的含義是指一類用來(lái)在客戶端與服務(wù)器之間保持狀態(tài)的解決方案④。有時(shí)候session也用來(lái)指這種解決方案的存儲(chǔ)結(jié)構(gòu),如“把xxx保存在session里”⑤。由于各種用于web開發(fā)的語(yǔ)言在一定程度上都提供了對(duì)這種解決方案的支持,所以在某種特定語(yǔ)言的語(yǔ)境下,session也被用來(lái)指代該語(yǔ)言的解決方案,比如經(jīng)常把Java里提供的javax.servlet.http.HttpSession簡(jiǎn)稱為session⑥。
鑒于這種混亂已不可改變,本文中session一詞的運(yùn)用也會(huì)根據(jù)上下文有不同的含義,請(qǐng)大家注意分辨。
在本文中,使用中文“瀏覽器會(huì)話期間”來(lái)表達(dá)含義①,使用“session機(jī)制”來(lái)表達(dá)含義④,使用“session”表達(dá)含義⑤,使用具體的“HttpSession”來(lái)表達(dá)含義⑥
二、HTTP協(xié)議與狀態(tài)保持
HTTP協(xié)議本身是無(wú)狀態(tài)的,這與HTTP協(xié)議本來(lái)的目的是相符的,客戶端只需要簡(jiǎn)單的向服務(wù)器請(qǐng)求下載某些文件,無(wú)論是客戶端還是服務(wù)器都沒有必要紀(jì)錄彼此過去的行為,每一次請(qǐng)求之間都是獨(dú)立的,好比一個(gè)顧客和一個(gè)自動(dòng)售貨機(jī)或者一個(gè)普通的(非會(huì)員制)大賣場(chǎng)之間的關(guān)系一樣。
然而聰明(或者貪心?)的人們很快發(fā)現(xiàn)如果能夠提供一些按需生成的動(dòng)態(tài)信息會(huì)使web變得更加有用,就像給有線電視加上點(diǎn)播功能一樣。這種需求一方面迫使HTML逐步添加了表單、腳本、DOM等客戶端行為,另一方面在服務(wù)器端則出現(xiàn)了CGI規(guī)范以響應(yīng)客戶端的動(dòng)態(tài)請(qǐng)求,作為傳輸載體的HTTP協(xié)議也添加了文件上載、cookie這些特性。其中cookie的作用就是為了解決HTTP協(xié)議無(wú)狀態(tài)的缺陷所作出的努力。至于后來(lái)出現(xiàn)的session機(jī)制則是又一種在客戶端與服務(wù)器之間保持狀態(tài)的解決方案。
讓我們用幾個(gè)例子來(lái)描述一下cookie和session機(jī)制之間的區(qū)別與聯(lián)系。筆者曾經(jīng)常去的一家咖啡店有喝5杯咖啡免費(fèi)贈(zèng)一杯咖啡的優(yōu)惠,然而一次性消費(fèi)5杯咖啡的機(jī)會(huì)微乎其微,這時(shí)就需要某種方式來(lái)紀(jì)錄某位顧客的消費(fèi)數(shù)量。想象一下其實(shí)也無(wú)外乎下面的幾種方案:
1、該店的店員很厲害,能記住每位顧客的消費(fèi)數(shù)量,只要顧客一走進(jìn)咖啡店,店員就知道該怎么對(duì)待了。這種做法就是協(xié)議本身支持狀態(tài)。
2、發(fā)給顧客一張卡片,上面記錄著消費(fèi)的數(shù)量,一般還有個(gè)有效期限。每次消費(fèi)時(shí),如果顧客出示這張卡片,則此次消費(fèi)就會(huì)與以前或以后的消費(fèi)相聯(lián)系起來(lái)。這種做法就是在客戶端保持狀態(tài)。
3、發(fā)給顧客一張會(huì)員卡,除了卡號(hào)之外什么信息也不紀(jì)錄,每次消費(fèi)時(shí),如果顧客出示該卡片,則店員在店里的紀(jì)錄本上找到這個(gè)卡號(hào)對(duì)應(yīng)的紀(jì)錄添加一些消費(fèi)信息。這種做法就是在服務(wù)器端保持狀態(tài)。
由于HTTP協(xié)議是無(wú)狀態(tài)的,而出于種種考慮也不希望使之成為有狀態(tài)的,因此,后面兩種方案就成為現(xiàn)實(shí)的選擇。具體來(lái)說cookie機(jī)制采用的是在客戶端保持狀態(tài)的方案,而session機(jī)制采用的是在服務(wù)器端保持狀態(tài)的方案。同時(shí)我們也看到,由于采用服務(wù)器端保持狀態(tài)的方案在客戶端也需要保存一個(gè)標(biāo)識(shí),所以session機(jī)制可能需要借助于cookie機(jī)制來(lái)達(dá)到保存標(biāo)識(shí)的目的,但實(shí)際上它還有其他選擇。
三、理解cookie機(jī)制
cookie機(jī)制的基本原理就如上面的例子一樣簡(jiǎn)單,但是還有幾個(gè)問題需要解決:“會(huì)員卡”如何分發(fā);“會(huì)員卡”的內(nèi)容;以及客戶如何使用“會(huì)員卡”。
正統(tǒng)的cookie分發(fā)是通過擴(kuò)展HTTP協(xié)議來(lái)實(shí)現(xiàn)的,服務(wù)器通過在HTTP的響應(yīng)頭中加上一行特殊的指示以提示瀏覽器按照指示生成相應(yīng)的cookie。然而純粹的客戶端腳本如JavaScript或者VBScript也可以生成cookie。
而cookie的使用是由瀏覽器按照一定的原則在后臺(tái)自動(dòng)發(fā)送給服務(wù)器的。瀏覽器檢查所有存儲(chǔ)的cookie,如果某個(gè)cookie所聲明的作用范圍大于等于將要請(qǐng)求的資源所在的位置,則把該cookie附在請(qǐng)求資源的HTTP請(qǐng)求頭上發(fā)送給服務(wù)器。意思是麥當(dāng)勞的會(huì)員卡只能在麥當(dāng)勞的店里出示,如果某家分店還發(fā)行了自己的會(huì)員卡,那么進(jìn)這家店的時(shí)候除了要出示麥當(dāng)勞的會(huì)員卡,還要出示這家店的會(huì)員卡。
cookie的內(nèi)容主要包括:名字,值,過期時(shí)間,路徑和域。
其中域可以指定某一個(gè)域比如.google.com,相當(dāng)于總店招牌,比如寶潔公司,也可以指定一個(gè)域下的具體某臺(tái)機(jī)器比如www.google.com或者froogle.google.com,可以用飄柔來(lái)做比。
路徑就是跟在域名后面的URL路徑,比如/或者/foo等等,可以用某飄柔專柜做比。
路徑與域合在一起就構(gòu)成了cookie的作用范圍。
如果不設(shè)置過期時(shí)間,則表示這個(gè)cookie的生命期為瀏覽器會(huì)話期間,只要關(guān)閉瀏覽器窗口,cookie就消失了。這種生命期為瀏覽器會(huì)話期的cookie被稱為會(huì)話cookie。會(huì)話cookie一般不存儲(chǔ)在硬盤上而是保存在內(nèi)存里,當(dāng)然這種行為并不是規(guī)范規(guī)定的。如果設(shè)置了過期時(shí)間,瀏覽器就會(huì)把cookie保存到硬盤上,關(guān)閉后再次打開瀏覽器,這些cookie仍然有效直到超過設(shè)定的過期時(shí)間。
存儲(chǔ)在硬盤上的cookie可以在不同的瀏覽器進(jìn)程間共享,比如兩個(gè)IE窗口。而對(duì)于保存在內(nèi)存里的cookie,不同的瀏覽器有不同的處理方式。對(duì)于IE,在一個(gè)打開的窗口上按Ctrl-N(或者從文件菜單)打開的窗口可以與原窗口共享,而使用其他方式新開的IE進(jìn)程則不能共享已經(jīng)打開的窗口的內(nèi)存cookie;對(duì)于Mozilla Firefox0.8,所有的進(jìn)程和標(biāo)簽頁(yè)都可以共享同樣的cookie。一般來(lái)說是用javascript的window.open打開的窗口會(huì)與原窗口共享內(nèi)存cookie。瀏覽器對(duì)于會(huì)話cookie的這種只認(rèn)cookie不認(rèn)人的處理方式經(jīng)常給采用session機(jī)制的web應(yīng)用程序開發(fā)者造成很大的困擾。
下面就是一個(gè)goolge設(shè)置cookie的響應(yīng)頭的例子
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
這是使用HTTPLook這個(gè)HTTP Sniffer軟件來(lái)俘獲的HTTP通訊紀(jì)錄的一部分
瀏覽器在再次訪問goolge的資源時(shí)自動(dòng)向外發(fā)送cookie
使用Firefox可以很容易的觀察現(xiàn)有的cookie的值
使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理。
IE也可以設(shè)置在接受cookie前詢問
這是一個(gè)詢問接受cookie的對(duì)話框。
四、理解session機(jī)制
session機(jī)制是一種服務(wù)器端的機(jī)制,服務(wù)器使用一種類似于散列表的結(jié)構(gòu)(也可能就是使用散列表)來(lái)保存信息。
當(dāng)程序需要為某個(gè)客戶端的請(qǐng)求創(chuàng)建一個(gè)session的時(shí)候,服務(wù)器首先檢查這個(gè)客戶端的請(qǐng)求里是否已包含了一個(gè)session標(biāo)識(shí) - 稱為session id,如果已包含一個(gè)session id則說明以前已經(jīng)為此客戶端創(chuàng)建過session,服務(wù)器就按照session id把這個(gè)session檢索出來(lái)使用(如果檢索不到,可能會(huì)新建一個(gè)),如果客戶端請(qǐng)求不包含session id,則為此客戶端創(chuàng)建一個(gè)session并且生成一個(gè)與此session相關(guān)聯(lián)的session id,session id的值應(yīng)該是一個(gè)既不會(huì)重復(fù),又不容易被找到規(guī)律以仿造的字符串,這個(gè)session id將被在本次響應(yīng)中返回給客戶端保存。
保存這個(gè)session id的方式可以采用cookie,這樣在交互過程中瀏覽器可以自動(dòng)的按照規(guī)則把這個(gè)標(biāo)識(shí)發(fā)揮給服務(wù)器。一般這個(gè)cookie的名字都是類似于SEEESIONID,而。比如weblogic對(duì)于web應(yīng)用程序生成的cookie,JSESSIONID=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是JSESSIONID。
由于cookie可以被人為的禁止,必須有其他機(jī)制以便在cookie被禁止時(shí)仍然能夠把session id傳遞回服務(wù)器。經(jīng)常被使用的一種技術(shù)叫做URL重寫,就是把session id直接附加在URL路徑的后面,附加方式也有兩種,一種是作為URL路徑的附加信息,表現(xiàn)形式為http://...../xxx;jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764另一種是作為查詢字符串附加在URL后面,表現(xiàn)形式為http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
這兩種方式對(duì)于用戶來(lái)說是沒有區(qū)別的,只是服務(wù)器在解析的時(shí)候處理的方式不同,采用第一種方式也有利于把session id的信息和正常程序參數(shù)區(qū)分開來(lái)。
為了在整個(gè)交互過程中始終保持狀態(tài),就必須在每個(gè)客戶端可能請(qǐng)求的路徑后面都包含這個(gè)session id。
另一種技術(shù)叫做表單隱藏字段。就是服務(wù)器會(huì)自動(dòng)修改表單,添加一個(gè)隱藏字段,以便在表單提交時(shí)能夠把session id傳遞回服務(wù)器。比如下面的表單
<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>
這種技術(shù)現(xiàn)在已較少應(yīng)用,筆者接觸過的很古老的iPlanet6(SunONE應(yīng)用服務(wù)器的前身)就使用了這種技術(shù)。實(shí)際上這種技術(shù)可以簡(jiǎn)單的用對(duì)action應(yīng)用URL重寫來(lái)代替。
在談?wù)搒ession機(jī)制的時(shí)候,常常聽到這樣一種誤解“只要關(guān)閉瀏覽器,session就消失了”。其實(shí)可以想象一下會(huì)員卡的例子,除非顧客主動(dòng)對(duì)店家提出銷卡,否則店家絕對(duì)不會(huì)輕易刪除顧客的資料。對(duì)session來(lái)說也是一樣的,除非程序通知服務(wù)器刪除一個(gè)session,否則服務(wù)器會(huì)一直保留,程序一般都是在用戶做log off的時(shí)候發(fā)個(gè)指令去刪除session。然而瀏覽器從來(lái)不會(huì)主動(dòng)在關(guān)閉之前通知服務(wù)器它將要關(guān)閉,因此服務(wù)器根本不會(huì)有機(jī)會(huì)知道瀏覽器已經(jīng)關(guān)閉,之所以會(huì)有這種錯(cuò)覺,是大部分session機(jī)制都使用會(huì)話cookie來(lái)保存session id,而關(guān)閉瀏覽器后這個(gè)session id就消失了,再次連接服務(wù)器時(shí)也就無(wú)法找到原來(lái)的session。如果服務(wù)器設(shè)置的cookie被保存到硬盤上,或者使用某種手段改寫瀏覽器發(fā)出的HTTP請(qǐng)求頭,把原來(lái)的session id發(fā)送給服務(wù)器,則再次打開瀏覽器仍然能夠找到原來(lái)的session。
恰恰是由于關(guān)閉瀏覽器不會(huì)導(dǎo)致session被刪除,迫使服務(wù)器為seesion設(shè)置了一個(gè)失效時(shí)間,當(dāng)距離客戶端上一次使用session的時(shí)間超過這個(gè)失效時(shí)間時(shí),服務(wù)器就可以認(rèn)為客戶端已經(jīng)停止了活動(dòng),才會(huì)把session刪除以節(jié)省存儲(chǔ)空間。
五、理解javax.servlet.http.HttpSession
HttpSession是Java平臺(tái)對(duì)session機(jī)制的實(shí)現(xiàn)規(guī)范,因?yàn)樗鼉H僅是個(gè)接口,具體到每個(gè)web應(yīng)用服務(wù)器的提供商,除了對(duì)規(guī)范支持之外,仍然會(huì)有一些規(guī)范里沒有規(guī)定的細(xì)微差異。這里我們以BEA的Weblogic Server8.1作為例子來(lái)演示。
首先,Weblogic Server提供了一系列的參數(shù)來(lái)控制它的HttpSession的實(shí)現(xiàn),包括使用cookie的開關(guān)選項(xiàng),使用URL重寫的開關(guān)選項(xiàng),session持久化的設(shè)置,session失效時(shí)間的設(shè)置,以及針對(duì)cookie的各種設(shè)置,比如設(shè)置cookie的名字、路徑、域,cookie的生存時(shí)間等。
一般情況下,session都是存儲(chǔ)在內(nèi)存里,當(dāng)服務(wù)器進(jìn)程被停止或者重啟的時(shí)候,內(nèi)存里的session也會(huì)被清空,如果設(shè)置了session的持久化特性,服務(wù)器就會(huì)把session保存到硬盤上,當(dāng)服務(wù)器進(jìn)程重新啟動(dòng)或這些信息將能夠被再次使用,Weblogic Server支持的持久性方式包括文件、數(shù)據(jù)庫(kù)、客戶端cookie保存和復(fù)制。
復(fù)制嚴(yán)格說來(lái)不算持久化保存,因?yàn)閟ession實(shí)際上還是保存在內(nèi)存里,不過同樣的信息被復(fù)制到各個(gè)cluster內(nèi)的服務(wù)器進(jìn)程中,這樣即使某個(gè)服務(wù)器進(jìn)程停止工作也仍然可以從其他進(jìn)程中取得session。
cookie生存時(shí)間的設(shè)置則會(huì)影響瀏覽器生成的cookie是否是一個(gè)會(huì)話cookie。默認(rèn)是使用會(huì)話cookie。有興趣的可以用它來(lái)試驗(yàn)我們?cè)诘谒墓?jié)里提到的那個(gè)誤解。
cookie的路徑對(duì)于web應(yīng)用程序來(lái)說是一個(gè)非常重要的選項(xiàng),Weblogic Server對(duì)這個(gè)選項(xiàng)的默認(rèn)處理方式使得它與其他服務(wù)器有明顯的區(qū)別。后面我們會(huì)專題討論。
關(guān)于session的設(shè)置參考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869
六、HttpSession常見問題
?。ㄔ诒拘」?jié)中session的含義為⑤和⑥的混合)
1、session在何時(shí)被創(chuàng)建
一個(gè)常見的誤解是以為session在有客戶端訪問時(shí)就被創(chuàng)建,然而事實(shí)是直到某server端程序調(diào)用HttpServletRequest.getSession(true)這樣的語(yǔ)句時(shí)才被創(chuàng)建,注意如果JSP沒有顯示的使用 <%@page session="false"%> 關(guān)閉session,則JSP文件在編譯成Servlet時(shí)將會(huì)自動(dòng)加上這樣一條語(yǔ)句HttpSession session = HttpServletRequest.getSession(true);這也是JSP中隱含的session對(duì)象的來(lái)歷。
由于session會(huì)消耗內(nèi)存資源,因此,如果不打算使用session,應(yīng)該在所有的JSP中關(guān)閉它。
2、session何時(shí)被刪除
綜合前面的討論,session在下列情況下被刪除a.程序調(diào)用HttpSession.invalidate();或b.距離上一次收到客戶端發(fā)送的session id時(shí)間間隔超過了session的超時(shí)設(shè)置;或c.服務(wù)器進(jìn)程被停止(非持久session)
3、如何做到在瀏覽器關(guān)閉時(shí)刪除session
嚴(yán)格的講,做不到這一點(diǎn)。可以做一點(diǎn)努力的辦法是在所有的客戶端頁(yè)面里使用javascript代碼window.oncolose來(lái)監(jiān)視瀏覽器的關(guān)閉動(dòng)作,然后向服務(wù)器發(fā)送一個(gè)請(qǐng)求來(lái)刪除session。但是對(duì)于瀏覽器崩潰或者強(qiáng)行殺死進(jìn)程這些非常規(guī)手段仍然無(wú)能為力。
4、有個(gè)HttpSessionListener是怎么回事
你可以創(chuàng)建這樣的listener去監(jiān)控session的創(chuàng)建和銷毀事件,使得在發(fā)生這樣的事件時(shí)你可以做一些相應(yīng)的工作。注意是session的創(chuàng)建和銷毀動(dòng)作觸發(fā)listener,而不是相反。類似的與HttpSession有關(guān)的listener還有HttpSessionBindingListener,HttpSessionActivationListener和HttpSessionAttributeListener。
5、存放在session中的對(duì)象必須是可序列化的嗎
不是必需的。要求對(duì)象可序列化只是為了session能夠在集群中被復(fù)制或者能夠持久保存或者在必要時(shí)server能夠暫時(shí)把session交換出內(nèi)存。在Weblogic Server的session中放置一個(gè)不可序列化的對(duì)象在控制臺(tái)上會(huì)收到一個(gè)警告。我所用過的某個(gè)iPlanet版本如果session中有不可序列化的對(duì)象,在session銷毀時(shí)會(huì)有一個(gè)Exception,很奇怪。
6、如何才能正確的應(yīng)付客戶端禁止cookie的可能性
對(duì)所有的URL使用URL重寫,包括超鏈接,form的action,和重定向的URL,具體做法參見[6]
http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770
7、開兩個(gè)瀏覽器窗口訪問應(yīng)用程序會(huì)使用同一個(gè)session還是不同的session
參見第三小節(jié)對(duì)cookie的討論,對(duì)session來(lái)說是只認(rèn)id不認(rèn)人,因此不同的瀏覽器,不同的窗口打開方式以及不同的cookie存儲(chǔ)方式都會(huì)對(duì)這個(gè)問題的答案有影響。
8、如何防止用戶打開兩個(gè)瀏覽器窗口操作導(dǎo)致的session混亂
這個(gè)問題與防止表單多次提交是類似的,可以通過設(shè)置客戶端的令牌來(lái)解決。就是在服務(wù)器每次生成一個(gè)不同的id返回給客戶端,同時(shí)保存在session里,客戶端提交表單時(shí)必須把這個(gè)id也返回服務(wù)器,程序首先比較返回的id與保存在session里的值是否一致,如果不一致則說明本次操作已經(jīng)被提交過了??梢詤⒖础禞2EE核心模式》關(guān)于表示層模式的部分。需要注意的是對(duì)于使用javascript window.open打開的窗口,一般不設(shè)置這個(gè)id,或者使用單獨(dú)的id,以防主窗口無(wú)法操作,建議不要再window.open打開的窗口里做修改操作,這樣就可以不用設(shè)置。
9、為什么在Weblogic Server中改變session的值后要重新調(diào)用一次session.setValue
做這個(gè)動(dòng)作主要是為了在集群環(huán)境中提示W(wǎng)eblogic Server session中的值發(fā)生了改變,需要向其他服務(wù)器進(jìn)程復(fù)制新的session值。
10、為什么session不見了
排除session正常失效的因素之外,服務(wù)器本身的可能性應(yīng)該是微乎其微的,雖然筆者在iPlanet6SP1加若干補(bǔ)丁的Solaris版本上倒也遇到過;瀏覽器插件的可能性次之,筆者也遇到過3721插件造成的問題;理論上防火墻或者代理服務(wù)器在cookie處理上也有可能會(huì)出現(xiàn)問題。
出現(xiàn)這一問題的大部分原因都是程序的錯(cuò)誤,最常見的就是在一個(gè)應(yīng)用程序中去訪問另外一個(gè)應(yīng)用程序。我們?cè)谙乱还?jié)討論這個(gè)問題。
七、跨應(yīng)用程序的session共享
常常有這樣的情況,一個(gè)大項(xiàng)目被分割成若干小項(xiàng)目開發(fā),為了能夠互不干擾,要求每個(gè)小項(xiàng)目作為一個(gè)單獨(dú)的web應(yīng)用程序開發(fā),可是到了最后突然發(fā)現(xiàn)某幾個(gè)小項(xiàng)目之間需要共享一些信息,或者想使用session來(lái)實(shí)現(xiàn)SSO(single sign on),在session中保存login的用戶信息,最自然的要求是應(yīng)用程序間能夠訪問彼此的session。
然而按照Servlet規(guī)范,session的作用范圍應(yīng)該僅僅限于當(dāng)前應(yīng)用程序下,不同的應(yīng)用程序之間是不能夠互相訪問對(duì)方的session的。各個(gè)應(yīng)用服務(wù)器從實(shí)際效果上都遵守了這一規(guī)范,但是實(shí)現(xiàn)的細(xì)節(jié)卻可能各有不同,因此解決跨應(yīng)用程序session共享的方法也各不相同。
首先來(lái)看一下Tomcat是如何實(shí)現(xiàn)web應(yīng)用程序之間session的隔離的,從Tomcat設(shè)置的cookie路徑來(lái)看,它對(duì)不同的應(yīng)用程序設(shè)置的cookie路徑是不同的,這樣不同的應(yīng)用程序所用的session id是不同的,因此即使在同一個(gè)瀏覽器窗口里訪問不同的應(yīng)用程序,發(fā)送給服務(wù)器的session id也可以是不同的。
根據(jù)這個(gè)特性,我們可以推測(cè)Tomcat中session的內(nèi)存結(jié)構(gòu)大致如下。
筆者以前用過的iPlanet也采用的是同樣的方式,估計(jì)SunONE與iPlanet之間不會(huì)有太大的差別。對(duì)于這種方式的服務(wù)器,解決的思路很簡(jiǎn)單,實(shí)際實(shí)行起來(lái)也不難。要么讓所有的應(yīng)用程序共享一個(gè)session id,要么讓應(yīng)用程序能夠獲得其他應(yīng)用程序的session id。
iPlanet中有一種很簡(jiǎn)單的方法來(lái)實(shí)現(xiàn)共享一個(gè)session id,那就是把各個(gè)應(yīng)用程序的cookie路徑都設(shè)為/(實(shí)際上應(yīng)該是/NASApp,對(duì)于應(yīng)用程序來(lái)講它的作用相當(dāng)于根)。
<session-info>
<path>/NASApp</path>
</session-info>
需要注意的是,操作共享的session應(yīng)該遵循一些編程約定,比如在session attribute名字的前面加上應(yīng)用程序的前綴,使得setAttribute("name", "neo")變成setAttribute("app1.name", "neo"),以防止命名空間沖突,導(dǎo)致互相覆蓋。
在Tomcat中則沒有這么方便的選擇。在Tomcat版本3上,我們還可以有一些手段來(lái)共享session。對(duì)于版本4以上的Tomcat,目前筆者尚未發(fā)現(xiàn)簡(jiǎn)單的辦法。只能借助于第三方的力量,比如使用文件、數(shù)據(jù)庫(kù)、JMS或者客戶端cookie,URL參數(shù)或者隱藏字段等手段。
我們?cè)倏匆幌耊eblogic Server是如何處理session的。
從截屏畫面上可以看到Weblogic Server對(duì)所有的應(yīng)用程序設(shè)置的cookie的路徑都是/,這是不是意味著在Weblogic Server中默認(rèn)的就可以共享session了呢?然而一個(gè)小實(shí)驗(yàn)即可證明即使不同的應(yīng)用程序使用的是同一個(gè)session,各個(gè)應(yīng)用程序仍然只能訪問自己所設(shè)置的那些屬性。這說明Weblogic Server中的session的內(nèi)存結(jié)構(gòu)可能如下
對(duì)于這樣一種結(jié)構(gòu),在session機(jī)制本身上來(lái)解決session共享的問題應(yīng)該是不可能的了。除了借助于第三方的力量,比如使用文件、數(shù)據(jù)庫(kù)、JMS或者客戶端cookie,URL參數(shù)或者隱藏字段等手段,還有一種較為方便的做法,就是把一個(gè)應(yīng)用程序的session放到ServletContext中,這樣另外一個(gè)應(yīng)用程序就可以從ServletContext中取得前一個(gè)應(yīng)用程序的引用。示例代碼如下,
應(yīng)用程序A
context.setAttribute("appA", session);
應(yīng)用程序B
contextA = context.getContext("/appA");
HttpSession sessionA = (HttpSession)contextA.getAttribute("appA");
值得注意的是這種用法不可移植,因?yàn)楦鶕?jù)ServletContext的JavaDoc,應(yīng)用服務(wù)器可以處于安全的原因?qū)τ赾ontext.getContext("/appA");返回空值,以上做法在Weblogic Server 8.1中通過。
那么Weblogic Server為什么要把所有的應(yīng)用程序的cookie路徑都設(shè)為/呢?原來(lái)是為了SSO,凡是共享這個(gè)session的應(yīng)用程序都可以共享認(rèn)證的信息。一個(gè)簡(jiǎn)單的實(shí)驗(yàn)就可以證明這一點(diǎn),修改首先登錄的那個(gè)應(yīng)用程序的描述符weblogic.xml,把cookie路徑修改為/appA訪問另外一個(gè)應(yīng)用程序會(huì)重新要求登錄,即使是反過來(lái),先訪問cookie路徑為/的應(yīng)用程序,再訪問修改過路徑的這個(gè),雖然不再提示登錄,但是登錄的用戶信息也會(huì)丟失。注意做這個(gè)實(shí)驗(yàn)時(shí)認(rèn)證方式應(yīng)該使用FORM,因?yàn)闉g覽器和web服務(wù)器對(duì)basic認(rèn)證方式有其他的處理方式,第二次請(qǐng)求的認(rèn)證不是通過session來(lái)實(shí)現(xiàn)的。具體請(qǐng)參看[7] secion 14.8 Authorization,你可以修改所附的示例程序來(lái)做這些試驗(yàn)。
八、總結(jié)
session機(jī)制本身并不復(fù)雜,然而其實(shí)現(xiàn)和配置上的靈活性卻使得具體情況復(fù)雜多變。這也要求我們不能把僅僅某一次的經(jīng)驗(yàn)或者某一個(gè)瀏覽器,服務(wù)器的經(jīng)驗(yàn)當(dāng)作普遍適用的經(jīng)驗(yàn),而是始終需要具體情況具體分析。
Using URL Rewriting
In some situations, a browser or wireless device may not accept cookies, which makes session tracking using cookies impossible. URL rewriting is a solution to this situation that can be substituted automatically when WebLogic Server detects that the browser does not accept cookies. URL rewriting involves encoding the session ID into the hyper-links on the Web pages that your servlet sends back to the browser. When the user subsequently clicks these links, WebLogic Server extracts the ID from the URL address and finds the appropriate HttpSession when your servlet calls the getSession() method.
Enable URL rewriting in WebLogic Server by setting the URLRewritingEnabled attribute in the WebLogic-specific deployment descriptor, weblogic.xml, under the <session-descriptor> element. The default value for this attribute is true. See URLRewritingEnabled.
Coding Guidelines for URL Rewriting
There are some general guidelines for how your code should handle URLs in order to support URL rewriting.
? Avoid writing a URL straight to the output stream, as shown here:
out.println("<a href=\"/myshop/catalog.jsp\">catalog</a>");
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));
WebLogic Server uses URL rewriting when a session is new, even if the browser does accept cookies, because the server cannot tell whether a browser accepts cookies in the first visit of a session.
? Your servlet can determine whether a given session ID was received from a cookie by checking the Boolean returned from the HttpServletRequest.isRequestedSessionIdFromCookie() method. Your application may respond appropriately, or simply rely on URL rewriting by WebLogic Server.
遇到關(guān)于Session的一點(diǎn)問題,在網(wǎng)上搜索看到這篇文章,收藏
只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。 | ||
![]() |
||
網(wǎng)站導(dǎo)航:
博客園
IT新聞
Chat2DB
C++博客
博問
管理
|
||