HttpSession框架指南
来源:互联网 发布:虚拟专用网络是免费的 编辑:程序博客网 时间:2024/06/05 16:10
HttpSession框架指南
1.问题
众所周知,标准的Servlet API中,有一个HttpSession的接口。本来HTTP协议是无状态的,通过session机制,就能把无状态的变成有状态的。有了session的支持,WEB应该就能够跟踪一个用户的操作状态。在一个WEB应用中,你可以这样使用session:
// 取得session对象
HttpSessionsession = request.getSession();
// 在session中保存用户状态
session.setAttribute(“loginId”,“myName”);
// 在另一个请求中,取出session的状态
String myName =(String) session.getAttribute(“loginId”);
如何保存session中的状态?一般的做法,是将session对象保存在内存里,然后用一Session ID来索引。同一时间,会有很多session被保存在服务器的内存里,但每个session的ID都是不一样的。对于JavaServlet,常见的做法是把这个SessionID保存在cookie里。这样一来,凡是cookie值相同的所有的请求,就被看作是在同一个session中的请求。由于内存是有限的,较先进的服务器会把session对象交换到文件中,以确保内存中的session数保持在一个合理的范围内。
为了提高系统扩展性和可用性,我们会使用集群技术—— 就是一组独立的机器共同运行同一个应用。对用户来讲,集群相当于一台服务器。而实际上,同一用户的两次请求可能被分配到两台不同的服务器上来处理。这样一来,怎样保证两次请求中存取的session值一致呢?
多数的服务器会使用session复制的方法:当session的值被改变时,将它复制到其它机器上。这个方案又有两种具体的实现,一种是广播的方式。这种方式下,任何一台服务器都保存着所有服务器所接受到的session对象。服务器之间随时保持着同步,因而所有服务器都是等同的。可想而知,当访问量增大的时候,这种方式花费在广播session上的带宽有多大,而且随着机器增加,网络负担成指数级上升,不具备高度可扩展性。另一种是TCP-ring的方式,也就是把集群中所有的服务器看成一个环,A->B->C->D->A,首尾相接。把A的session复制到B,B的session复制到C,……,以此类推,最后一台服务器的session复制到A。这样,万一A宕机,还有B可以顶上来,用户的session数据不会轻易丢失。但这种方案也有缺点:一是配置复杂;二是每增添/减少一台机器时,ring都需要重新调整,这将成为性能瓶颈;三是要求前端的LoadBalancer具有相当强的智能,才能将用户请求分发到正确的机器上。
另一种保存session的思路是,将session保存在单一的数据源中。这个数据源可被集群中所有的机器所共享。这样一来,复制就不是问题了。然而性能成了问题。每个用户请求,都需要访问后端的数据源(很可能是数据库)来存取用户的数据。这种思路的第二个问题是:缺少应用服务厂商的支持—— 很少有应用服务器支持这种方案,更不用说数据源有很多种(MySQL、Oracle、Hsqldb等各种数据库、专用的session server等)了。第三个问题是:数据源成了系统的瓶颈,一但这个数据源崩溃,所有的应用都不可能正常运行了。
综上所述,session看起来很简单,实际上是一个大问题!犹其是对一个访问量极高的大型网站而言。
2.解决方案
2.1.将session数据保存在客户端
前面所说的通过复制,或者通过单一数据源,都可以解决集群中分布式session的问题,然而存在前面所说的很多问题。另一种思路,是把session保存在客户端。这样一来,服务器就变成无状态了,就能够做到线性可扩展和极高的可用性。怎么保存呢?目前唯一的方法,恐怕就是cookie了。
Cookie方案也不是完美的,有以下限制:
1. Cookie数量和长度的限制。每个domain最多只能有20条cookie,每个cookie长度不能超过4KB,否则会被截掉。
2. 安全性问题。如果cookie被人拦截了,那人就可以取得所有的session信息。即使加密也与事无补,因为拦截者并不需要知道cookie的意义,他只要原样转发cookie就可以达到目的了。
3. 有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数器。如果我们把这个计数器保存在客户端,那么它起不到任何作用。
虽然有上述缺点,但是对于其优点(极高的扩展性和可用性)来说,就显得微不足道。然而我们仍然要设法回避上述的缺点,方法是:
1. 通过良好的编程,控制保存在cookie中的session对象的大小。
2. 通过加密和安全传输技术(SSL),减少cookie被破解的可能性。
3. 只在cookie中存放不敏感数据,即使被盗也不会有重大损失。
4. 控制cookie的生命期,使之不会永远有效。偷盗者很可能拿到一个过期的cookie。
2.2.将session保存在专用的服务器上
前面提到,由于cookie机制本身的限制,我们不可能将所有的数据都放在cookie中,势必要有服务器端的方案来辅助。相比复制的方法,单一的数据源更简单更可靠。我们可以使用数据库来保存这部分session。然而我倾向于使用更廉价、更轻量的存储。事实证明,BerkeleyDB是一种很好的选择。
由于我们只把少量关键的信息保存在服务端,因而这个数据源的压力不会非常大。事实证明,BerkeleyDB在保持良好的可用性的前提下,具有相当高的系统容量,可以高效处理大量的数据请求。
2.3.创建通用的session框架
不是Java Servlet API的session机制不够灵活,而是多数应用服务器并没有留出足够的余地,来让你自定义session的存储方案。根据上面的讨论,任何一种session方案都有其优缺点。最好的方法是把它们结合起来。例如:结合cookie-based session和berkeleyDB-based session,就可以解决我们的绝大部分问题。
纵使某个应用服务器提供了对外扩展的接口,可以自定义session的方案,我们也不大可能使用它。为什么呢?因为我们希望保留选择应用服务器软件的自由。
因此,最好的方案,不是在应用服务器上增加什么新功能,而是在WEB应用框架上做手术。一但我们在WEB应用框架中实现了这种灵活的session框架,那么我们的应用可以跑在任何标准的JavaEE应用服务器上。
除此之外,一个好的session框架还应该做到对应用程序透明。具体表现在:
1. 使用标准的HttpSession接口,而不是增加新的API。这样任何WEB应用,都可以轻易在两种不同的session机制之间切换。
2. 应用程序不需要知道session中的对象是被保存到了cookie中还是别的什么地方。
3. Session框架可以把同一个session中的不同的对象分别保存到不同的地方去,应用程序同样不需要关心这些。例如,把一般信息放到cookie中,关键信息放到berkeleyDB中。甚至同是cookie,也有持久和临时之分,有生命期长短之分。
3.Session框架详解
我们实现了这个session框架。
目前,这个session框架是依赖于我们的通用service框架的。由于service框架是Webx框架的基础,所以Webx自然可以方便地使用这个session框架。
对于webx之外的WEB应用 —— 例如:独立运行的JSP、由其它应用框架如webwork制作的应用 —— 我们提供了一个filter。这样所有的应用都可以使用我们的session框架,从而支持cookie-based session、berkeleyDB-based session以及扩展出任意类型的session实现。
你可以从Subversion中checkout框架的源码:
svn co http://svn.alibaba-inc.com/repos/opentech/toolkit/trunk/webx/session/
或二进制包:
cd repository.project\toolkit\webx
svn co http://svn.alibaba-inc.com/repos/binary/repository.project/toolkit/webx/session/
不过这个框架还会直接或接间地依赖很多其它的包(有Antx来管理其依赖关系,你不必惊慌)。
3.1.开始装配session系统
从现在起,我们开始演示如何配置一个实际的session系统。并在配置的过程中,进一步解说session框架的详情。
最常见的情况下,session系统是由Webx框架装载的。为了在Webx框架中使用session框架,需要修改WEB-INF/webx.xml文件中的RunDataService。RunDataService是前面所述的RequestContext框架的入口:
<servicename="RunDataService"class="com.alibaba.webx.service.rundata.DefaultRunDataService"> <!-- - 将response.getWriter()和response.getOutputStream()的内容缓存起来。 --> <property name="request.buffered.class" value="com.alibaba.webx.request.context.buffered.BufferedRequestContextFactory"/> <!-- - 拦截sendRedirect等可能导致response提交的操作,延后至请求结束才做。 --> <property name="request.lazycommit.class" value="com.alibaba.webx.request.context.lazycommit.LazyCommitRequestContextFactory"/> <!-- - 自动parse参数(包括upload form)。 --> <property name="request.parser.class" value="com.alibaba.webx.request.context.parser.ParserRequestContextFactory"/> <!-- - Session框架的配置。 --> <property name="request.session"> <property name="class"value="com.alibaba.webx.session.request.SessionRequestContextFactory"/> (…未完待续)</property> <!-- - 设置请求的locale/charset。 --> <property name="request.locale"> <property name="class"value="com.alibaba.webx.request.context.locale.SetLocaleRequestContextFactory"/> <propertyname="defaultLocale" value="zh_CN"/> <propertyname="defaultCharset" value="GBK"/> </property></service>
上面的配置文件还没有完成。我们先来看看这段配置是什么意思。
3.2.Session的总体结构
从表面上看,实现一个session框架很简单,但实际上,它同时设及到对request和response两个对象的修改。好在Java Servlet API提供了HttpServletRequestWrapper和HttpServletResponseWrapper类来包装request和response。为此,我们设计了一个接口:RequestContext。该接口同时包装了request和response对象。不仅如此,多个RequestContext还能串接起来,形成一条链,以创造出不同的功能组合。RequestContext是另一个独立的框架,请参见相关的文档。我们的session框架只不过实现了RequestContext接口,并由该框架来引导session的创建过程。
下图简述了Session框架的整体静态结构。
很明显前面RunDataService的配置被划分成很多“段”:request.buffered.class、request.lazycommit.class、request.parser.class、request.session.class、request.locale.class等。其实这就是所谓的RequestContext链。每一“段”都是一个RequestContext的实现,都会对request和(或)response进行一层包装,以便实现一种特定的功能。这个RequestContext框架不是专为session框架设计的,因此上面配置中有几个request context的实现和session框架是没有关系的。例如:request.parser.class的功能是解析request parameters,透明处理multipart-form格式的请求;request.locale.class的功能是设置当前请求的输入/输出locale和charset。我们将在另文中对这些RequestContext进行详细讨论。
值得注意的是剩下的三个request context:request.buffered.class、request.lazycommit.class和request.session.class。前两个request context —— request.buffered.class和request.lazycommit.class ——并不是session框架的一部分,但是没有它们,就不能实现cookie-basedsession。为什么呢?这要从HTTP协议谈起。下面是一个标准的HTTP响应的文本。无论你的服务器使用了何种平台(Apache HTTPDServer、JavaServlet/JSP、Microsoft IIS,……),只要你通过浏览器来访问,必须返回类似下面的HTTP响应:
HTTP/1.1 200 OKServer:Apache-Coyote/1.1Set-Cookie:JSESSIONID=AywiPrQKPEzfF9OZ;Path=/Content-Type:text/html;charset=GBKContent-Language:zh-CNContent-Length:48Date: Mon, 06 Nov2006 07:59:38 GMT <html><body>
……
我们注意到,HTTP响应分为Header和Content两部分。从“HTTP/1.1 200 OK”开始,到“<html>”之前,都是HTTP Header,后面则为HTTP Content。而cookie是在header中指定的。一但应用服务器开始向浏览器输出content,那就再也没有机会修改header了。问题就出在这里。作为session的cookie可以在应用程序的任何时间被修改,甚至可能在content开始输出之后被修改。但是此后修改的session将不能被保存到cookie中。
JavaServlet API的术语称“应用服务器开始输出content”为“response被提交”。你可以通过response.isCommitted()方法来判断这一点。那么,哪些操作会导致response被提交呢?
1. 向response.getWriter()或getOutputStream()所返回的流中输出,累计达到服务器所设定的一个chunk的大小,通常为8K。
2. 用户程序或系统调用response.flushBuffer()。
3. 用户程序或系统调用response.sendError()转到错误页面。
4. 用户程序或系统调用response.sendRedirect()重定向。
只要避免上述情形的出现,就可以确保cookie可以被随时写入。
前两个request context —— request.buffered.class和request.lazycommit.class正好解决了上面的问题。第一个RequestContext(request.buffered.class)将所有的输出到response.getWriter()或getOutputStream()的内容缓存在内存里,直到最后一刻才真正输出到浏览器;第二个RequestContext(request.lazycommit.class)拦截了response对象中引起提交的方法,将它们延迟到最后才执行。这样就保证了在cookie被完整写入之前,response绝不会被任何因素提交。
此外,request.buffered.class不是专为session框架而设计的。Webx的页面布局系统也依赖这个RequestContext。
下面,我们进一步讨论session request context的配置,也就是标记“…未完待续”的那部分配置:
<servicename="RunDataService"class="com.alibaba.webx.service.rundata.DefaultRunDataService"> …… <!-- - Session框架的配置。 --> <property name="request.session"> <property name="class"value="com.alibaba.webx.session.request.SessionRequestContextFactory"/> (…未完待续)</property> ……</service>
3.3.Session的创建过程
在配置文件中,我们指定了:request.session.class = c.a.w.s.r.SessionRequestContextFactory。
SessionRequestContextFactory是由RequestContext框架激活的。当它初始化的时候,它就会根据配置文件的内容来创建适当的SessionIDBroker、SessionStore等对象。接下来,它会创建session框架的核心对象:SessionRequestContextImpl。这个对象中包装了原始的request和response,并返回给系统一对修改过的request和response。用户的应用程序最终会通过这个修改过的request对象取得session对象(通过request.getSession()调用),这样一来,就得到了实现标准HttpSession接口的SessionImpl对象。
整个过程如下图:
通常,Java Servlet风格的session系统,会返回一个名叫JSESSIONID的cookie给浏览器,这个cookie中包含了session的主键。系统就是通过这个cookie来跟踪session的。Java Servlet API同时支持将JSESSIONID编码到URL中。这种方式我们的session框架同样支持。现在的问题是,如何生成这个ID?
为了达到最大的兼容性,我们分两种情况来处理JSESSIONID:当一个新session到达时,假如cookie或URL中已然包含了JSESSIONID,那么我们将直接利用这个值。为什么这样做呢?因为这个JSESSIONID可能是由同一域名下的另一个不相关应用生成的。如果我们不由分说地将这个cookie覆盖掉,那么另一个应用的session就会丢失。理想的情况下,对于一个新session,应该是不包含JSESSIONID的。这时,我们需要利用SessionIDBroker来生成一个唯一的字符串,作为JSESSIONID。SessionIDBroker是一个接口,其实现是可被替换的。如果不加指定,默认的实现为RandomSessionIDBroker。
下面罗列出一系列和JSESSIONID相关的配置,所有配置均置身于前例标记“…未完待续”的那部分之中。
² 是否将JSESSIONID保存在cookie中,默认为true。如果为false,应用必须调用response.encodeURL()或response.encodeRedirectURL()来将JSESSIONID保存在URL中。参见:session.urlencode.enabled开关。
<propertyname="session.cookie.enabled" value="true"/>
² 指定保存session ID的cookie的名字,默认为"JSESSIONID"。
<propertyname="session.cookie.name" value="JSESSIONID"/>
² 指定session ID cookie的domain,如果不设置,则不发送domain。这意味着浏览器认为你的cookie属于当前域名。如果你的应用包含多个子域名,例如:www.alibaba.com、china.alibaba.com,而你又希望它们能共享session的话,请把域名设置成“alibaba.com”。
<propertyname="session.cookie.domain" value="alibaba.com"/>
² 指定session ID cookie的path,默认为"/"根目录。通常不需要修改这个默认值。
<propertyname="session.cookie.path" value="/"/>
² 指定session ID cookie的寿命(过期时间),单位是秒。默认为0,意味着cookie持续到浏览器被关闭(或称临时cookie)。有效值必须大于0,否则均被认为是临时cookie。
<propertyname="session.cookie.maxAge" value="0"/>
² 是否允许将session ID编码到URL中,默认为false。注意,如果打开该选项,必须关闭session.cookie.enabled。
<propertyname="session.urlencode.enabled" value="false"/>
² 指定在URL中表示session ID的名字,默认和cookie名相同:"JSESSIONID"。此时,如果session.urlencode.enabled为true的话,调用response.encodeURL("http://localhost:8080/test.jsp?id=1")将得到类似这样的结果:"http://localhost:8080/test.jsp;JSESSIONID=xxxyyyzzz?id=1"。
<propertyname="session.urlencode.name" value="JSESSIONID"/>
² 指定用来生成Session ID的类。默认为RandomSessionIDBroker。如果你扩展的新的SessionIDBroker需要一些额外的参数,请使用下面的第二种配置形式。
<propertyname="session.idbroker.class" value="com.alibaba.webx.session.idbroker.random.RandomSessionIDBroker"/>
或:
<propertyname="session.idbroker">
<property name="class" value="com.alibaba.webx.session.idbroker.random.RandomSessionIDBroker"/>
<property name="xyz" value="123"/>
</property>
3.4.Session的生命期
所谓生命期,就是session从创建到失效的整个过程。如下图所示:
总结一下,其实很简单:
1. 第一次打开浏览器时,JSESSIONID还不存在,或者存在由同一域名下的其它应用所设置的无效的JSESSIONID。这种情况下,session.isNew()返回true。
2. 随后,只要在规定的时间间隔内,以及cookie过期之前,每一次访问系统,都会使session得到更新。此时session.isNew总是返回false。Session中的数据得到保持。
3. 如果用户有一段时间不访问系统了,超过指定的时间,那么系统会清除所有的session内容,并将session看作是新的session。
4. 用户可以调用session.invalidate()方法,直接清除所有的session内容。此后所有试图session.getAttribute()或session.setAttribute()等操作,都会失败,得到IllegalStateException异常,直到下一个请求到来。
在session框架中,有一个重要的特殊对象,用来保存session生命期的状态。这个对象叫作session model。它被当作一个普通的对象存放在session中,但是通过HttpSession接口不能直接看到它。
下面是有关session生命期控制的配置文件项。这些配置也是置身于前例标记“…未完待续”的那部分之中
² 指定session不活动而失效的期限,单位是秒。默认为0,也就是永不失效(除非cookie失效)。例如,设置3600秒,表示用户离开浏览器1小时以后再回来,session将重新开始,老数据将被丢弃。
<propertyname="session.maxInactiveInterval" value="0"/>
² 指定用于保存session状态的对象的名称。默认为"SESSION_MODEL"。一般不需要修改这个值。
<propertyname="session.model.name" value="SESSION_MODEL"/>
² 是否每次都touch session(即更新最近访问时间)。如果是false,那么只在session值有改变时touch。当将session model保存在cookie中时,设为false可以减少网络流量。但如果session值长期不改变,由于最近访问时间一直无法更新,将会使session超过maxInactiveInterval所设定的秒数而失效。默认为false。
<propertyname="session.touch.always" value="false"/>
3.5.Session的保存
至此,我们开始讨论session框架中最核心的部分:SessionStore。Session框架最灵活的部分就在于此。我们可以定义很多个session store,让不同的session对象分别存放到不同的Session Store中。前面提到有一个特殊的对象:SESSION_MODEL也必须保存在某个session store中。
Session store的配置包含两部分内容:
1. 如何创建Session store?配置session store的实现类名、初始化参数等。
2. 将指定key的对象保存在哪个store中?配置匹配方案。
基本配置方法如下:
<servicename="RunDataService" class="com.alibaba.webx.service.rundata.DefaultRunDataService"> (……其它RequestContext的配置) <!-- - Session框架的配置。 --> <property name="request.session"> <property name="class"value="com.alibaba.webx.session.request.SessionRequestContextFactory"/> (……前述关于JSESSIONID和生命期的配置) <!--Session store 1 --> <property name="session.store.Store名称1"> <property name="class"value="Store类名"/> <!-- 匹配方案 --> <property name="match"value="精确匹配,或*代表匹配所有"/> <property name="matchRegex" value="匹配正则表达式"/> <!-- 其它参数,取决于具体的store实现 --> </property> <!-- Session store 2 --> <property name="session.store.Store名称2"> <!-- 完全类似store 1 --> </property> <!-- 更多session stores --></property> (……其它RequestContext的配置)</service>
下面是一段cookie store配置的范例:
<!-- - temporary cookie store --><propertyname="session.store.temporary"> <property name="class"value="com.alibaba.webx.session.store.cookie.CookieStore"/> <property name="match"value="*"/> <property name="cookie.name"value="tmp"/> <propertyname="cookie.permanent" value="false"/></property><!-- - permanent cookie store --><propertyname="session.store.permanent"> <property name="class"value="com.alibaba.webx.session.store.cookie.CookieStore"/> <property name="match"value="userId"/> <property name="matchRegexp"value="login_\w+"/> <property name="matchRegexp"value="history_\w+"/> <property name="cookie.name"value="pmt"/> <propertyname="cookie.permanent" value="true"/></property>
需要注意以下几点:
² 你可以配置任意多个session store,只要名字不重复。
上例中,temporary和permanent分别是两个session store的名称。
² 可以包含若干个match属性,用来精确匹配session key。一个特别的值是“*”,它代表默认匹配所有的key。在整个session配置中,只能有一个store拥有默认的匹配。
上例中,如果我的程序调用session.setAttribute("userId",user.getId()),那么这个ID值将被保存到permanent store里;
而session.setAttribute("someKey",something)将被默认匹配到temporarystore中。
² 可以包含若干个matchRegexp属性,用正则表达式来匹配session key。
上例中,login_a、login_b、history_1等key都将被保存到permanent store里;
² 匹配遵循最大匹配的原则,假如有两个以上的表达式被同时匹配,匹配长度最长的胜出。默认匹配总是在所有的匹配都失败以后才会激活。
² 必须有一个session store能够匹配session model —— 用来存放session的生命期状态。你可以用<property name="match" value="*"/>来匹配session model,也可以用精确匹配:<propertyname="match" value="SESSION_MODEL"/>。其中session model的名字是必须和前述session.model.name的配置相同,默认为“SESSION_MODEL”。
3.6.CookieStore详解
前面的例子中,已经多次出现了cookie store的身影。确实,实现cookie store是我们的session框架的最重要的设计目标之一。上文已经给出了一段cookie store配置的范例,下面我们将比较详细地解释一下cookie store的配置方法。
² 指定cookie的名称。假设名称为“tmp”,那么将生成tmp0、tmp1、tmp2等cookie。多个cookie store的cookie名称不能重复。
<propertyname="cookie.name" value="…"/>
² 指定cookie的域名和路径。默认值为JSESSIONID cookie的域名和路径。因此一般不需要特别设置这两个值。
<propertyname="cookie.domain" value="alibaba.com"/>
<propertyname="cookie.path" value="/"/>
² 指定cookie的寿命(过期时间),单位是秒。默认为0,意味着cookie持续到浏览器被关闭(或称临时cookie)。有效值必须大于0,否则均被认为是临时cookie。
<propertyname="cookie.maxAge" value="0"/>
² 是否在多个session中共享当前store中的对象?默认为false。
如果这个值为true,必须同时设置一个大于0的cookie.maxAge。反之,即使maxAge大于0,但permanent为false,那么对象的值也不可能在多个session之间共享。
<propertyname="cookie.permanent" value="false"/>
² 指定每个cookie的最大长度。默认为3993,即3.9K。
Cookie store会把所有对象序列化到cookie中。但是前面讲过cookie的长度是不能超过4K的。因此我们必须限制cookie的长度,如果超过这个长度,就把数据分发到新的cookie中去。因此一个cookie store实际可能产生好几个cookie。假设cookie.name为tmp,那么所生成的cookie的名称将分别为:tmp0、tmp1、tmp2,以此类推。
<propertyname="cookie.maxLength" value="3993"/>
² 指定cookie的最大个数。默认为5。
因此,实际cookiestore可生成的cookie总长度为:cookie.maxLength * cookie.maxCount。如果超过这个长度,cookie store将会在日志里面发出警告(WARN级别),并忽略store中的所有对象。
<propertyname="cookie.maxCount" value="5"/>
² 是否创建概要cookie。默认为false。
有时由于域名/路径等设置的问题,会导致cookie紊乱。例如:发现同名的cookie、cookie缺失等错误。这些问题很难跟踪。概要cookie就是为检查这类问题提供一个线索。如果将些开关打开,将会产生一个概要性的cookie。假如cookie.name为tmp,那么概要cookie的名字将是tmpsum。概要cookie会指出当前store共有几个cookie,每个cookie的前缀等内容。当cookie的总数和内容与概要cookie不符时,系统将会在日志中提出详细的警告信息(DEBUG级别)。请尽量不要在生产系统中使用这个功能。
<propertyname="cookie.summary" value="false"/>
² 最后,还有一个非常重要的配置项:cookie.encoder。默认的cookie encoder为EncryptCookieEncoderImpl。如果你扩展的cookie encoder有额外的配置,请使用下面第二种形式。
<propertyname="cookie.encoder.class"value="com.alibaba.webx.session.store.cookie.encoder.EncryptCookieEncoderImpl"/>或:<propertyname="cookie.encoder"><property name="class" value="com.alibaba.webx.session.store.cookie.encoder.EncryptCookieEncoderImpl"/><property name="xyz" value="123"/></property>
Cookie encoder是做什么的呢?它的功能就是把一个对象序列化成一个cookie可以接受的字符串,或反之。这个过程通常会包括序列化、加密、压缩、Base64转换等操作。你完全可以实现一种更有效的序列化算法,来替换默认的实现。目前cookie encoder包含了4个实现:
你可以任选其中之一种cookie encoder来配置你的cookie store。当然不同的cookie store完全可以配置不同的cookie encoder。
3.7.其它Session Store
3.7.1.SimpleMemoryStore
SimpleMemoryStore是最简单的session store。它将所有的session对象都保存在内存里面。这种store不支持多台机器的session同步,而且也不关心内存是否被用尽。因此这种简单的store一般只应使用于测试环境。
配置的方法极简单:
<!-- Simplememory store --><propertyname="session.store.mySimpleStore"> <property name="class"value="com.alibaba.webx.session.store.simple.SimpleMemoryStore"/> <!-- 匹配方案 --> <property name="match"value="…"/> <property name="matchRegex"value="…"/></property>
3.7.2.MultiplexSessionStore
有时候为了兼容的原因,或其它的原因,我们需要将一个attribute同时写入到两个或以上的session store中。MultiplexSessionStore就是为了解决这个问题。它本身不实现任何实际的session store的逻辑,而是将所有的session store请求转发给一个或多个session store。
配置的方法也不复杂:
<!-- store 1--><propertyname="session.store.store1"> <property name="class"value="…"/> <!-- 请不要配置匹配方案--></property> <!-- store 2--><propertyname="session.store.store1"> <property name="class"value="…"/> <!-- 请不要配置匹配方案--></property> <!-- multiplexstore --><propertyname="session.store.myMultiplexStore"> <property name="class"value="com.alibaba.webx.session.store.multiplex.MultiplexSessionStore"/> <!-- 匹配方案 --> <property name="match"value="…"/><property name="matchRegex" value="…"/> <!-- 转发到哪些store--><property name="store"> <value>store1</value> <value>store2</value></property></property>
3.8.实现新的Session Store
要创建一个新的session store并不复杂。你只需要实现一个接口:SessionStore。
public interfaceSessionStore { void init(String storeName,Configuration configuration, SessionConfig sessionConfig) throwsServiceInitializationException; Iterator getAttributeNames(String sessionID,StatusHolder statusHolder); Object loadAttribute(String attrName,String sessionID, StatusHolder statusHolder); void invaldiate(StringsessionID, StatusHolder statusHolder); void commit(Map attrs, StringsessionID, StatusHolder statusHolder);}
3.8.1.初始化session store
void init(String storeName,Configuration configuration, SessionConfig sessionConfig)
throwsServiceInitializationException;
当session框架被初始化时,所有在其中的session store的init方法都会被调用。在init方法里面,
1. 你可以取得当前session store的名称;
2. 你可以从configuration对象中取得当前session store的配置;
3. 你还可以用sessionConfig对象取得整个session框架的配置信息。
3.8.2.装载数据
Object loadAttribute(String attrName,String sessionID, StatusHolder statusHolder);
当下列情况下,相应session store的loadAttribute方法将被调用:
1. 当用户调用:session.getAttribute(attrName)时,系统将从匹配attrName的session store中装载该对象。
2. 在一个请求中,用户第一次调用request.getSession()时,系统将尝试从匹配的session store中装载SESSION_MODEL对象。
在同一个request中,对于同一个session store和同一个attrName,loadAttribute方法至多被调用一次。
3.8.3.保存(提交)数据
void commit(Map attrs, StringsessionID, StatusHolder statusHolder);
当一个请求结束时,系统将调用commit方法,来通知sessionstore将数据保存起来。
在同一个request中,除非touchAways开关为false,并且没有修改/增加/删除任何session对象,那么该方法一定会,且仅会被调用一次。
被提交的attrs表中,仅包括新增的和修改的对象。如果对象被删除,则值为null。所有的sessionstore均会得到一次机会提交,即使其attrs为空。
3.8.4.遍历数据
Iterator getAttributeNames(StringsessionID, StatusHolder statusHolder);
当用户调用session.getAttributeNames()方法时,系统会向每个session store提出遍历的请求。
3.8.5.清除所有数据
void invaldiate(StringsessionID, StatusHolder statusHolder);
当下列情况下,session store的invalidate方法被调用:
1. 当用户调用:session.invalidate()时,系统将请求所有session store清除数据。
2. 在一个请求中,用户第一次调用request.getSession()时,无论是新的session,或是过期的session,系统都会请求所有的session store清除数据。
对于cookie store而言,有一种特殊情况:当cookie store的cookie.permanent为true时,cookie store在invalidate时不会清理数据。这样,这些数据就可以被多个session所共享。
3.8.6.优化session store
Session store在整个系统中被设计成singleton,因此,你不能在session store对象的成员变量中保存任何request scope的状态。然而,有时候这是必须的,例如:
² 对于数据库为基础的session store,为了性能的考虑,你希望第一次loadAttribute时,一次性从数据库中读取所有对象。随后的loadAttribute只需要从内存中读取就可以了。
² 对于cookie store,必须一次性解码所有的cookie。如果每次loadAttribute都去解码cookie就会很低效。
对于这些需求,session框架提供了一个StatusHolder接口。你可能已经留意到,所有的session store的接口方法中,都有一个statusHolder的参数。这就是给你存放状态的地方。除此以外,StatusHolder接口还为你提供了一些访问session系统的方法,例如:getHttpSession ()可以取得当前的session对象,getSessionRequestContext()可以取得当前的request context,从而取得当前session系统的一些配置。
看一下cookiestore中的代码片段,你就会明白了:
public IteratorgetAttributeNames(String sessionID, StatusHolder statusHolder) { Status status = getStatus(statusHolder); returnstatus.attributes.keySet().iterator();} public ObjectloadAttribute(String attrName, String sessionID, StatusHolder statusHolder) { Status status = getStatus(statusHolder); return status.attributes.get(attrName);} ... private StatusgetStatus(StatusHolder statusHolder) { Status status = (Status)statusHolder.getStatus(); if (status == null) { status = new Status(); statusHolder.setStatus(status); } ensureCookieLoading(status,statusHolder.getSessionRequestContext().getRequest(), statusHolder); return status;} ... private classStatus { private boolean cookieLoaded; private boolean cookieCommitted; private boolean cookieSummary; private Map requestCookies; private String mergedCookieValue; private Map attributes;}
4.实例示范
4.1.不使用Webx框架
Session框架可以被非webx应用(如JSP)直接使用。
4.1.1.创建项目(取得依赖的jar包)
首先,请创建一个antx项目,修改一下project.xml(也可以不用antx,但这是为了方便你取得依赖的jar包):
<?xmlversion="1.0" encoding="GB2312"?> <projectid="test/session"> <build> <dependencies> <include uri="toolkit/webx/filter"version="2.0"/> <includeuri="toolkit/webx/session" version="2.0"/> </dependencies> </build></project>
4.1.2.创建web.xml
对于antx项目,这个文件在src/descriptors/web/目录中。最终这个文件将出现在web应用的WEB-INF/目录下。
<?xmlversion="1.0" encoding="GB2312"?><!DOCTYPEweb-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"><web-app> <filter> <filter-name>rundata</filter-name> <filter-class>com.alibaba.webx.filter.rundata.RunDataFilter</filter-class> </filter> <filter-mapping> <filter-name>rundata</filter-name> <url-pattern>*.jsp</url-pattern> </filter-mapping> <servlet> <servlet-name>webx</servlet-name> <servlet-class>com.alibaba.webx.WebxControllerServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet></web-app>
4.1.3.创建webx.xml
对于antx项目,这个文件在src/webroot/WEB-INF/目录中。最终这个文件将出现在web应用的WEB-INF/目录下。
<?xmlversion="1.0" encoding="GB2312"?><configuration> <services> <service name="RunDataService"class="com.alibaba.webx.service.rundata.DefaultRunDataService"> <propertyname="request.buffered.class" value="com.alibaba.webx.request.context.buffered.BufferedRequestContextFactory"/> <property name="request.lazycommit.class" value="com.alibaba.webx.request.context.lazycommit.LazyCommitRequestContextFactory"/> <propertyname="request.parser.class" value="com.alibaba.webx.request.context.parser.ParserRequestContextFactory"/> <property name="request.session"> <property name="class"value="com.alibaba.webx.session.request.SessionRequestContextFactory"/> <property name="session.cookie.domain"value=""/> <propertyname="session.cookie.path" value="/"/> <!-- - temporary cookie store --> <property name="session.store.temporary"> <property name="class" value="com.alibaba.webx.session.store.cookie.CookieStore"/> <property name="match" value="*"/> <property name="cookie.name" value="tmp"/> <property name="cookie.permanent"value="false"/> </property> </property> <propertyname="request.locale"> <propertyname="class" value="com.alibaba.webx.request.context.locale.SetLocaleRequestContextFactory"/> <property name="defaultLocale"value="zh_CN"/> <propertyname="defaultCharset" value="GBK"/> </property> </service> </services></configuration>
4.1.4.创建测试用的JSP
在src/webroot/目录下创建一个test.jsp:
<%@ pagecontentType="text/html; charset=GBK" pageEncoding="GBK"%><html><body><%Integer count =(Integer) session.getAttribute("count"); if (count ==null) { count = new Integer(0);} count = newInteger(count.intValue() + 1); session.setAttribute("count",count);%> Count:<%=count%></body></html>
4.1.5.设置日志(可选、推荐)
设置一下log4j.xml,这样可以查看session系统的日志。为了方便,我们将所有日志打印在屏幕上。
请在src/webroot/WEB-INF/目录下创建log4j.xml:
<?xmlversion="1.0" encoding="GB2312"?><log4j:configurationxmlns:log4j="http://jakarta.apache.org/log4j/"> <!-- =====================================================================--> <!-- 以下是appender的定义 --> <!--===================================================================== --> <appender name="PROJECT"class="org.apache.log4j.ConsoleAppender"> <param name="encoding"value="GB2312"/> <layoutclass="org.apache.log4j.PatternLayout"> <paramname="ConversionPattern" value="%-4r [%t] %-5p %c %x -%m%n"/> </layout> </appender> <!-- =====================================================================--> <!-- 以下是logger的定义 --> <!--===================================================================== --> <logger name="org.apache" additivity="false"> <level value="error"/> </logger> <!--===================================================================== --> <!-- Root logger的定义 --> <!-- =====================================================================--> <root> <level value="debug"/> <appender-refref="PROJECT"/> </root></log4j:configuration>
4.1.6.打包、运行
执行antx war打包、发布。在浏览器上输入:http://localhost:8080/session/test.jsp就可以看到结果。当然,域名、端口、context path都得根据你布署的实际情况来定。
4.2.使用Webx框架
使用webx框架是最自然的 ——把上面的web.xml配置中RunDataFilter去掉即可 —— Webx不需要这个filter。
5.总结
Session是个难题,特别是对于要求高扩展性和高可用性的网站来说。
我们在标准的Java Servlet API的基础之上,实现了一套全新的session框架。在此基础上可以进一步实现多种session的技术,例如:基于cookie的session、基于数据库的session、基于berkeley DB的session、基于内存的session,甚至也可以实现基于TCP-ring的session等等。最重要的是,我们能把这些技术结合起来,使每种技术的优点能够互补,缺点可以被避免。
所有这一切,对应用程序是完全透明的—— 应用程序不用知道session是如何实现的、它们的对象被保存到哪个session store中等问题 —— session框架可以妥善地处理好这一切。
- HttpSession框架指南
- HttpSession
- HttpSession
- HttpSession
- httpsession
- HttpSession
- HttpSession
- HttpSession
- HttpSession
- HttpSession
- HTTPSession
- HttpSession
- HttpSession
- HttpSession
- HttpSession
- HttpSession
- HttpSession
- HttpSession
- 《Java Concurrency in Practice》之可见性(Visibility)
- Oracle 物化视图 说明
- hbase shell使用secureCRT登录后无法backspace删除字符解决
- 使用虚拟机作为开发测试环境总结
- ubuntu有线无法上网(双系统下windows可以)--可以连接无法上网
- HttpSession框架指南
- myeclipse快捷键大全
- phpMyAdmin添加数据库独立管理用户
- hadoop遇到的问题: org.apache.hadoop.ipc.Client: Retrying connect to server异常的解决
- Objective-C对象模型
- 初识设计模式 chapter 03-装饰者模式
- Linux TCP 回收与重用
- location.reload(); 与window.location.href = window.location.href;的差别
- 嵌入式linux下,简单的misc设备驱动框架