晉哥哥的私房錢~

          不是天才的話,那就奮斗吧……
          posts - 7, comments - 18, trackbacks - 0, articles - 0

          大話Session

          Posted on 2010-02-19 19:59 晉哥哥 閱讀(2388) 評(píng)論(5)  編輯  收藏 所屬分類: Java

          轉(zhuǎn)載請(qǐng)保留出處:shoru.cnblogs.com 晉哥哥的私房錢

          引言

              在web開(kāi)發(fā)中,session是個(gè)非常重要的概念。在許多動(dòng)態(tài)網(wǎng)站的開(kāi)發(fā)者看來(lái),session就是一個(gè)變量,而且其表現(xiàn)像個(gè)黑洞,他只需要將東西在合適的時(shí)機(jī)放進(jìn)這個(gè)洞里,等需要的時(shí)候再把東西取出來(lái)。這是開(kāi)發(fā)者對(duì)session最直觀的感受,但是黑洞里的景象或者說(shuō)session內(nèi)部到底是怎么工作的呢?當(dāng)筆者向身邊的一些同事或朋友問(wèn)及相關(guān)的更進(jìn)一步的細(xì)節(jié)時(shí),很多人往往要么含糊其辭要么主觀臆斷,所謂知其然而不知其所以然。

          筆者由此想到很多開(kāi)發(fā)者,包括我自己,每每都是糾纏于框架甚至二次開(kāi)發(fā)平臺(tái)之上,而對(duì)于其下的核心和基礎(chǔ)知之甚少,或者有心無(wú)力甚至毫不關(guān)心,少了逐本溯源的精神,每憶及此,無(wú)不慚愧。曾經(jīng)實(shí)現(xiàn)過(guò)一個(gè)簡(jiǎn)單的HttpServer,但當(dāng)時(shí)由于知識(shí)儲(chǔ)備和時(shí)間的問(wèn)題,沒(méi)有考慮到session這塊,不過(guò)近期在工作之余翻看了一些資料,并進(jìn)行了相關(guān)實(shí)踐,小有所得,本著分享的精神,我將在本文中盡可能全面地將個(gè)人對(duì)于session的理解展現(xiàn)給讀者,同時(shí)盡我所能地論及一些相關(guān)的知識(shí),以期讀者在對(duì)session有所了解的同時(shí)也能另有所悟,正所謂授人以漁。

          Session是什么

              Session一般譯作會(huì)話,牛津詞典對(duì)其的解釋是進(jìn)行某活動(dòng)連續(xù)的一段時(shí)間。從不同的層面看待session,它有著類似但不全然相同的含義。比如,在web應(yīng)用的用戶看來(lái),他打開(kāi)瀏覽器訪問(wèn)一個(gè)電子商務(wù)網(wǎng)站,登錄、并完成購(gòu)物直到關(guān)閉瀏覽器,這是一個(gè)會(huì)話。而在web應(yīng)用的開(kāi)發(fā)者開(kāi)來(lái),用戶登錄時(shí)我需要?jiǎng)?chuàng)建一個(gè)數(shù)據(jù)結(jié)構(gòu)以存儲(chǔ)用戶的登錄信息,這個(gè)結(jié)構(gòu)也叫做session。因此在談?wù)搒ession的時(shí)候要注意上下文環(huán)境。而本文談?wù)摰氖且环N基于HTTP協(xié)議的用以增強(qiáng)web應(yīng)用能力的機(jī)制或者說(shuō)一種方案,它不是單指某種特定的動(dòng)態(tài)頁(yè)面技術(shù),而這種能力就是保持狀態(tài),也可以稱作保持會(huì)話。

          為什么需要session

              談及session一般是在web應(yīng)用的背景之下,我們知道web應(yīng)用是基于HTTP協(xié)議的,而HTTP協(xié)議恰恰是一種無(wú)狀態(tài)協(xié)議。也就是說(shuō),用戶從A頁(yè)面跳轉(zhuǎn)到B頁(yè)面會(huì)重新發(fā)送一次HTTP請(qǐng)求,而服務(wù)端在返回響應(yīng)的時(shí)候是無(wú)法獲知該用戶在請(qǐng)求B頁(yè)面之前做了什么的。

              對(duì)于HTTP的無(wú)狀態(tài)性的原因,相關(guān)RFC里并沒(méi)有解釋,但聯(lián)系到HTTP的歷史以及應(yīng)用場(chǎng)景,我們可以推測(cè)出一些理由:

          1.   設(shè)計(jì)HTTP最初的目的是為了提供一種發(fā)布和接收HTML頁(yè)面的方法。那個(gè)時(shí)候沒(méi)有動(dòng)態(tài)頁(yè)面技術(shù),只有純粹的靜態(tài)HTML頁(yè)面,因此根本不需要協(xié)議能保持狀態(tài);

          2.   用戶在收到響應(yīng)時(shí),往往要花一些時(shí)間來(lái)閱讀頁(yè)面,因此如果保持客戶端和服務(wù)端之間的連接,那么這個(gè)連接在大多數(shù)的時(shí)間里都將是空閑的,這是一種資源的無(wú)端浪費(fèi)。所以HTTP原始的設(shè)計(jì)是默認(rèn)短連接,即客戶端和服務(wù)端完成一次請(qǐng)求和響應(yīng)之后就斷開(kāi)TCP連接,服務(wù)器因此無(wú)法預(yù)知客戶端的下一個(gè)動(dòng)作,它甚至都不知道這個(gè)用戶會(huì)不會(huì)再次訪問(wèn),因此讓HTTP協(xié)議來(lái)維護(hù)用戶的訪問(wèn)狀態(tài)也全然沒(méi)有必要;

          3.   將一部分復(fù)雜性轉(zhuǎn)嫁到以HTTP協(xié)議為基礎(chǔ)的技術(shù)之上可以使得HTTP在協(xié)議這個(gè)層面上顯得相對(duì)簡(jiǎn)單,而這種簡(jiǎn)單也賦予了HTTP更強(qiáng)的擴(kuò)展能力。事實(shí)上,session技術(shù)從本質(zhì)上來(lái)講也是對(duì)HTTP協(xié)議的一種擴(kuò)展。

          總而言之,HTTP的無(wú)狀態(tài)是由其歷史使命而決定的。但隨著網(wǎng)絡(luò)技術(shù)的蓬勃發(fā)展,人們?cè)僖膊粷M足于死板乏味的靜態(tài)HTML,他們希望web應(yīng)用能動(dòng)起來(lái),于是客戶端出現(xiàn)了腳本和DOM技術(shù),HTML里增加了表單,而服務(wù)端出現(xiàn)了CGI等等動(dòng)態(tài)技術(shù)。

          而正是這種web動(dòng)態(tài)化的需求,給HTTP協(xié)議提出了一個(gè)難題:一個(gè)無(wú)狀態(tài)的協(xié)議怎樣才能關(guān)聯(lián)兩次連續(xù)的請(qǐng)求呢?也就是說(shuō)無(wú)狀態(tài)的協(xié)議怎樣才能滿足有狀態(tài)的需求呢?

          此時(shí)有狀態(tài)是必然趨勢(shì)而協(xié)議的無(wú)狀態(tài)性也是木已成舟,因此我們需要一些方案來(lái)解決這個(gè)矛盾,來(lái)保持HTTP連接狀態(tài),于是出現(xiàn)了cookie和session。

          對(duì)于此部分內(nèi)容,讀者或許會(huì)有一些疑問(wèn),筆者在此先談兩點(diǎn):

          1.   無(wú)狀態(tài)性和長(zhǎng)連接

          可能有人會(huì)問(wèn),現(xiàn)在被廣泛使用的HTTP1.1默認(rèn)使用長(zhǎng)連接,它還是無(wú)狀態(tài)的嗎?

          連接方式和有無(wú)狀態(tài)是完全沒(méi)有關(guān)系的兩回事。因?yàn)闋顟B(tài)從某種意義上來(lái)講就是數(shù)據(jù),而連接方式只是決定了數(shù)據(jù)的傳輸方式,而不能決定數(shù)據(jù)。長(zhǎng)連接是隨著計(jì)算機(jī)性能的提高和網(wǎng)絡(luò)環(huán)境的改善所采取的一種合理的性能上的優(yōu)化,一般情況下,web服務(wù)器會(huì)對(duì)長(zhǎng)連接的數(shù)量進(jìn)行限制,以免資源的過(guò)度消耗。

          2.   無(wú)狀態(tài)性和session

                  Session是有狀態(tài)的,而HTTP協(xié)議是無(wú)狀態(tài)的,二者是否矛盾呢?

              Session和HTTP協(xié)議屬于不同層面的事物,后者屬于ISO七層模型的最高層應(yīng)用層,前者不屬于后者,前者是具體的動(dòng)態(tài)頁(yè)面技術(shù)來(lái)實(shí)現(xiàn)的,但同時(shí)它又是基于后者的。在下文中筆者會(huì)分析Servlet/Jsp技術(shù)中的session機(jī)制,這會(huì)使你對(duì)此有更深刻的理解。

          Cookie和Session

              上面提到解決HTTP協(xié)議自身無(wú)狀態(tài)的方式有cookie和session。二者都能記錄狀態(tài),前者是將狀態(tài)數(shù)據(jù)保存在客戶端,后者則保存在服務(wù)端。

              首先看一下cookie的工作原理,這需要有基本的HTTP協(xié)議基礎(chǔ)。

          cookie是在RFC2109(已廢棄,被RFC2965取代)里初次被描述的,每個(gè)客戶端最多保持三百個(gè)cookie,每個(gè)域名下最多20個(gè)Cookie(實(shí)際上一般瀏覽器現(xiàn)在都比這個(gè)多,如Firefox是50個(gè)),而每個(gè)cookie的大小為最多4K,不過(guò)不同的瀏覽器都有各自的實(shí)現(xiàn)。對(duì)于cookie的使用,最重要的就是要控制cookie的大小,不要放入無(wú)用的信息,也不要放入過(guò)多信息。

              無(wú)論使用何種服務(wù)端技術(shù),只要發(fā)送回的HTTP響應(yīng)中包含如下形式的頭,則視為服務(wù)器要求設(shè)置一個(gè)cookie:

          Set-cookie:name=name;expires=date;path=path;domain=domain

              支持cookie的瀏覽器都會(huì)對(duì)此作出反應(yīng),即創(chuàng)建cookie文件并保存(也可能是內(nèi)存cookie),用戶以后在每次發(fā)出請(qǐng)求時(shí),瀏覽器都要判斷當(dāng)前所有的cookie中有沒(méi)有沒(méi)失效(根據(jù)expires屬性判斷)并且匹配了path屬性的cookie信息,如果有的話,會(huì)以下面的形式加入到請(qǐng)求頭中發(fā)回服務(wù)端:

              Cookie: name="zj"; Path="/linkage"

              服務(wù)端的動(dòng)態(tài)腳本會(huì)對(duì)其進(jìn)行分析,并做出相應(yīng)的處理,當(dāng)然也可以選擇直接忽略。

              這里牽扯到一個(gè)規(guī)范(或協(xié)議)與實(shí)現(xiàn)的問(wèn)題,簡(jiǎn)單來(lái)講就是規(guī)范規(guī)定了做成什么樣子,那么實(shí)現(xiàn)就必須依據(jù)規(guī)范來(lái)做,這樣才能互相兼容,但是各個(gè)實(shí)現(xiàn)所使用的方式卻不受約束,也可以在實(shí)現(xiàn)了規(guī)范的基礎(chǔ)上超出規(guī)范,這就稱之為擴(kuò)展了。無(wú)論哪種瀏覽器,只要想提供cookie的功能,那就必須依照相應(yīng)的RFC規(guī)范來(lái)實(shí)現(xiàn)。所以這里服務(wù)器只管發(fā)Set-cookie頭域,這也是HTTP協(xié)議無(wú)狀態(tài)性的一種體現(xiàn)。

          需要注意的是,出于安全性的考慮,cookie可以被瀏覽器禁用。

              再看一下session的原理:

              筆者沒(méi)有找到相關(guān)的RFC,因?yàn)閟ession本就不是協(xié)議層面的事物。它的基本原理是服務(wù)端為每一個(gè)session維護(hù)一份會(huì)話信息數(shù)據(jù),而客戶端和服務(wù)端依靠一個(gè)全局唯一的標(biāo)識(shí)來(lái)訪問(wèn)會(huì)話信息數(shù)據(jù)。用戶訪問(wèn)web應(yīng)用時(shí),服務(wù)端程序決定何時(shí)創(chuàng)建session,創(chuàng)建session可以概括為三個(gè)步驟:

          1.   生成全局唯一標(biāo)識(shí)符(sessionid);

          2.   開(kāi)辟數(shù)據(jù)存儲(chǔ)空間。一般會(huì)在內(nèi)存中創(chuàng)建相應(yīng)的數(shù)據(jù)結(jié)構(gòu),但這種情況下,系統(tǒng)一旦掉電,所有的會(huì)話數(shù)據(jù)就會(huì)丟失,如果是電子商務(wù)網(wǎng)站,這種事故會(huì)造成嚴(yán)重的后果。不過(guò)也可以寫到文件里甚至存儲(chǔ)在數(shù)據(jù)庫(kù)中,這樣雖然會(huì)增加I/O開(kāi)銷,但session可以實(shí)現(xiàn)某種程度的持久化,而且更有利于session的共享;

          3.   將session的全局唯一標(biāo)示符發(fā)送給客戶端。

          問(wèn)題的關(guān)鍵就在服務(wù)端如何發(fā)送這個(gè)session的唯一標(biāo)識(shí)上。聯(lián)系到HTTP協(xié)議,數(shù)據(jù)無(wú)非可以放到請(qǐng)求行、頭域或Body里,基于此,一般來(lái)說(shuō)會(huì)有兩種常用的方式:cookie和URL重寫。

          1.   Cookie

          讀者應(yīng)該想到了,對(duì),服務(wù)端只要設(shè)置Set-cookie頭就可以將session的標(biāo)識(shí)符傳送到客戶端,而客戶端此后的每一次請(qǐng)求都會(huì)帶上這個(gè)標(biāo)識(shí)符,由于cookie可以設(shè)置失效時(shí)間,所以一般包含session信息的cookie會(huì)設(shè)置失效時(shí)間為0,即瀏覽器進(jìn)程有效時(shí)間。至于瀏覽器怎么處理這個(gè)0,每個(gè)瀏覽器都有自己的方案,但差別都不會(huì)太大(一般體現(xiàn)在新建瀏覽器窗口的時(shí)候);

          2.   URL重寫

          所謂URL重寫,顧名思義就是重寫URL。試想,在返回用戶請(qǐng)求的頁(yè)面之前,將頁(yè)面內(nèi)所有的URL后面全部以get參數(shù)的方式加上session標(biāo)識(shí)符(或者加在path info部分等等),這樣用戶在收到響應(yīng)之后,無(wú)論點(diǎn)擊哪個(gè)鏈接或提交表單,都會(huì)在再帶上session的標(biāo)識(shí)符,從而就實(shí)現(xiàn)了會(huì)話的保持。讀者可能會(huì)覺(jué)得這種做法比較麻煩,確實(shí)是這樣,但是,如果客戶端禁用了cookie的話,URL重寫將會(huì)是首選。

              到這里,讀者應(yīng)該明白我前面為什么說(shuō)session也算作是對(duì)HTTP的一種擴(kuò)展了吧。如下兩幅圖是筆者在Firefox的Firebug插件中的截圖,可以看到,當(dāng)我第一次訪問(wèn)index.jsp時(shí),響應(yīng)頭里包含了Set-cookie頭,而請(qǐng)求頭中沒(méi)有。當(dāng)我再次刷新頁(yè)面時(shí),圖二顯示在響應(yīng)中不在有Set-cookie頭,而在請(qǐng)求頭中卻有了Cookie頭。注意一下Cookie的名字:jsessionid,顧名思義,就是session的標(biāo)識(shí)符,另外可以看到兩幅圖中的jsessionid的值是相同的,原因筆者就不再多解釋了。另外讀者可能在一些網(wǎng)站上見(jiàn)過(guò)在最后附加了一段形如jsessionid=xxx的URL,這就是采用URL重寫來(lái)實(shí)現(xiàn)的session。

          (圖一,首次請(qǐng)求index.jsp)

          (圖二,再次請(qǐng)求index.jsp)

          Cookie和session由于實(shí)現(xiàn)手段不同,因此也各有優(yōu)缺點(diǎn)和各自的應(yīng)用場(chǎng)景:

          1.   應(yīng)用場(chǎng)景

          Cookie的典型應(yīng)用場(chǎng)景是Remember Me服務(wù),即用戶的賬戶信息通過(guò)cookie的形式保存在客戶端,當(dāng)用戶再次請(qǐng)求匹配的URL的時(shí)候,賬戶信息會(huì)被傳送到服務(wù)端,交由相應(yīng)的程序完成自動(dòng)登錄等功能。當(dāng)然也可以保存一些客戶端信息,比如頁(yè)面布局以及搜索歷史等等。

          Session的典型應(yīng)用場(chǎng)景是用戶登錄某網(wǎng)站之后,將其登錄信息放入session,在以后的每次請(qǐng)求中查詢相應(yīng)的登錄信息以確保該用戶合法。當(dāng)然還是有購(gòu)物車等等經(jīng)典場(chǎng)景;

          2.   安全性

          cookie將信息保存在客戶端,如果不進(jìn)行加密的話,無(wú)疑會(huì)暴露一些隱私信息,安全性很差,一般情況下敏感信息是經(jīng)過(guò)加密后存儲(chǔ)在cookie中,但很容易就會(huì)被竊取。而session只會(huì)將信息存儲(chǔ)在服務(wù)端,如果存儲(chǔ)在文件或數(shù)據(jù)庫(kù)中,也有被竊取的可能,只是可能性比cookie小了太多。

          Session安全性方面比較突出的是存在會(huì)話劫持的問(wèn)題,這是一種安全威脅,這在下文會(huì)進(jìn)行更詳細(xì)的說(shuō)明。總體來(lái)講,session的安全性要高于cookie;

          3.   性能

          Cookie存儲(chǔ)在客戶端,消耗的是客戶端的I/O和內(nèi)存,而session存儲(chǔ)在服務(wù)端,消耗的是服務(wù)端的資源。但是session對(duì)服務(wù)器造成的壓力比較集中,而cookie很好地分散了資源消耗,就這點(diǎn)來(lái)說(shuō),cookie是要優(yōu)于session的;

          4.   時(shí)效性

          Cookie可以通過(guò)設(shè)置有效期使其較長(zhǎng)時(shí)間內(nèi)存在于客戶端,而session一般只有比較短的有效期(用戶主動(dòng)銷毀session或關(guān)閉瀏覽器后引發(fā)超時(shí));

          5.   其他

          Cookie的處理在開(kāi)發(fā)中沒(méi)有session方便。而且cookie在客戶端是有數(shù)量和大小的限制的,而session的大小卻只以硬件為限制,能存儲(chǔ)的數(shù)據(jù)無(wú)疑大了太多。

          Servlet/JSP中的Session

              通過(guò)上述的講解,讀者應(yīng)該對(duì)session有了一個(gè)大體的認(rèn)識(shí),但是具體到某種動(dòng)態(tài)頁(yè)面技術(shù),又是怎么實(shí)現(xiàn)session的呢?下面筆者將結(jié)合session的生命周期(lifecycle),從源代碼的層次來(lái)具體分析一下在servlet/jsp技術(shù)中,session是怎么實(shí)現(xiàn)的。代碼部分以tomcat6.0.20作為參考。

          創(chuàng)建

          在我問(wèn)過(guò)的一些從事java web開(kāi)發(fā)的人中,對(duì)于session的創(chuàng)建時(shí)機(jī)大都這么回答:當(dāng)我請(qǐng)求某個(gè)頁(yè)面的時(shí)候,session就被創(chuàng)建了。這句話其實(shí)很含糊,因?yàn)橐獎(jiǎng)?chuàng)建session請(qǐng)求的發(fā)送是必不可少的,但是無(wú)論何種請(qǐng)求都會(huì)創(chuàng)建session嗎?錯(cuò)。我們來(lái)看一個(gè)例子。

          眾所周知,jsp技術(shù)是servlet技術(shù)的反轉(zhuǎn),在開(kāi)發(fā)階段,我們看到的是jsp頁(yè)面,但真正到運(yùn)行時(shí)階段,jsp頁(yè)面是會(huì)被“翻譯”為servlet類來(lái)執(zhí)行的,例如我們有如下jsp頁(yè)面:

          <%@ page language="java" pageEncoding="ISO-8859-1" session="true"%>

          <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

          <html>

              <head>

                  <title>index.jsp</title>

              </head>

              <body>

                  This is index.jsp page.

                  <br>

              </body>

          </html>

              在我們初次請(qǐng)求該頁(yè)面后,在對(duì)應(yīng)的work目錄可以找到該頁(yè)面對(duì)應(yīng)的java類,考慮到篇幅的原因,在此只摘錄比較重要的一部分,有興趣的讀者可以親自試一下:

          ......

          response.setContentType("text/html;charset=ISO-8859-1");

          pageContext = _jspxFactory.getPageContext(this, request, response,

                      null, true, 8192, true);

          _jspx_page_context = pageContext;

          application = pageContext.getServletContext();

          config = pageContext.getServletConfig();

          session = pageContext.getSession();

          out = pageContext.getOut();

          _jspx_out = out;

           

          out.write("\r\n");

          out.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\r\n");

          out.write("<html>\r\n");

          ......

              可以看到有一句顯式創(chuàng)建session的語(yǔ)句,它是怎么來(lái)的呢?我們?cè)倏匆幌聦?duì)應(yīng)的jsp頁(yè)面,在jsp的page指令中加入了session="true",意思是在該頁(yè)面啟用session,其實(shí)作為動(dòng)態(tài)技術(shù),這個(gè)參數(shù)是默認(rèn)為true的,這很合理,在此顯示寫出來(lái)只是做一下強(qiáng)調(diào)。很顯然二者有著必然的聯(lián)系。筆者在jsp/servlet的翻譯器(org.apache.jasper.compiler)的源碼中找到了相關(guān)證據(jù):

          ......

          if (pageInfo.isSession())

              out.printil("session = pageContext.getSession();");

          out.printil("out = pageContext.getOut();");

          out.printil("_jspx_out = out;");

          ......

              上面的代碼片段的意思是如果頁(yè)面中定義了session="true",就在生成的servlet源碼中加入session的獲取語(yǔ)句。這只能夠說(shuō)明session創(chuàng)建的條件,顯然還不能說(shuō)明session是如何創(chuàng)建的,本著逐本溯源的精神,我們繼續(xù)往下探索。

              有過(guò)servlet開(kāi)發(fā)經(jīng)驗(yàn)的應(yīng)該記得我們是通過(guò)HttpServletRequest的getSession方法來(lái)獲取當(dāng)前的session對(duì)象的:

          public HttpSession getSession(boolean create);

          public HttpSession getSession();

              二者的區(qū)別只是無(wú)參的getSession將create默認(rèn)設(shè)置為true而已。即:

          public HttpSession getSession() {

              return (getSession(true));

          }

              那么這個(gè)參數(shù)到底意味著什么呢?通過(guò)層層跟蹤,筆者終于理清了其中的脈絡(luò),由于函數(shù)之間的關(guān)系比較復(fù)雜,如果想更詳細(xì)地了解內(nèi)部機(jī)制,建議去獨(dú)立閱讀tomcat相關(guān)部分的源代碼。這里我將其中的大致流程敘述一下:

          1.   用戶請(qǐng)求某jsp頁(yè)面,該頁(yè)面設(shè)置了session="true";

          2.   Servlet/jsp容器將其翻譯為servlet,并加載、執(zhí)行該servlet;

          3.   Servlet/jsp容器在封裝HttpServletRequest對(duì)象時(shí)根據(jù)cookie或者url中是否存在jsessionid來(lái)決定是綁定當(dāng)前的session到HttpRequest還是創(chuàng)建新的session對(duì)象(在請(qǐng)求解析階段發(fā)現(xiàn)并記錄jsessionid,在Request對(duì)象創(chuàng)建階段將session綁定);

          4.   程序按需操作session,存取數(shù)據(jù);

          5.   如果是新創(chuàng)建的session,在結(jié)果響應(yīng)時(shí),容器會(huì)加入Set-cookie頭,以提醒瀏覽器要保持該會(huì)話(或者采用URL重寫方式將新的鏈接呈現(xiàn)給用戶)。

          通過(guò)上面的敘述讀者應(yīng)該了解了session是何時(shí)創(chuàng)建的,這里再?gòu)膕ervlet這個(gè)層面總結(jié)一下:當(dāng)用戶請(qǐng)求的servlet調(diào)用了getSession方法時(shí),都會(huì)獲取session,至于是否創(chuàng)建新的session取決于當(dāng)前request是否已綁定session。當(dāng)客戶端在請(qǐng)求中加入了jsessionid標(biāo)識(shí)而servlet容器根據(jù)此標(biāo)識(shí)查找到了對(duì)應(yīng)的session對(duì)象時(shí),會(huì)將此session綁定到此次請(qǐng)求的request對(duì)象,客戶端請(qǐng)求中不帶jsessionid或者此jsessionid對(duì)應(yīng)的session已過(guò)期失效時(shí),session的綁定無(wú)法完成,此時(shí)必須創(chuàng)建新的session。同時(shí)發(fā)送Set-cookie頭通知客戶端開(kāi)始保持新的會(huì)話。

          保持

              理解了session的創(chuàng)建,就很好理解會(huì)話是如何在客戶端和服務(wù)端之間保持的了。當(dāng)首次創(chuàng)建了session后,客戶端會(huì)在后續(xù)的請(qǐng)求中將session的標(biāo)識(shí)符帶到服務(wù)端,服務(wù)端程序只要在需要session的時(shí)候調(diào)用getSession,服務(wù)端就可以將對(duì)應(yīng)的session綁定到當(dāng)前請(qǐng)求,從而實(shí)現(xiàn)狀態(tài)的保持。當(dāng)然這需要客戶端的支持,如果禁用了cookie而又不采用url重寫的話,session是無(wú)法保持的。

              如果幾次請(qǐng)求之間有一個(gè)servlet未調(diào)用getSession(或者干脆請(qǐng)求一個(gè)靜態(tài)頁(yè)面)會(huì)不會(huì)使得會(huì)話中斷呢?這個(gè)不會(huì)發(fā)生的,因?yàn)榭蛻舳酥粫?huì)將合法的cookie值傳送給服務(wù)端,至于服務(wù)端拿cookie做什么事它是不會(huì)關(guān)心的,當(dāng)然也無(wú)法關(guān)心。Session建立之后,客戶端會(huì)一直將session的標(biāo)識(shí)符傳送到服務(wù)器,無(wú)論請(qǐng)求的頁(yè)面是動(dòng)態(tài)的、靜態(tài)的,甚至是一副圖片。

          銷毀

              此處談到的銷毀是指會(huì)話的廢棄,至于存儲(chǔ)會(huì)話信息的數(shù)據(jù)結(jié)構(gòu)是回收被重用還是直接釋放內(nèi)存我們并不關(guān)心。Session的銷毀有兩種情況:超時(shí)和手動(dòng)銷毀。

              由于HTTP協(xié)議的無(wú)狀態(tài)性,服務(wù)端無(wú)法得知一個(gè)session對(duì)象何時(shí)將再次被使用,可能用戶開(kāi)啟了一個(gè)session之后再也沒(méi)有后續(xù)的訪問(wèn),而且session的保持是需要消耗一定的服務(wù)端開(kāi)銷的,因此不可能一味地創(chuàng)建session而不去回收無(wú)用的session。這里就引入了一個(gè)超時(shí)機(jī)制。Tomcat中的超時(shí)在web.xml里做如下配置:

          <session-config>

          <session-timeout>30</session-timeout>

          </session-config>

              上述配置是指session在30分鐘沒(méi)有被再次使用就將其銷毀。Tomcat是怎么計(jì)算這個(gè)30分鐘的呢?原來(lái)在getSession之后,都要調(diào)用它的access方法,修改lastAccessedTime,在銷毀session的時(shí)候就是判斷當(dāng)前時(shí)間和這個(gè)lastAccessedTime的差值。

              手動(dòng)銷毀是指直接調(diào)用其invalidate方法,此方法實(shí)際上是調(diào)用expire方法來(lái)手動(dòng)將其設(shè)置為超時(shí)。

              當(dāng)用戶手動(dòng)請(qǐng)求了session的銷毀時(shí),客戶端是無(wú)法知道服務(wù)端的session已經(jīng)被銷毀的,它依然會(huì)發(fā)送先前的session標(biāo)識(shí)符到服務(wù)端。而此時(shí)如果再次請(qǐng)求了某個(gè)調(diào)用了getSession的servlet,服務(wù)端是無(wú)法根據(jù)先前的session標(biāo)識(shí)符找到相應(yīng)的session對(duì)象的,這是又要重新創(chuàng)建新的session,分配新的標(biāo)識(shí)符,并告知服務(wù)端更新session標(biāo)識(shí)符開(kāi)始保持新的會(huì)話。

          Session的數(shù)據(jù)結(jié)構(gòu)

              在servlet/jsp中,容器是用何種數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)session相關(guān)的變量的呢?我們猜測(cè)一下,首先它必須被同步操作,因?yàn)樵诙嗑€程環(huán)境下session是線程間共享的,而web服務(wù)器一般情況下都是多線程的(為了提高性能還會(huì)用到池技術(shù));其次,這個(gè)數(shù)據(jù)結(jié)構(gòu)必須容易操作,最好是傳統(tǒng)的鍵值對(duì)的存取方式。

              那么我們先具體到單個(gè)session對(duì)象,它除了存儲(chǔ)自身的相關(guān)信息,比如id之外,tomcat的session還提供給程序員一個(gè)用以存儲(chǔ)其他信息的接口(在類org.apache.catalina.session. StandardSession里):

          public void setAttribute(String name, Object value, boolean notify)

              在這里可以追蹤到它到底使用了何種數(shù)據(jù):

          protected Map attributes = new ConcurrentHashMap();

              這就很明確了,原來(lái)tomcat使用了一個(gè)ConcurrentHashMap對(duì)象存儲(chǔ)數(shù)據(jù),這是java的concurrent包里的一個(gè)類。它剛好滿足了我們所猜測(cè)的兩點(diǎn)需求:同步與易操作性。

              那么tomcat又是用什么數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)所有的session對(duì)象呢?果然還是ConcurrentHashMap(在管理session的org.apache.catalina.session. ManagerBase類里):

          protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();

              具體原因就不必多說(shuō)了。至于其他web服務(wù)器的具體實(shí)現(xiàn)也應(yīng)該考慮到這兩點(diǎn)。

          Session Hijack

              Session hijack即會(huì)話劫持是一種比較嚴(yán)重的安全威脅,也是一種廣泛存在的威脅,在session技術(shù)中,客戶端和服務(wù)端通過(guò)傳送session的標(biāo)識(shí)符來(lái)維護(hù)會(huì)話,但這個(gè)標(biāo)識(shí)符很容易就能被嗅探到,從而被其他人利用,這屬于一種中間人攻擊。

          本部分通過(guò)一個(gè)實(shí)例來(lái)說(shuō)明何為會(huì)話劫持,通過(guò)這個(gè)實(shí)例,讀者其實(shí)更能理解session的本質(zhì)。

          首先,我編寫了如下頁(yè)面:

          <%@ page language="java" pageEncoding="ISO-8859-1" session="true"%>

          <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

          <html>

              <head>

                 <title>index.jsp</title>

              </head>

              <body>

                 This is index.jsp page.

                 <br>

                 <%

                     Object o = session.getAttribute("counter");

                     if (o == null) {

                        session.setAttribute("counter", 1);

                     } else {

                        Integer i = Integer.parseInt(o.toString());

                        session.setAttribute("counter", i + 1);

                     }

                     out.println(session.getAttribute("counter"));

                 %>

                 <a href="<%=response.encodeRedirectURL("index.jsp")%>">index</a>

              </body>

          </html>

              頁(yè)面的功能是在session中放置一個(gè)計(jì)數(shù)器,第一次訪問(wèn)該頁(yè)面,這個(gè)計(jì)數(shù)器的值初始化為1,以后每一次訪問(wèn)這個(gè)頁(yè)面計(jì)數(shù)器都加1。計(jì)數(shù)器的值會(huì)被打印到頁(yè)面。另外,為了比較簡(jiǎn)單地模擬,筆者禁用了客戶端(采用firefox3.0)的cookie,轉(zhuǎn)而改用URL重寫方式,因?yàn)橹苯訌?fù)制鏈接要比偽造cookie方便多了。

              下面,打開(kāi)firefox訪問(wèn)該頁(yè)面,我們看到了計(jì)數(shù)器的值為1:

          (圖三)

              然后點(diǎn)擊index鏈接來(lái)刷新計(jì)數(shù)器,注意不要刷新當(dāng)前頁(yè),因?yàn)槲覀儧](méi)用采用cookie的方式,只能在url后面帶上jsessionid,而此時(shí)地址欄里的url是無(wú)法帶上jsessionid的。如圖四,我把計(jì)數(shù)器刷新到了20。

          (圖四)

              下面是最關(guān)鍵的,復(fù)制firefox地址欄里的地址(筆者看到的是http://localhost:8080/sessio

          n/index.jsp;jsessionid=1380D9F60BCE9C30C3A7CBF59454D0A5),然后打開(kāi)另一個(gè)瀏覽器,此處不必將其cookie禁用。這里我打開(kāi)了蘋果的safari3瀏覽器,然后將地址粘貼到其地址欄里,回車后如下圖:

          (圖五)

              很奇怪吧,計(jì)數(shù)器直接到了21。這個(gè)例子筆者是在同一臺(tái)計(jì)算機(jī)上做的,不過(guò)即使換用兩臺(tái)來(lái)做,其結(jié)果也是一樣的。此時(shí)如果交替點(diǎn)擊兩個(gè)瀏覽器里的index鏈接你會(huì)發(fā)現(xiàn)他們其實(shí)操縱的是同一個(gè)計(jì)數(shù)器。其實(shí)不必驚訝,此處safari盜用了firefox和tomcat之間的維持會(huì)話的鑰匙,即jsessionid,這屬于session hijack的一種。在tomcat看來(lái),safari交給了它一個(gè)jsessionid,由于HTTP協(xié)議的無(wú)狀態(tài)性,它無(wú)法得知這個(gè)jsessionid是從firefox那里“劫持”來(lái)的,它依然會(huì)去查找對(duì)應(yīng)的session,并執(zhí)行相關(guān)計(jì)算。而此時(shí)firefox也無(wú)法得知自己的保持會(huì)話已經(jīng)被“劫持”。

          結(jié)語(yǔ)

              到這里,讀者應(yīng)該對(duì)session有了更多的更深層次的了解,不過(guò)由于筆者的水平以及視野有限,文中也不乏表述欠妥之處,通篇更多地描述了在servlet/jsp中的session機(jī)制,但其他開(kāi)發(fā)平臺(tái)的機(jī)制也都萬(wàn)變不離其宗。只要認(rèn)真思考,你會(huì)發(fā)現(xiàn)其實(shí)這里林林總總之間,總有一些因果關(guān)系存在。在軟件規(guī)模日益增大的背景下,我們更多的時(shí)候接觸到的是框架、組件,程序員的雙眼被蒙蔽了,在這些框架、組件不斷產(chǎn)生以及版本的不斷更新中,其實(shí)有很多相對(duì)不變的東西,那就是規(guī)范、協(xié)議、模式、算法等等,真正令一個(gè)人得到提高的還是那些底層的支撐技術(shù)。平時(shí)多多思考的話,你就能把類似的探索轉(zhuǎn)化為印證。做技術(shù)猶如解牛,知根知底方能游刃有余。

          轉(zhuǎn)載請(qǐng)保留出處:shoru.cnblogs.com 晉哥哥的私房錢

          Feedback

          # re: 大話Session  回復(fù)  更多評(píng)論   

          2010-02-21 19:03 by 小菜花
          贊,寫得不錯(cuò),我對(duì)這一塊以前一直是一個(gè)模糊的認(rèn)識(shí),看了你的文章后更加了解了

          # re: 大話Session  回復(fù)  更多評(píng)論   

          2010-02-22 11:29 by Victor P
          非常感謝你的文章。

          # re: 大話Session  回復(fù)  更多評(píng)論   

          2010-02-22 14:06 by 雪月
          好文章

          # re: 大話Session  回復(fù)  更多評(píng)論   

          2010-05-17 22:26 by ll
          恩,寫的很好,最近正好要用這個(gè),能不能介紹點(diǎn)webservice,需要用一下~

          # re: 大話Session  回復(fù)  更多評(píng)論   

          2010-05-24 09:53 by 晉哥哥
          @ll
          請(qǐng)谷歌上場(chǎng):)

          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 朝阳区| 尉犁县| 阿克| 蛟河市| 顺昌县| 和林格尔县| 嘉义县| 汉源县| 东港市| 华安县| 商城县| 安新县| 五河县| 绥滨县| 德清县| 望奎县| 靖江市| 三河市| 通辽市| 台湾省| 疏附县| 华蓥市| 吉首市| 望江县| 沙湾县| 武隆县| 宁化县| 沙雅县| 翁源县| 荃湾区| 平和县| 宁远县| 乌苏市| 繁昌县| 邵东县| 定边县| 依安县| 牡丹江市| 莲花县| 措美县| 康乐县|