Session机制详解以及会话共享
虽然机制在web应⽤程序中被采⽤已经很长时间了,但是仍然有很多⼈不清楚ssion机制的本质,以⾄不能正确的应⽤这⼀技术。本⽂将详细讨论ssion的⼯作机制并且对在Java web application中应⽤ssion机制时常见的问题作出解答。
⼀、术语ssion 在我的经验⾥,ssion这个词被滥⽤的程度⼤概仅次于transaction,更加有趣的是transaction与ssion在某些语境下的含义是相同的。 ssion,中⽂经常翻译为会话,其本来的含义是指有始有终的⼀系列动作/消息,⽐如打电话时从拿起电话拨号到挂断电话这中间的⼀系列过程可以称之为⼀个ssion。有时候我们可以看到这样的话“在⼀个浏览器会话期间,...”,这⾥的会话⼀词⽤的就是其本义,是指从⼀个浏览器窗⼝打开到关闭这个期间①。最混乱的是“⽤户(客户端)在⼀次会话期间”这样⼀句话,它可能指⽤户的⼀系列动作(⼀般情况下是同某个具体⽬的相关的⼀系列动作,⽐如从登录到选购商品到结账登出这样⼀个⽹上购物的过程,有时候也被称为⼀个transaction),然⽽有时候也可能仅仅是指⼀次连接,也有可能是指含义①,其中的差别只能靠上下⽂来推断②。 然⽽当ssion⼀词与⽹络协议相关联时,它⼜往往隐含了“⾯向连接”和/或“保持状态”这样两个含义,“⾯向连接”指的是在通信双⽅在通信之前要先建⽴⼀个通信的渠道,⽐如打电话,直到对⽅接了电话通信才能开始,与此相对的是写信,在你把信发出去的时候你并不能确认对⽅的地址是否正确,通信渠道不⼀定能建⽴,但对发信⼈来说,通信已经开始了。“保持状态”则是指通信的⼀⽅能够把⼀系列的消息关联起来,使得消
息之间可以互相依赖,⽐如⼀个服务员能够认出再次光临的⽼顾客并且记得上次这个顾客还⽋店⾥⼀块钱。这⼀类的例⼦
有“⼀个TCP ssion”或者“⼀个POP3 ssion”③。 ⽽到了web服务器蓬勃发展的时代,ssion在web开发语境下的语义⼜有了新的扩展,它的含义是指⼀类⽤来在客户端与服务器之间保持状态的解决⽅案④。有时候ssion也⽤来指这种解决⽅案的存储结构,
如“把xxx保存在ssion⾥”⑤。由于各种⽤于web开发的语⾔在⼀定程度上都提供了对这种解决⽅案的⽀持,所以在某种特定语⾔的语境下,ssion也被⽤来指代该语⾔的解决⽅案,⽐如经常把⾥提供的javax.rvlet.http.HttpSession简称为ssion⑥。 鉴于这种混乱已不可改变,本⽂中ssion⼀词的运⽤也会根据上下⽂有不同的含义,请⼤家注意分辨。 在本⽂中,使⽤中⽂“浏览器会话期间”来表达含义①,使⽤“ssion机制”来表达含义④,使⽤“ssion”表达含义⑤,使⽤具体的“HttpSession”来表达含义⑥ ⼆、HTTP 协议与状态保持 HTTP协议本⾝是⽆状态的,这与HTTP协议本来的⽬的是相符的,客户端只需要简单的向服务器请求下载某些⽂件,⽆论是客户端还是服务器都没有必要纪录彼此过去的⾏为二年级语文上册
,每⼀次请求之间都是独⽴的,好⽐⼀个顾客和⼀个⾃动售货机或者⼀个普通鱼缸的摆放位置
的(⾮会员制)⼤卖场之间的关系⼀样。 然⽽聪明(或者贪⼼?)的⼈们很快发现如果能够提供⼀些按需⽣成的动态信息会使web变得更加有⽤,就像给有线电视加上点播功能⼀样。这种需求⼀⽅⾯迫使HTML逐步添加了表单、脚本、DOM等客户端⾏为,另⼀⽅⾯在服务器端则出现了CGI规范以响应客
户端的动态请求,作为传输载体的HTTP协议也添加了⽂件上载、cookie这些特性。其中cookie的作⽤就是为了解决HTTP协议⽆状态的缺陷所作出的努⼒。⾄于后来出现的ssion机制则是⼜⼀种在客户端与服务器之间保持状态的解决⽅案。 让我们⽤⼏个例⼦来描述⼀下cookie和ssion机制之间的区别与联系。笔者曾经常去的⼀家咖啡店有喝5杯咖啡免费赠⼀杯咖啡的优惠,然⽽⼀次性消费5杯咖啡的机会微乎其微,这时就需要某种⽅式来纪录某位顾客的消费数量。想象⼀下其实也⽆外乎下⾯的⼏种⽅案: 1、该店的店员很厉害,能记住每位顾客的消费数量,只要顾客⼀⾛进咖啡店,店员就知道该怎么对待了。这种做法就是协议本⾝⽀持状态。 2、发给顾客⼀张卡⽚,上⾯记录着消费的数量,⼀般还有个有效期限。每次消费时,如果顾客出⽰这张卡⽚,则此次消费就会与以前或以后的消费相联系起来。这种做法就是在客户端保持状态。 3、发给顾客⼀张会员卡,除了卡号之外什么信息也不纪录,每次消费时,如果顾客出⽰该卡⽚,则店员在店⾥的纪录本上找到这个卡号对应的纪录添加⼀些消费信息。这种做法就是在服务器端保持状态。 由于HTTP协议是⽆状态的,⽽出于种种考虑也不希望使之成为有状态的,因此,后⾯两种⽅案就成为现实的选择。具体来说cookie机制采⽤的是在客户端保持状态的⽅案,⽽ssion机制采⽤的是在服务器端保持状态的⽅案。同时我们也看到,由于采⽤服务器端保持状态的⽅案在客户端也需要保存⼀个标识,所以ssion机制可能需要借助于cookie机制来达到保存标识的⽬的,但实际上它还有其他选择。 三、理解cookie 机制 cookie机制的基本原理就如上⾯的例⼦⼀样简单,但是还有⼏个问题需要解决:“会员卡”如何分发;“会员卡”的内容;以及客户如何使⽤“会员卡”。 正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过
在HTTP的响应头中加上⼀⾏特殊的指⽰以提⽰浏览器按照指⽰⽣成相应的cookie。然⽽纯粹的客户端脚本如JavaScript或者VBScript也可以⽣成cookie。 ⽽cookie的使⽤是由浏览器按照⼀定的原则在后台⾃动发送给服务器的。浏览器检查所有存储的cookie,如果某个cookie所声明的作⽤范围⼤于等于将要请求的资源所在的位置,则把该cookie附在请求资源的HTTP请求头上发送给服务器。意思是麦当劳的会员卡只能在麦当劳的店⾥出⽰,如果某家分店还发⾏了⾃⼰的会员卡,那么进这家店的时候除了要出⽰麦当劳的会员卡,还要出⽰这家店的会员卡。 cookie的内容主要包括:名字,值,过期时间,路径和域。 其中域可以指定某⼀个域⽐如.,相当于总店招牌,⽐如宝洁公司,也可以指定⼀个域下的具体某台机器⽐如或者,可以⽤飘柔来做⽐。 路径就是跟在域名后⾯的URL路径,⽐如/或者/foo等等,可以⽤某飘柔专柜做⽐。路径与域合在⼀起就构成了cookie的作⽤范围。如果不设置过期时间,则表⽰这个cookie的⽣命期为浏览器会话期间,只要关闭浏览器窗⼝,cookie就消失了。这种⽣命期为浏览器会话期的cookie被称为会话cookie。会话cookie⼀般不存储在硬盘上⽽是保存在内存⾥,当然这种⾏为并不是规范规定的。如果设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。 存储在硬盘上的cook非凡反义词
ie可以在不同的浏览器进程间共享,⽐如两个IE窗⼝。⽽对于保存在内存⾥的cookie,不同的浏览器有不同的处理⽅式。对于IE,在⼀个打开的窗⼝上按Ctrl-N(或者从⽂件菜单)打开的窗⼝可以与原窗⼝共享,⽽使⽤其他⽅式新开的IE进程则不能共享已经打开的窗⼝的内存cookie;对于Mozilla Firefox0.8,所有的进程和标签页都可以共享同样的cookie。⼀般来说是⽤java
script的window.open打开的窗⼝会与原窗⼝共享内存cookie。浏览器对于会话cookie的这种只认cookie 不认⼈的处理⽅式经常给采⽤ssion机制的web应⽤程序开发者造成很⼤的困扰。
这是使⽤HTTPLook这个HTTP Sniffer软件来俘获的HTTP通讯纪录的⼀部分:
浏览器在再次访问goolge的资源时⾃动向外发送cookie:
使⽤Firefox可以很容易的观察现有的cookie的值,使⽤HTTPLook配合Firefox可以很容易的理解cookie的⼯作原理。 IE也可以设置在接受cookie前询问:
根据这个特性,我们可以推测Tomcat中ssion的内存结构⼤致如下。
笔者以前⽤过的iPlanet也采⽤的是同样的⽅式,估计SunONE与iPlanet之间不会有太⼤的差别。对于这种⽅式的服务器,解决的思路很简单,实际实⾏起来也不难。要么让小故事短篇
所有的应⽤程序共享⼀个ssion id,要么让应⽤程序能够获得其他应⽤程序的ssion id。
iPlanet中有⼀种很简单的⽅法来实现共享⼀个ssion id,那就是把各个应⽤程序的cookie路径都设为/(实际上应该是/NASApp,对于应⽤程序来讲它的作⽤相当于根)。
<ssion-info>
<path>/NASApp&女人安全期怎么算
lt;/path>
</ssion-info>
需要注意的是,操作共享的ssion应该遵循⼀些编程约定,⽐如在ssion attribute名字的前⾯加上应⽤程序的前缀,使得tAttribute("name", "neo")变成tAttribute("app1.name", "neo"),以防⽌命名空间冲突,导致互相覆盖。
在Tomcat中则没有这么⽅便的选择。在Tomcat版本3上,我们还可以有⼀些⼿段来共享ssion。对于版本4以上的Tomcat,⽬前笔者尚未发现简单的办法。只能借助于第三⽅的⼒量,⽐如使⽤⽂件、数据库、JMS或者客户端cookie,URL参数或者隐课间作文
藏字段等⼿段。
我们再看⼀下Weblogic Server是如何处理ssion的。
从截屏画⾯上可以看到Weblogic Server对所有的应⽤程序设置的cookie的路径都是/,这是不是意味着在Weblogic Server中默认的就可以共享ssion了呢?然⽽⼀个⼩实验即可证明即使不同的应⽤程序使⽤的是同⼀个ssion,各个应⽤程序仍然只能访问⾃⼰所设置的那些属性。这说明Weblogic Server中的ssion的内存结构可能如下:
对于这样⼀种结构,在ssion机制本⾝上来解决ssion共享的问题应该是不可能的了。除了借助于第三⽅的⼒量,⽐如使⽤⽂件、数据库、JMS或者客户端cookie,URL参数或者隐藏字段等⼿段,还有⼀种较为⽅便的做法,就是把⼀个应⽤程序的ssion放到ServletContext中,金上京历史博物馆
这样另外⼀个应⽤程序就可以从ServletContext中取得前⼀个应⽤程序的引⽤。⽰例代码如下,
应⽤程序A :
context.tAttribute("appA", ssion);
应⽤程序B :
contextA = Context("/appA");
HttpSession ssionA = (Attribute("appA");
值得注意的是这种⽤法不可移植,因为根据ServletContext的JavaDoc,应⽤服务器可以处于安全的原因对于
那么Weblogic Server为什么要把所有的应⽤程序的cookie路径都设为/呢?原来是为了SSO,凡是共享这个ssion的应⽤程序都可以共享认证的信息。⼀个简单的实验就可以证明这⼀点,修改⾸先登录的那个应⽤程序的描述符l,把cookie路径修改为/appA 访问另外⼀个应⽤程序会重新要求登录,即使是反过来,先访问cookie路径为/的应⽤程序,再访问修改过路径的这个,虽然不再提⽰登录,但是登录的⽤户信息也会丢失。注意做这个实验时认证⽅式应该使⽤FORM,因为浏览器和web服务器对basic认证⽅式有其他的处理⽅式,第⼆次请求的认证不是通过ssion来实现的。具体请参看[7] cion 14.8 Authorization,你可以修改所附的⽰例程序来做这些试验。
⼋、总结 ssion机制本⾝并不复杂,然⽽其实现和配置上的灵活性却使得具体情况复杂多变。这也
要求我们不能把仅仅某⼀次的经验或者某⼀个浏览器,服务器的经验当作普遍适⽤的经验,⽽是始终需要具体情况具体分析。